What is the alternative to Environment and Library Constraints before macOS 14.0?

In the macOS 14.0 SDK, environment and library constraints were introduced, which made defense against common attack vectors relatively simple (especially with the LightWeightCodeRequirements framework added in 14.4).

Now, the application I'm working on must support macOS 13.0 too, so I was looking into alternatives that do work for those operating systems as well.

What I found myself is that the SecCode/SecStaticCode APIs in the Security Framework do offer very similar fashion checks as the LightWeightCodeRequirements framework does:

  • SecCodeCopySigningInformation can return values like signing identifier, team identifier, code requirement string and so on.
  • SecStaticCodeCreateWithPath can return a SecStaticCode object to an executable/app bundle on the file system.

Let's say, I would want to protect myself against launchd executable swap.

  1. From macOS 14.0 onward, I would use a Spawn Constraint for this, directly in the launchd.plist file.
  2. Before macOS 14.0, I would create a SecStaticCode object for the executable path found in the launchd.plist, and then examine its SecCodeCopySigningInformation dictionary. If the expectations are met, only then would I execute the launchd.plist-defined executable or connect to it via XPC.

Are these two equivalent? If not, what are the differences?

Answered by DTS Engineer in 823018022

I’m not a big for of SMJobSubmit. I understand why folks use it, but I’m unconvinced that it’s better than SMJobBless. Most apps that update themselves want to be able to do this repeatedly, and SMJobBless facilitates that.

Written by berd-lp in 822885022
Only App Bundle Protection would prevent someone from doing this

App bundle protection is kinda key. I only see two ways to solve this problem properly:

  • SMJobBless, which makes a copy and then checks the copy

  • Relying on app data protection

Everything else suffers from at TOC/TOU, at least.

However, I agree that there are two levels of threat here:

  • Replacing the executable at any time

  • Replacing the executable at just the right time to hit the TOC/TOU window

The latter is much harder to pull off in practice.

Which brings us back to your original question. If you want to check that your helper tool is the code that you think it is — while acknowledging that your check is subject to TOC/TOU attacks on systems without app bundle protection or where app bundle protection is not working for some reason — then the code signing API is fine. I wouldn’t manually check the code’s attributes. Rather, craft an appropriate requirement — usually the existing DR on the tool is the right option here — and use SecCodeCheckValidityWithErrors to check that requirement.

I talk more these concepts in more depth in TN3127 Inside Code Signing: Requirements.

Share and Enjoy

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

What threat are you trying to protect against?

That matters because it’s easy for checks like this to devolve into security theatre. They seem cool but they don’t actually protect you from anything.

Specifically, I’ve found that checks like this often fall foul of two issues:

  • TOC/TOU problems

  • airtight hatchway problems — That is, if the launchd property list points to an executable in a protected location, and the attacker can replace that executable with a different one, what’s to stop the attacker replacing the launchd property list? Or the other code that’s checking the launchd property list?

Share and Enjoy

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

Those are really good questions Quinn!

I believe this issue not to be Security Theatre because of the following:

I am using SMJobSubmit to deploy a helper tool into the user or the system domain for a one-time use, as needed. The executable is planned to be distributed inside the main app bundle because of this. I am unable to copy it to a protected location without SMJobSubmit anyways - if my memory serves me right, as that is again a privileged operation.

I think this setup allows for a very easy way to replace the executable, allowing execution of arbitrary code as root. (Someone downloads the app, it's in the ~/Downloads folder and then that's not a protected location. Only App Bundle Protection would prevent someone from doing this, but regardless, replacing the executable this way is not a privileged operation still.)

Specifically, the attack I'm trying to protect the app against is

  • running the app,
  • getting the SMJobSubmit popup, and then
  • accidentally allowing a replaced executable to run with elevated privileges.

I understand based on the first point you provided (TOC/TOU) that my check might be timed wrong - and somewhat impossible to time right.

To be honest, my trust in spawn constraints has shaken too now - its implementation is private, so I don't even know if that times the checks right. Do you have any insight on that?

And also, if you don't think this defense idea makes sense is there then some other way to protect against the issue?

The only alternative I can think of is SMJobBless, because that has requirements to embed a code requirement in the app and the helper tool (probably for this exact reason) both. However I don't need ongoing privileges, and the daemon then have to clean up itself seemed like unnecessary hassle.

I’m not a big for of SMJobSubmit. I understand why folks use it, but I’m unconvinced that it’s better than SMJobBless. Most apps that update themselves want to be able to do this repeatedly, and SMJobBless facilitates that.

Written by berd-lp in 822885022
Only App Bundle Protection would prevent someone from doing this

App bundle protection is kinda key. I only see two ways to solve this problem properly:

  • SMJobBless, which makes a copy and then checks the copy

  • Relying on app data protection

Everything else suffers from at TOC/TOU, at least.

However, I agree that there are two levels of threat here:

  • Replacing the executable at any time

  • Replacing the executable at just the right time to hit the TOC/TOU window

The latter is much harder to pull off in practice.

Which brings us back to your original question. If you want to check that your helper tool is the code that you think it is — while acknowledging that your check is subject to TOC/TOU attacks on systems without app bundle protection or where app bundle protection is not working for some reason — then the code signing API is fine. I wouldn’t manually check the code’s attributes. Rather, craft an appropriate requirement — usually the existing DR on the tool is the right option here — and use SecCodeCheckValidityWithErrors to check that requirement.

I talk more these concepts in more depth in TN3127 Inside Code Signing: Requirements.

Share and Enjoy

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

What is the alternative to Environment and Library Constraints before macOS 14.0?
 
 
Q