LaunchCodeRequirement alternatives

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).

  1. What does it exactly check? Executable at URL before launching the process (as OnDiskConstraint) or launched process (as ProcessConstraint)?
  2. Is there any chance the process gets some CPU before it's killed in case of failed codesign check?
  3. 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

Answered by DTS Engineer in 824935022
Written by pavel-kozlov-01 in 774345021
Is that a correct statement?

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.

Written by pavel-kozlov-01 in 774345021
Is there any chance the process gets some CPU before it's killed in case of failed codesign check?

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.

Written by pavel-kozlov-01 in 774345021
Any way to distinguish between codesign requirement termination and other reasons at point #4?

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"

Accepted Answer
Written by pavel-kozlov-01 in 774345021
Is that a correct statement?

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.

Written by pavel-kozlov-01 in 774345021
Is there any chance the process gets some CPU before it's killed in case of failed codesign check?

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.

Written by pavel-kozlov-01 in 774345021
Any way to distinguish between codesign requirement termination and other reasons at point #4?

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"

No.

Well, if there were, that’d be a significant security bug (-:

Nice! Thanks for confirming that! At least we can rely on LaunchCodeRequirement on macOS 14.4+.

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.

Well, I expected process.run() to throw an exception I could catch and learn from it what exactly has happened in case of failed codesign requirement check. I can submit a feature request if you think it's doable.

Everything is possible, it’s just bits after all, but it’s not necessarily easy. We can’t preflight this on the parent side because of TOC/TOU concerns, and Unix-y platform have a very limited ability to communicate state from the child back to the parent. So, if you do submit an enhancement request, make sure to include information about why you need this, to help justify the work involved.

And please post your bug number, just for the record.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

LaunchCodeRequirement alternatives
 
 
Q