Compiler adds RPATH to executable, making macOS Gatekeeper angry

I am deploying an extremely simple Swift helper command line app together with my Electron + .NET Core macOS application. The source code consist of only one small file and only one import (AppKit).

When deploying it together with my app, the whole app won't start (even though I code-sign and notarize everything), complaining that it comes from an "unidentified developer". I've checked the logs and found out that it is an RPATH entry that macOS (Gatekeeper?) is angry about:

/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/macosx

I'm builing the swift package with

swift build -c release

So nothing special.

If I remove the RPATH via

install_name_tool delete_rpath /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/macosx

after the build and before signing / notarizing, everything works fine, at least in a fresh Big Sur VM. So it doesn't seem like the RPATH entry is needed.

My question is: How can I keep the swift compiler from adding the RPATH entry in the first place, so I don't have to remove it after the build?

As I am totally new to swift, it could be that I'm asking the wrong question and that I have misunderstood something about dependencies, frameworks, ...

This is my Package.swift file. I use VSCode, not the Xcode UI.

// swift-tools-version:5.3
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
    name: "Server.MacOSHelper",
    dependencies: [
    ],
    targets: [
        .target(
            name: "Server.MacOSHelper",
            dependencies: [])
    ]
)

The compiler has some hidden flags to turn that off:

> swift --help-hidden | ag stdlib-rpath -A1
  -no-stdlib-rpath        Don't add any rpath entries.
  -no-toolchain-stdlib-rpath
                          Do not add an rpath entry for the toolchain's standard library (default)
  -toolchain-stdlib-rpath Add an rpath entry for the toolchain's standard library, rather than the OS's
  -trace-stats-events     Trace changes to stats in -stats-output-dir

You can try swift build -c release -Xswiftc -no-toolchain-stdlib-rpath, I think that will do what you want (I've never used Swift on a Mac, that definitely works on linux and Android).

Check that you are not disabling library validation when you build. Something like:

<key>com.apple.security.cs.disable-library-validation</key>
<true/>

Gatekeeper only runs these checks if you have library validation disabled, either because you haven’t enabled the hardened runtime or because you’ve applied the disable-library-validation hardened runtime exception entitlement.

1 Like

Thank you! Unfortunately neither -no-stdlib-rpath nor -no-toolchain-stdlib-rpath helped. :confused: Perhaps I have to deliver something together with my binary because I import AppKit? But, as I said, my single-file binary works totally fine on a fresh Big Sur VM.

Thank you! Unfortunately, com.apple.security.cs.disable-library-validation is "required for proper operation of .NET Core":

The same seems to apply to Electron as well:

https://github.com/electron-userland/electron-builder/issues/3940

You only need to disable library validation if you're linking with code that was signed by another third-party developer (that is, not you and not Apple). How is that the case here?

In my experience most folks using third-party runtimes include a copy of the runtime with their app and thus can (and in some cases must) re-sign the runtime with their own signing identity. This is the easiest and best solution to this problem. Disabling library validation has its place — specifically, if you plan to load plug-ins from various other third-party developers — but most standalone apps should ship with it enabled because it’s a significant security benefit.

Which isn’t to say that SPM should be adding random rpath entries to release code. That is, IMO, eminently bugworthy. You can work around that by removing the rpath entry using install_name_tool. However, in most cases that’s the wrong solution to this Gatekeeper issue because it continues to leave library validation disabled, which is a bad option security-wise.

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

3 Likes

Alright, I dug deeper and apparently SPM also sets a stdlib RPATH for Darwin executables only, which explains why I never heard of it, and there's no way to disable it. Looks like you're stuck removing it manually, or maybe you could submit a pull to SPM with a flag to disable that.

1 Like

Quinn "The Eskimo", thank you for the excellent explanation! This makes total sense to me. :rocket:

I was indeed able to solve my problem by removing the com.apple.security.cs.disable-library-validation
entitlement, and code-signing all executables files that MSBuild generates / copies, except for DLL files.

I assume that the DLL files ("assemblies") don't need to be (and probably cannot be) code-signed because they are no "real" executables to macOS, but files that are just in time-compiled by the .NET runtime.

And thank you also Matt and Buttaface for your help in this case!

I assume that the DLL files ("assemblies") don't need to be (and
probably cannot be) code-signed because they are no "real" executables
to macOS

Correct. The Apple code signing architecture supports code and data, where code means a Mach-O. If you try to sign data as code, the code signature gets stored in extended attributes, which often doesn’t end well. Thus, if you have ‘code’ that’s not Mach-O, such as a .NET DLL, it’s best to sign it as data. This extends to code-like things, for example, shell scripts.

Technote 2206 macOS Code Signing In Depth is the go-to resource for all these tidbits. And Signing a Mac Product For Distribution is a focused, step-by-step guide for signing outside of Xcode.

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

1 Like