fix(process): classify withheld Darwin environments before parser failures

This commit is contained in:
morluto committed 2026-11-03 23:48:22 +08:00
1 parent 9a68ce57a0
commit 26b033d51f
3 files changed
+83 -22

No files matched your search

+23 -22
View File
@@ -228,6 +228,22 @@ func processRunTokenObservation(
parse: Result<ParsedRunToken, RunTokenReadError>,
codeSigningFlags: () -> UInt32?
) -> RunTokenObservation {
// Native inspection restrictions apply even when argv is incomplete or
// overwritten, before parsing reaches the environment. Keep this fact at
// the native boundary; cleanup still decides whether the process is related.
func withheldEnvironment() -> RunTokenObservation? {
guard let flags = codeSigningFlags() else { return nil }
if (flags & csPlatformBinary) != 0 {
return unavailable(pid: pid, reason: "platform_binary_environment_withheld")
}
if (flags & csRestrict) != 0 {
return unavailable(pid: pid, reason: "restricted_process_environment_withheld")
}
return nil
}
func unavailableEnvironment(_ reason: String) -> RunTokenObservation {
withheldEnvironment() ?? unavailable(pid: pid, reason: reason)
}
switch parse {
case .success(let parsed):
if let runId = parsed.runId {
@@ -238,40 +254,25 @@ func processRunTokenObservation(
if parsed.environmentEntryCount > 0 {
return RunTokenObservation(pid: pid, state: "readable", run_id: nil, reason: nil)
}
let flags = codeSigningFlags()
if let flags, (flags & csPlatformBinary) != 0 {
return unavailable(pid: pid, reason: "platform_binary_environment_withheld")
}
if let flags, (flags & csRestrict) != 0 {
return unavailable(pid: pid, reason: "restricted_process_environment_withheld")
}
if let withheld = withheldEnvironment() { return withheld }
return RunTokenObservation(pid: pid, state: "readable", run_id: nil, reason: nil)
case .failure(let failure):
switch failure {
case .environmentUnavailable:
let flags = codeSigningFlags()
if let flags, (flags & csPlatformBinary) != 0 {
return unavailable(pid: pid, reason: "platform_binary_environment_withheld")
}
if let flags, (flags & csRestrict) != 0 {
return unavailable(pid: pid, reason: "restricted_process_environment_withheld")
}
return unavailable(pid: pid, reason: "environment_unavailable")
return unavailableEnvironment("environment_unavailable")
case .ambiguousEnvironmentBoundary:
if let flags = codeSigningFlags(), (flags & csRestrict) != 0 {
return unavailable(pid: pid, reason: "restricted_process_environment_withheld")
}
return unavailable(pid: pid, reason: "ambiguous_environment_boundary")
return unavailableEnvironment("ambiguous_environment_boundary")
// These failures already establish that token bytes were available.
case .duplicateToken:
return unavailable(pid: pid, reason: "duplicate_run_token")
case .invalidTokenEncoding:
return unavailable(pid: pid, reason: "invalid_run_token_encoding")
case .appleVectorUnavailable:
return unavailable(pid: pid, reason: "apple_vector_unavailable")
return unavailableEnvironment("apple_vector_unavailable")
case .invalidBuffer:
return unavailable(pid: pid, reason: "malformed_procargs")
return unavailableEnvironment("malformed_procargs")
case .invalidBufferAt(let stage):
return unavailable(pid: pid, reason: "malformed_procargs_\(stage.rawValue)")
return unavailableEnvironment("malformed_procargs_\(stage.rawValue)")
}
}
}
@@ -1317,6 +1317,29 @@ it.skipIf(!onDarwin)(
missingAppleBoundaryFailsClosed: true,
malformedBuffersRejected: true,
invalidPointerSizeFailsClosed: true,
incompleteRestrictedArguments: [
"malformed_procargs_path_padding",
"malformed_procargs_arguments",
"apple_vector_unavailable",
].map((reason) => [
{
state: "unavailable",
reason: "platform_binary_environment_withheld",
},
{
state: "unavailable",
reason: "restricted_process_environment_withheld",
},
{ state: "unavailable", reason },
]),
signedReadableTokens: [true, true],
signedInvalidTokens: [
"duplicate_run_token",
"invalid_run_token_encoding",
].map((reason) => [
{ state: "unavailable", reason },
{ state: "unavailable", reason },
]),
malformedStages: [
{ state: "unavailable", reason: "malformed_procargs_header" },
{ state: "unavailable", reason: "malformed_procargs_argument_count" },
+37
View File
@@ -193,6 +193,40 @@ struct ProcessRunTokenParserProbe {
parse: .failure(.invalidBuffer),
codeSigningFlags: { 0 }
)
// A restricted native process can expose an incomplete argv layout before
// the parser reaches the withheld environment (CI observed mdworker here).
let incompleteRestrictedArguments = [
RunTokenReadError.invalidBufferAt(.pathPadding),
.invalidBufferAt(.arguments),
.appleVectorUnavailable,
].map { failure in
[UInt32(0x0400_0000), UInt32(0x0000_0800), nil].map { flags in
let observation = processRunTokenObservation(
pid: 1,
parse: .failure(failure),
codeSigningFlags: { flags }
)
return ["state": observation.state, "reason": observation.reason ?? ""]
}
}
let signedReadableTokens = [UInt32(0x0400_0000), UInt32(0x0000_0800)].map { flags in
let observation = processRunTokenObservation(
pid: 1,
parse: .success(realToken),
codeSigningFlags: { flags }
)
return observation.state == "readable" && observation.run_id == "owned-token"
}
let signedInvalidTokens = [RunTokenReadError.duplicateToken, .invalidTokenEncoding].map { failure in
[UInt32(0x0400_0000), UInt32(0x0000_0800)].map { flags in
let observation = processRunTokenObservation(
pid: 1,
parse: .failure(failure),
codeSigningFlags: { flags }
)
return ["state": observation.state, "reason": observation.reason ?? ""]
}
}
let duplicateTokenFailsClosed: Bool
do {
_ = try parseProcessEnvironment(in: fixture(
@@ -311,6 +345,9 @@ struct ProcessRunTokenParserProbe {
"malformedBuffersRejected": malformedBuffersRejected,
"invalidPointerSizeFailsClosed": invalidPointerSizeFailsClosed,
"malformedStages": malformedStages,
"incompleteRestrictedArguments": incompleteRestrictedArguments,
"signedReadableTokens": signedReadableTokens,
"signedInvalidTokens": signedInvalidTokens,
"malformedObservation": [
"state": malformedObservation.state,
"reason": malformedObservation.reason as Any? ?? NSNull(),