Hello!
I've just recently discovered LaunchCodeRequirement API and I'm exploring how it works compared to existing alternatives available for macOS versions below 14.4.
Some questions I have with regards to safety of older and newer APIs examining the given example:
func runProcess(executableURL: URL) throws {
let process = Process()
process.executableURL = executableURL
if #available(macOS 14.4, *) {
process.launchRequirement = try LaunchCodeRequirement.allOf {
ValidationCategory(.developerID)
SigningIdentifier("some-signing-identifier")
TeamIdentifier("some-team-identifier")
}
} else {
try secStaticCodeCheckValidity(executableURL) // Point #1
}
do {
try process.run() // Point #2
if #available(macOS 14.4, *) {
// process.launchRequirement should take care of the process
// and kill it if launchRequirement constraint is not satisfied
} else {
try secCodeCheckValidity(process.processIdentifier) // Point #3
}
process.waitUntilExit()
} catch {
process.terminate()
throw error
}
// Point #4
guard process.terminationReason == .exit else {
throw SomeError()
}
}
let requirement =
"""
anchor apple generic
and identifier = "some-signing-identifier"
and certificate 1[field.1.2.840.113635.100.6.2.6]
and certificate leaf[field.1.2.840.113635.100.6.1.13]
and certificate leaf [subject.OU] = "some-team-identifier"
"""
func secStaticCodeCheckValidity(_ executableURL: URL) throws {
// Init SecStaticCode from `executableURL`
// Init SecRequirement from `requirement`
let flags = SecCSFlags(rawValue: kSecCSBasicValidateOnly)
guard SecStaticCodeCheckValidityWithErrors(code, flags, secRequirement, nil) == errSecSuccess else {
throw CodeSignError()
}
}
func secCodeCheckValidity(_ processIdentifier: Int32) {
// Init SecCode from `processIdentifier`
// Init SecRequirement from `requirement`
guard SecCodeCheckValidityWithErrors(code, [], secRequirement, nil) == errSecSuccess else {
throw CodeSignError()
}
}
Before macOS 14.4+ flow
There's still a small chance that between checking executable binary codesign requirement (Point #1) and launched process' one (Point #3) the binary could be replaced with something malicious and even get some CPU between Points #2 and #3 so technically it can't be 100% safe. Is that a correct statement? Any advices on making it safer?
macOS 14.4+ flow
Now let's see how launchRequirement is better. I guess initialized launchRequirement gets evaluated on running the process (Point #2).
- What does it exactly check? Executable at URL before launching the process (as OnDiskConstraint) or launched process (as ProcessConstraint)?
- Is there any chance the process gets some CPU before it's killed in case of failed codesign check?
- Any way to distinguish between codesign requirement termination and other reasons at point #4? It returns SIGKILL (9) as terminationStatus but it's not precise enough to be sure it was killed due to failed requirement check. I guess newer SecStaticCodeCheckValidityWithOnDiskRequirement & SecCodeCheckValidityWithProcessRequirement are the same as SecStaticCodeCheckValidityWithErrors & SecCodeCheckValidityWithErrors but a little simpler and can't be used as a 'more secure' way of validating codesign requirement.
Thanks,
Pavel
Largely.
The one exception point to keep in mind is that macOS 13 and later support app bundle protection, so if this tool is in your app bundle then you benefit from that. For more, see the WWDC 2022 talk referenced in Trusted Execution Resources.
No.
Well, if there were, that’d be a significant security bug (-:
I don’t want to get too deep into the details here, partly because they’re all implementation details that could change, but mostly because I don’t work on this stuff and thus there are limits to how much I can talk about it. However…
I think it’s say to say that Apple platforms have a trusted execution subsystem that controls the code that a process is allowed to load and run. A classic example of this is library validation. The LWCR mechanism is another input into that subsystem, which constrains the code that the process is allowed to use as its main executable.
Not really. As you’ve determined, from the parent’s perspective it looks like the process died very early with a SIGKILL
. There’s no additional information that comes along with that termination status. You could turn around and run code signing checks on the executable, but at best that only gives you indirect information.
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"