run command line tool with associated dylib

I've been given an Xcode project which produces a command line tool which links to a dylib. I have the dylib, but not its source code.

I change the signing option for the command line tool target so it is signed automatically by my personal team.

On an attempt to run the tool, it fails to load the dylib, because the dylib is signed with a different certificate. I manually codesign the dylib with the same certificate I am using for the command line app.

Now, I can build the app, but not run it. If I try to do so, I see four dialogs telling me “libXXX.dylib” can’t be opened because Apple cannot check it for malicious software, then the console tells me "'/path/to/libXXX.dylib' not valid for use in process: library load disallowed by system policy)"

I found an old document about Gatekeeper (https://developer.apple.com/library/archive/documentation/Security/Conceptual/CodeSigningGuide/Procedures/Procedures.html) which suggests that Gatekeeper just won't let me do this - I can't just put the dylib next to the executable, although the dynamic linker finds the dylib, Gatekeeper doesn't like it because the dylib isn't inside the app bundle (there is none), and isn't in one of the well-known places.

I dealt with this by making a do-nothing app which I can sign with my personal certificate. Then I replace the signature on the dylib (and its dependent dylibs) with my own. I add the command line tool and all its dylib dependencies to the do-nothing app, then add those files into the Copy Bundle Resources phase of the do-nothing app.

Now, the command line tool and its dylibs all live in do-nothing.app/Contents/Resources, and I can run the tool from there without Gatekeeper complaining.

Is there an easier way (aside from asking my supplier for static libraries)? And if this is the only way, is Contents/Resources the right place to put command line tools and the dylibs they link to?

The best path forward here is to change your library’s build system to produce a mergeable library. Then clients get to decide whether they want to:

  • Ship the library as an independent dynamic library

  • Or merge it into their main executable, à la a static library

WWDC 2023 Session 10268 Meet mergeable libraries has the details.


Failing that, the next best option is to enable the hardened runtime on your executable and make sure not to disable library validation. This disables the Gatekeeper check that you’re hitting, because that check is designed to stop injecting a malicious library into your program, and library validation prevents that regardless of the dynamic linker search path.

Finally, I recommend rpath-relative install names, as covered in Dynamic Library Identification.

Share and Enjoy

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

Thank you for pointing out mergeable libraries, I didn't know about those.

I already enabled the hardened runtime on the command line tool, and didn't check any of the boxes which relax it.

I'm stumbling over the double negative in "make sure not to disable library validation" though. You said "this disables the Gatekeeper check...", but does "this" mean library validation or disabling library validation?

I couldn't get the dylib to be used at all at runtime without packaging the tool and its dylib inside an app bundle - that's the part I find most inconvenient. The tool is used as a test harness for the library, which includes code that I would like to be able to debug.

I'm stumbling over the double negative

Yeah, that’s confusing:

I ran a quick end-to-end test of this today and confirmed that it works the way I expected. Here’s (roughly) what I did:

  1. Using Xcode 15.4, I created a new test project Test755989 from the macOS > Command Line Tool template.

  2. Within that, I created a new library target macOS > Library target, selecting Dynamic from the Type popup.

  3. I added code to my library and called that from the app.

  4. In that target, I set the Dynamic Library Install Name Base to @rpath. I’m following the rpath-relative install name process described in Dynamic Library Identification.

  5. In the tool target I set Runpath Search Paths build setting to @executable_path.

  6. I ran the tool locally to ensure that the above was all working.

  7. I re-signed the library and tool for direct distribution:

    % codesign -s "Developer ID" -f --timestamp libWaffle.dylib
    % codesign -s "Developer ID" -f -o runtime --timestamp Test755989
    

    This was based on the advice in Creating distribution-signed code for macOS. Note the o runtime, which enables the hardened runtime.

  8. I used ditto to package those up into a zip archive. The command was based on the one in Packaging Mac software for distribution.

  9. I notarised that.

  10. I testing that zip archive in a VM per Testing a Notarised Product. The tool ran just fine.

    Note that I didn’t disable networking because zip archives don’t support stapling.

I’m not sure why this is failing for you. If you don’t spot something based on the above, I recommend that you try to reproduce these steps with a small test project.

Share and Enjoy

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

Thank you, Quinn for your in-depth steps. I tried steps 1 through 6 locally and they worked, so I had another look at the libraries I'd been given.

The problem was, they had their quarantine flag set (because they emanated from a .zip file from my vendor).

If I use Finder or cp to copy these files, the quarantine flag is preserved, but it seems to be removed if Xcode copies the files in a Copy Bundle Resources phase, which is why the tool linked and ran when it was inside a folder in an app bundle - nothing to do with the app bundle, rather a side effect of using Xcode to copy the files.

Does the forum allow one to accept two answers to a problem?

I’m glad you got this sorted.

Does the forum allow one to accept two answers to a problem?

No. But I’m not doing this for credit [1], so as long as you mark one post as the answer that’s fine by me (-:

Share and Enjoy

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

[1] Well, not that sort of credit (-: £££

run command line tool with associated dylib
 
 
Q