A dylib fails to load under Hardened Runtime

Hello!

Hardened Runtime appears to prevent a bundled dylib from being loaded and I would greatly appreciate some help in figuring out what to do about it. I'm not entirely sure I'm not doing this the wrong way.

Setup


My setup is not an Xcode project of any description but an executable built by the hover tool. It runs fine unsigned, with expected security alerts. I pack this build into an .app which I pack into a .dmg. I sign everything and then notarise using the command-line tools, so all of the security alarms go away and all checks pass successfully, both codesign --verify and xcrun altool --notarization-info.

The app stops loading the dylib when I sign the code with -o runtime --timestamp so that Hardened Runtime gets enabled. If I remove these build flags, I get the app blocked by security mechanisms, but it runs fine again when unblocked from the system preferences.

The dylib


The dylib in question is libcrypto.1.1.dylib from the brew version of OpenSSL.

I copy it to MyApp.app/Contents/Frameworks and change the path to the library in my main executable to @loader_path/../Frameworks/libcrypto.1.1.dylib with the install_name_tool (checked with otool -l).

Effectively, the executable points to ../Frameworks/libcrypto.1.1.dylib.

The notarisation success log says the following among other things, and gives no warnings at all:
Code Block
{
"path": "MyApp.dmg/MyApp.app/Contents/Frameworks/libcrypto.1.1.dylib",
"digestAlgorithm": "SHA-256",
"cdhash": "852834600a6f9d6b6029d479227693e8d766df61",
"arch": "x86_64"
}

Theories


My current theories after several hours of research are these:
  1. Hardened Runtime doesn't like the relative path of the dylib. (How do I use an absolute one?)

  2. Hardened Runtime doesn't like the absence of LC_VERSION_MIN_MACOSX in the dylib: otool -l shows it's not there at all. (Can I add it to an existing dylib?)

  3. I need to actually build the library for some reason. But why would that be? It's not signed in the brew version, I sign it with my certificate.

  4. I'm doing something wrong entirely.

Please advise!
Answered by Aurora42 in 625596022
I figured this out!

It transpired the Allow Unsigned Executable Memory Entitlement is required to load the dylib the way I describe here. Why, oh, why the error message couldn't be clearer?

I found this out by trial and error after stumbling upon a thread on Github.

To sum it up, all what was required is adding the following to hardened_runtime_entitlements.plist used with codesign tool.

Code Block
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>


Code Block
codesign \
--force \
--deep \
--verbose \
--options=runtime \
--timestamp \
--entitlements ./osx/hardened_runtime_entitlements.plist \
--sign "XXXXXXXX" MyApp.app


The rest of what I describe in this thread stayed the same and appears to work fine.

Thanks for participating!
  • The Team in the signature of the dylib definitely needs to match, you should get detailed error messages if you run your app in the debugger with the hardened runtime enabled; it will list why a particular candidate was inappropriate. Make sure you're comparing the verbose output of codesign -d in the app and dylib

  • LC_VERSION_MIN_MACOSX was replaced by LC_BUILD_VERSION, they should be equivalent for these purposes. I don't think it's possible to build a Mach-O binary without one of them.

  • Relative paths should be fine, though @rpath is generally a better idea (any of the dylibs in Xcode.app/Contents/Frameworks are a good example), e.g. libclang.dylib is "@rpath/libclang.dylib" with an rpath of "@loader_path/../lib"

  • Something like OpenSSL really deserves to be a framework, if you can manage it

The most informative error I see in the Console app is in the system.log (no crash report):
Code Block
Aug 4 16:38:30 MacBook-Pro-8 com.apple.xpc.launchd[1] (com.apple.xpc.launchd.oneshot.0x1000004f.myapp[51169]): removing service since it exited with consistent failure - OS_REASON_EXEC | Gatekeeper policy blocked execution
Aug 4 16:38:41 MacBook-Pro-8 com.apple.xpc.launchd[1] (com.apple.xpc.launchd.oneshot.0x10000050.myapp[51205]): Service exited with abnormal code: 2

If I try to run the same build again it just says "Service exited with abnormal code: 2".

I tried to add Hardened Runtime entitlements just to see if it makes any difference. It didn't.
Code Block
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
</dict>
</plist>

Used this file in the call to codesign:
Code Block
codesign \
--force \
--deep \
--display \
--verbose \
--options=runtime \
--timestamp \
--entitlements ./hardened_runtime_entitlements.plist \
--sign "MYAPPSIGNENTITY" MyApp.app

This had no effect whatsoever. :(

codesign --dvvv shows that both the dylib and the rest of the files are signed with one TeamIdentifier.

codesign --verify --verbose is happy with dylib.
  • Relying entirely on codesign --verify is not a good guide (as I've previously experienced). It doesn't check whole classes of issues related to signatures, dynamic libraries, entitlements, and code signature requirements

  • Since you're building manually (makefiles or other, not Xcode), you're likely missing a particular flag or step. Replacing libcrypto with an empty dylib target built by Xcode might be one way of narrowing down the issue

  • In my experience, library validation issues are best troubleshooted in the debugger, since dyld is pretty explicit when it can't find something

  • I'm pretty sure --display (-d) and --sign (-s) are mutually-exclusive modes of codesign

  • What was the exact issue you saw? You said your app stops loading the dylib, but how did you find out?

Thanks for you replies TyngJJ! I'm at a loss here...

I'm fairly certain the problem is in dylib loader under hardened runtime. This is how I found out:
  1. If I don't use install_name_tool on my executable (thus relying on OpenSSL being installed in the system), I can see a proper crash saying "Library not loaded: /usr/local/opt/openssl@1.1/lib/libcrypto.1.1.dylib". If I make openssl available, everything is fine. This is a totally expected behaviour, also works with notarisation under hardened runtime rules.

  2. If I use install_name_tool on my executable to change the path to the dylib as described in the original post, I can run the app fine without OpenSSL available in the system. This is how I know the dylib gets successfully loaded from my app bundle. However, not under hardened runtime. Once I add --options=runtime to the signing procedure, the app crashes immediately on start.

What are you using OpenSSL for? Since this is usually used for security, it is better to use a static library. Otherwise, malicious folks can swap out your dylib with a stub and do nefarious things.

Then, add your OpenSSL libraries to your Xcode project under "Frameworks, Librarys, and Embedded Content" and Xcode will figure it out.

Also, you should be building this yourself. Don't ship objects from brew. You could be shipping other dependencies on brew.

If you wanted to use a dylib, you could do that too. Just add the dylib instead of a static library. You will need to manually add the search paths for the headers. In your build phases make sure to install it into Frameworks. Xcode should properly sign it and setup any @rpath that you will need. I'm not 100% certain on that however. Normally I use static builds of OpenSSL and frameworks. You can easily build a manual Framework from your OpenSSL binary if you want.
Accepted Answer
I figured this out!

It transpired the Allow Unsigned Executable Memory Entitlement is required to load the dylib the way I describe here. Why, oh, why the error message couldn't be clearer?

I found this out by trial and error after stumbling upon a thread on Github.

To sum it up, all what was required is adding the following to hardened_runtime_entitlements.plist used with codesign tool.

Code Block
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>


Code Block
codesign \
--force \
--deep \
--verbose \
--options=runtime \
--timestamp \
--entitlements ./osx/hardened_runtime_entitlements.plist \
--sign "XXXXXXXX" MyApp.app


The rest of what I describe in this thread stayed the same and appears to work fine.

Thanks for participating!
Are you sure that's correct? I can't think of anything in libcrypto (which is supposed to be more fundamental than libssl) that would require JIT-like exceptions to the hardened runtime.
@TyngJJ I've found the correct entitlement by trial and error 🤷

While I'm not aware of all the implications as all of this is pretty new to me, I'm sure this is what caused the crash. The JIT entitlement is a different one btw.
Your link to the entitlement used is pretty clear about it allowing JIT-like behavior, but without the relative safety of using the JIT APIs. Considering the library you're loading is supposed to provide security primitives, it's worth examining why it's triggering an exception that:

In rare cases, an app might need to override or patch C code, use the long deprecated NSCreateObjectFileImageFromMemory (which is fundamentally insecure), or use the DVDPlayback framework. Add the Allow Unsigned Executable Memory Entitlement to enable these use cases. Otherwise, the app might crash or behave in unexpected ways.

and

Including this entitlement exposes your app to common vulnerabilities in memory-unsafe code languages. Carefully consider whether your app needs this exception.

Thanks for the insight TyngJJ! That's definitely something to look into.

@TyngJJ I've found the correct entitlement by trial and error 🤷

While I'm not aware of all the implications as all of this is pretty new to me, I'm sure this is what caused the crash. The JIT entitlement is a different one btw.

That seems like a risky approach when security software is concerned.

Maybe take a step back for a minute. Why are you using OpenSSL in the first place? Why not use the system-provided frameworks? The only time Apple recommends using OpenSSL is when you need to statically link to prevent software piracy. If you are just using OpenSSL as any old library, then why not use the system APIs? If this is just for convenience and you have some code that works with OpenSSL, you still shouldn't need any of this to link. Build your OpenSSL and either link it statically or include it in your app bundle. Then it will be signed properly and you won't need any entitlements for it.
@Etresoft first of all, I'm not using SSL of any description. I'm using crypto primitives from the package that happens to be OpenSSL. It's not even me really, it's SQLCipher that provides transparent database encryption over SQLite. But this is just a bit more funny because I use Golang bindings over SQLite and an SQLCipher Golang port. Which I attach to a go-flutter-desktop project as a plugin, that compiles by the means of CGO as a part of a Flutter project compiled with Hover for desktop.

It's still a good question why the latest brew version of OpenSSL's libcrypto requires that entitlement. Sounds odd and not right as both you and TyngJJ rightfully pointed out.

Other than that, quite a lot of steps back and in every other direction were taken here. 🤷

Probably building my own separate library of primitives is a good idea and thanks for bringing this to my attention.

it's SQLCipher that provides transparent database encryption over SQLite. But this is just a bit more funny because I use Golang bindings over SQLite and an SQLCipher Golang port. Which I attach to a go-flutter-desktop project as a plugin, that compiles by the means of CGO as a part of a Flutter project compiled with Hover for desktop. 

OK then.

It's still a good question why the latest brew version of OpenSSL's libcrypto requires that entitlement. Probably building my own separate library of primitives is a good idea and thanks for bringing this to my attention.

It's never a good idea to use someone else's build. There are ways to hack it and shove it in with brute force. But OpenSSL is really easy to build.
A dylib fails to load under Hardened Runtime
 
 
Q