hardened runtime entitlements can't be used with child process?

I'm preparing a sandboxed app for macOS Catalina and adopting the hardened runtime, so I can participate in app notarization. Everything is generally working except for a child process my app sometimes needs to launch using NSTask. This child process is also sandboxed, inheriting the sandbox from its parent (the main app).


The problem is that the child process requires special entitlements to function. Specifically I'm referring to new hardened runtime entitlements like com.apple.security.cs.disable-library-validation. However, if I add such entitlements to the child process it fails to launch. This is somewhat expected, as the child process inherits the parent app's sandbox (via the entitlement com.apple.security.inherit). As described by Apple's documentation on inheriting a sandbox: "If you specify any other App Sandbox entitlement, the system aborts the child process". That means I am unable to directly add hardened runtime entitlements like com.apple.security.cs.disable-library-validation to the child's entitlements. As the docs say, adding the new entitlements prevent the child process from running at all.


I thought maybe the child process could inherit the required hardened runtime entitlements from its parent. I tried adding com.apple.security.cs.disable-library-validation to the main app's entitlements, hoping the child process would inherit the capability. However, this appears to be untrue. My child process failed as if the required entitlement was not present.


My questions are:


1. Is this expected? In other words, should a child process with an inherited sandbox also inherit its parent's hardened runtime entitlements?


2. Is there any way to workaround this, so the child process can be sandboxed and use additional hardened runtime entitlement?


Right now I am stuck at an impasse. I can either sandbox the child process or specify the hardened runtime entitlement, but not both. This prevents app notarization, which obviously will be a big problem once Catalina arrives.


Thanks for any help!

Replies

Why are you sandboxing? If this is for the Mac App Store, you don't need notarization. If you aren't releasing in the Mac App Store, you don't need to sandbox.

Sandboxing might not be required for an app distributed outside the App Store via Developer ID, but it's still a good idea. It is a security feature. Speaking as a regular user I know that I personally prefer running sandboxed apps. As a developer it's also nice to have consistent behavior for my app, which is distributed both in the App Store and externally.


But this question is moot. My app is sandboxed across the board and will remain that way, so I need to get app notarization working with it. Thanks all the same for your suggestion!

1. Is this expected?

Clearly not by you (-: Seriously though, the fact you’re hitting this problem doesn’t surprise me because it’s a natural consequence of how all of these bits fit together. However, I agree that it’s quite annoying, and I encourage you to file a bug about it.

Please post your bug number, just for the record.

2. Is there any way to workaround this, so the child process can be sandboxed and use additional hardened runtime entitlement?

One potential solution is to give the child process its own App Sandbox, which means you’re not relying on

com.apple.security.inherit
. There’s two ways to do this:
  • Make the ‘child’ process an app. I think you’ll still be able to launch it via

    NSTask
    , although you may end up needing to launch it via a more app-like mechanism (like
    NSWorkspace
    ).
  • Launch the child process from an XPC Service.

Whether this is practical depends on how much you depend on the parent/child relationship. For example, if you’re using pipes to establish two way communication between your app and the child process, replicating that will be tricky (although probably still possible).

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Thanks kindly for your reply Quinn! I appreciate it and the ideas you put forward (:


My child process is already a full app bundle, so that's not a roadblock. I can launch it via NSWorkspace as you suggested. However, as you anticipated I now have a problem with interprocess communication. I don't use pipes, but had earlier relied upon distributed notifications. Now that the apps are in separate sandboxes those communications are prevented. I see messages like this in the logs:


"attempt to post distributed notification '...' thwarted by sandboxing"


That's entirely expected. As the Apple docs state: "Sandboxed apps can send notifications only if they do not contain a dictionary". I need two-way communications allowing arbitrary data. Do you have any recommendations on how to accomplish that?


I don't know that your idea of using an XPC service will be feasible for me. My child process is something of a behemoth. It's a big open source project that my main app is leveraging. I'm not sure I can contort the project to be repackaged as an XPC service. It has its own expectations about how it runs its event loop and that kind of thing. My understanding is that there's no way to get around calling NSXPCListener's resume method in the service, which never returns control to the caller. I could try to shoehorn this project's code into the exported object's callback method, but I'm reluctant to try because it will take some doing and I fear it will fail in strange or insurmountable ways.

With all due respect, you are not a regular user. Regular users know nothing about the sandbox or the hardened runtime and care even less.


If you have your heart set on the sandbox, then just adopt that and distribute via the Mac App Store. You won't need the hardened runtime and Apple handles all the dirty work.


Wait, I see your last reply. Some open source projects are not compatible with the Mac App Store. GPL software is not allowed. Anything else is fine. However, I should caution you that the sandbox, and the hardened runtime to a lesser extent, change the runtime environment of your app and any child processes. Depending on what you need to use that open source project for, you may need to do extensive testing to ensure that it still functions. Sandbox problems can be very subtle. I have seen major differences depending on whether the sandboxed app is launched from the Finder vs. from the command line.


You can communicate between the apps using sockets (with appropriate network entitlements) or with named pipes.

You're right that most regular users don't know about sandboxing and don't care. But I still believe it's best for an app to adopt sandboxing if possible. It improves security for the user, whether they know it or not.


I'm aware that sandboxing can significantly change app behaviors, and in subtle ways. My app has been sandboxed for years. It was initially a lot of work, with several difficult quirks to iron out, but I'm happy with the results and tradeoffs, at least for my app. Other apps may find it be more or less of a nuisance, or total non-starter.


As for distributing only via the App Store, to avoid my hardened runtime impasse, that won't be possible. Although my app does sell via the App Store, its direct sales numbers are much higher.


Thank you for the suggestion to try sockets and named pipes. I don't have experience using them, but I expect that to be a more reliable solution than XPC services for my particular situation. A quick search through Google also suggests I can use CFMessagePortCreateLocal from inside the sandbox, if the port name is prefixed with my apps' group identifier. I'm going to give it a try and cross my fingers.


Thanks again for your thoughts.

My child process is already a full app bundle, so that's not a roadblock.

Cool.

However, as you anticipated I now have a problem with interprocess communication.

That should be solvable. More on this below.

I don't know that your idea of using an XPC service will be feasible for me. My child process is something of a behemoth. It's a big open source project that my main app is leveraging. I'm not sure I can contort the project to be repackaged as an XPC service.

I wasn’t suggesting that, but rather have your XPC Service sublaunch the helper process. Thus, the XPC Service is only present to allow you to switch sandboxes.

Still, this idea doesn’t really hold water given that your child process is already structured like an app.

I have two suggestions for inter-process communication, one that I prefer but may not work, and another that should work but is much less nicer:

  • You could create an XPC Service in your main app, and have both it and your secondary app talk to that XPC Service. You can then use XPC to transport an endpoint between the two, allowing for direct communication.

    Alas, I don’t think this will work, at least not directly. The first sticking point is sandboxing: The sandbox will prevent your secondary app from communicating with your XPC Service.

    You can probably work around this problem using an entitlement, but then you’ll run into the second problem, scoping. When an app starts an XPC Service, that service is only available to that app, not to other related apps.

    You’re not the first person to run into snags like this — folks building app extensions often want to do similar things — and my advice, assuming your like this approach, is to file an enhancement request describing your requirements.

  • Another option is to create an app group that you share between your main and secondary apps. You can then create a UNIX domain socket in that app group, and communicate via that.

    The downside to this approach is that communicating over a UNIX domain socket is a lot less pleasant than communicating over XPC.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Thank you for your additional help Quinn, and the clarification on what you meant about using XPC services. Given the potential points of failure that you described, I don't think trying an XPC service in my particular situation sounds like a good use of time.


I don't have any experience with unix sockets and they seem quite low level, so that approach isn't too appealing either.


In searching for other potential solutions, another one turned up that also involves an app group. It seems you can use CFMessagePort from inside the sandboxes of both apps, if the port name is prefixed with the group identifier of your apps. APIs like CFMessagePortSendRequest seem more convenient than unix sockets. Do you know of any potential problems with using this approach in my situation?

It seems you can use

CFMessagePort
from inside the sandboxes of both apps, if the port name is prefixed with the group identifier of your apps.

I did not know that. Is that officially documented somewhere?

CFMessagePort
is not my favourite API, but I’m not aware of any specific problems with it. If it works for you, that’s grand.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

I didn't see any official documentation that CFMessagePort allows sandboxed IPC via app groups. I only found several Google results that point out it's possible, eg: on stackoverflow. But it seems like a relatively safe assumption even lacking an official statement. Apple's open source code shows that CFMessagePort is implemented using mach ports, which are documented to allow sandboxed IPC in this way. Theoretically CFMessagePort's implementation could change (I don't see any guarantees that it uses mach ports) but that seems unlikely. I'd be more worried that CFMessagePort gets deprecated and goes away entirely.


I agree that the API isn't likely to be anyone's favorite. But it worked well enough for my purposes, and seems a bit nicer than dropping down to sockets or mach ports directly.


Thanks again for your help Quinn!

Apple's open source code shows that

CFMessagePort
is implemented using mach ports, which are documented to allow sandboxed IPC in this way.

Ah, yeah, that makes sense.

Theoretically

CFMessagePort
’s implementation could change (I don't see any guarantees that it uses mach ports) but that seems unlikely. I'd be more worried that
CFMessagePort
gets deprecated and goes away entirely.

Agreed.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

@MartinW @eskimo

I was happy to discover this thread, as I've been struggling with a similar issue for a month (My helper process seems to not inherit the the parent entitlements). I would greatly appreciate any insights or guidance.

If I try to inherit the entitlements (following all instructions from https://developer.apple.com/documentation/xcode/embedding-a-helper-tool-in-a-sandboxed-app), I have a library validation issue (seeming to indicate that the entitlements aren't inherited).

Launching the binary of the helper app from Terminal runs fine (even with all its sandboxed entitlements). As my helper process currently uses output and error pipes, communicating between Python and Swift's Process(), I'm looking for any method that can preserve similar IPC.

After reading this post, I couldn't determine the method Martin used to launch his helper process.

Do you have any insights on how to successfully launch and maintain such a helper process within the constraints of sandboxing?

If there is anything else I could specify about my setup or use-case, please let me know.