macOS Sandbox and writing to system folders (audio plug-Ins)

Hello macOS gurus, I am writing an AUv3 plug-in and wanted to add support for additional formats such as CLAP and VST3. These plug-ins must reside in an appropriate folder /Library/Audio/Plug-Ins/ or ~/Library/Audio/Plug-Ins/. The typical way these are delivered is with old school installers.

I have been experimenting with delivering theses formats in a sandboxed app. I was using the com.apple.security.temporary-exception.files.absolute-path.read-write entitlement to place a symlink in the system folder that points to my CLAP and VST3 plug-ins in the bundle. Everything was working very nicely until I realize that on my Mac I had changed the permissions on these folders from

to

The problem is that when the folder has the original system permissions, my attempt to place the symlink fails, even with the temporary exception entitlement.

Here's the code I'm using with systemPath = "/Library/Audio/Plug-Ins/VST3/"

static func symlinkToBundle(fileName: String, fileExt: String, from systemPath: String) throws {
        guard let bundlePath = Bundle.main.resourcePath?.appending("/\(fileName).\(fileExt)") else {
            print("File not in bundle")
        }
        
        let fileManager = FileManager.default
        
        do {
            try fileManager.createSymbolicLink(atPath: systemPath, withDestinationPath: bundlePath)
        } catch {
            print(error.localizedDescription)
        }
    }

So the question is ... Is there a way to reliably place this symlink in /Library/... from a sandboxed app using the temporary exception entitlements? I understand there will probably be issues with App Review but for now I am just trying to explore my options.

Thanks.

Answered by DTS Engineer in 797383022

Is there a way to reliably place this symlink in /Library/... from a sandboxed app using the temporary exception entitlements?

Actually the better option here which is also compatible with the App Store is NSWorkspace's "Privileged File Operation" API. That API will only allow your perform very specific operation*, but one of those is "createSymbolicLink".

*For example, you can use this API to replace an existing file, but it cannot be used to create a new one.

It does require an entitlement which you'll need to request, but there are apps on the App Store using this API.

You use this API by:

-Call requestAuthorization, passing in the file operation you want to perform.

-The system will present an authorization dialog on your behalf and return an NSWorkspace.Authorization to your app (assuming the user approves it).

-You pass that authorization into FileManager.init(authorization:) to create a "special" FileManager object.

-You then call the method for the operation you requested using that FileManager and the system does the rest. So, for "createSymbolicLink", you'd call "createSymbolicLink(at:withDestinationURL:)". Note you MUST call that specific method and ONLY that method. Similar method like "createSymbolicLink(atPath..." or "linkItem(at:to:)" will not work and you should expect ANY other FileManager methods to work either.
__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

OK so it does work reliably if instead I use com.apple.security.temporary-exception.files.home-relative-path.read-write and put my symlink in the user library.

Would still be interested to know the answer to my original question if anyone has some insight!

Accepted Answer
The problem is that when the folder has the original system permissions, my attempt to place the symlink fails, even with the temporary exception entitlement.

Yep. macOS has multiple different subsystems that enforce file system permissions, and to perform an operation you have to get past all of them. For details, see On File System Permissions. In this case, the entitlement gets you past App Sandbox but then you crash into BSD.

Is there a way to reliably place this symlink in /Library/... from a sandboxed app using the temporary exception entitlements?

Much to my own surprise, the answer to this is “Yes.”

To pass the BSD check you must be running as root. There are various ways to do this, as I outline in BSD Privilege Escalation on macOS. One of those mechanisms [1], using SMAppService to install a daemon, works from a sandboxed app O-:

Now, the daemon itself has to be sandboxed, but that’s OK because you can apply temporary exception entitlements to it.

I understand there will probably be issues with App Review

That’s likely. My reading of the App Review Guidelines suggests that clause 2.4.5(v) is relevant. Also, my experience is that App Review takes a dim view of folks using temporary exception entitlements.

However, the usual caveat applies here: I don’t work for App Review and so I can’t make definitive statements on their behalf.

Share and Enjoy

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

[1] You may be able to get older mechanism working with sufficient temporary exception entitlements, but it’s probably better to avoid going down that path.

Is there a way to reliably place this symlink in /Library/... from a sandboxed app using the temporary exception entitlements?

Actually the better option here which is also compatible with the App Store is NSWorkspace's "Privileged File Operation" API. That API will only allow your perform very specific operation*, but one of those is "createSymbolicLink".

*For example, you can use this API to replace an existing file, but it cannot be used to create a new one.

It does require an entitlement which you'll need to request, but there are apps on the App Store using this API.

You use this API by:

-Call requestAuthorization, passing in the file operation you want to perform.

-The system will present an authorization dialog on your behalf and return an NSWorkspace.Authorization to your app (assuming the user approves it).

-You pass that authorization into FileManager.init(authorization:) to create a "special" FileManager object.

-You then call the method for the operation you requested using that FileManager and the system does the rest. So, for "createSymbolicLink", you'd call "createSymbolicLink(at:withDestinationURL:)". Note you MUST call that specific method and ONLY that method. Similar method like "createSymbolicLink(atPath..." or "linkItem(at:to:)" will not work and you should expect ANY other FileManager methods to work either.
__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Can this API be used to create a directory?

No. As Kevin mentioned, the available operations are very limited — see the NSWorkspace.AuthorizationType enum — and creating directories isn’t one of them )-:

But, yeah, I should mention this option in BSD Privilege Escalation on macOS. Lemme go fix that now…

Share and Enjoy

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

OK, so I was able to obtain the Privileged File Operations entitlement, but now I am needing some help getting it to work.

When I request authorization, I see the message that my app "wants to install symlinks in /usr/local.

Is it possible to change this path or is this API only for this location? I've only found a couple examples of people using it online and the ones I've come across are using it for installing command line tools.

When I get my authorization, I call the function signature specified in the documentation but then I crash with "Invalid path/URL argument" ... The URLs work if I force the operation with a temporary exception, so not sure what the issue is there.

I get this crash even if I try to create my symlink in /usr/local/.

Perhaps I can get some code-level support on this? Any help appreciated!

Is it possible to change this path or is this API only for this location?

No, apparently not. This is actually the first time I've looked closely at "NSWorkspaceAuthorizationTypeCreateSymbolicLink" (the other two are more common) and I didn't realize we'd specifically restricted it like this. If you haven't already, please file a bug on this specific limitation, as well as a more general bug asking for a better solution for installing these components. Please post bug numbers back here as well.

Having said that, with your issue here:

When I get my authorization, I call the function signature specified in the documentation but then I crash with "Invalid path/URL argument"

Can you post the actual error message and the code you're running? Nothing directly in the security code uses that particular message and I that code should be failing (with bad input), not crashing.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

macOS Sandbox and writing to system folders (audio plug-Ins)
 
 
Q