Codesigning a MacOS app breaks the app.

Context : I'm developing a python app with Tkinter GUI on a 2020 M1 Macbook Air.

I have already built, signed, and notarized the app successfully on the native arm64 architecture - so far so good.

Now I am trying to do the same for x86_64, on the same machine. I've built a conda environment for x86_64, built the app with pyinstaller, and verified that it runs when I double-click on AppName.app. So far so good.

The problem happens when I sign it. After signing with the same command I used for the arm64 version:

codesign -s "Developer ID Application: MY_CERTIFICATE_NAME" -v --deep --timestamp --entitlements entitlements.plist -o runtime "dist/MyAppName.app" --force

Entitlements file just sets com.apple.security.cs.allow-unsigned-executable-memory to true - apparently necessary for python programs.

The app now crashes when I double-click. The crash-log contains the line:

Termination Reason:    Namespace ROSETTA, Code 0 
rosetta error: unable to mmap __TEXT: 1 
/var/db/*/libffi.8.dylib.aot

When I try to run from command line with

./projects/eagle_eyes_video_scanner/dist/EagleEyesScan.app/Contents/MacOS/main

I get another error:

rosetta error: unable to mmap __TEXT: 1 
 /var/db/oah/ffdfb26a8f1f835406614fae08b99665733faafa40599b6bc0aace0981564015/4893345e2743c970aa1c71f137e03f8e791c82b6b7354da038bebfac5673be73/libffi.8.dylib.aotzsh: abort   ./projects/eagle_eyes_video_scanner/dist/EagleEyesScan.app/Contents/MacOS/mai

I don't know if this libffi is central to the problem or just the first thing to fail.

So, how can I sign my app without breaking it?

Post not yet marked as solved Up vote post of petered Down vote post of petered
2.3k views

Replies

So, how can I sign my app without breaking it?

Well, I recommend that you start by not using --deep. See --deep Considered Harmful as to why that’s a bad idea. For detailed advice on how to manually sign Mac code, see:

Having said that, the fact that this works for Apple silicon and fails for Intel under Rosetta is interesting. If you run your Intel build on real Intel hardware, does it work there?

Share and Enjoy

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

Thanks for your reply Quinn.

In this case, it appears that the problem was entitlements. I had to add the com.apple.security.cs.disable-library-validation entitlement to entitlements.plist in order to have the program not crash when I sign with hardened-runtime (-o runtime).

Specifically, before signing, I could run

./dist/EagleEyesScan.app/Contents/MacOS/main

And my app would launch. But after signing the main executable with:

codesign -s "Developer ID Application: MY_CERTIFICATE" -v --timestamp --entitlements entitlements.plist dist/MY_APP.app/Contents/MacOS/main --force -o runtime  

..., my app would crash when trying to run it again with ./dist/EagleEyesScan.app/Contents/MacOS/main

After adding com.apple.security.cs.disable-library-validation was added to the entitlements and re-signining with the above command, it works.


Re: --deep: every guide I have found online for manually signing bundles from Pyinstaller recommends using it, while acknowledging that you're "not supposed to" - it seems like a case where doing it the right way is complicated enough that people go for the quick and dirty. The code signing process in general has been much more difficult than I expected.

I had to add the com.apple.security.cs.disable-library-validation entitlement to entitlements.plist

That’s almost certainly not the right solution. There are very limited circumstances where disabling library validation is the right answer. The one I’m aware of is where you have an app that needs to load in-process plug-ins from other third parties. If your app is not doing that, disabling library validation is not required. Moreover, by enabling library validation you’re actively making it harder to pass Gatekeeper, as explained in Resolving Gatekeeper Problems Caused by Dangling Load Command Paths.

It’s likely that library validation is preventing the crash because your app is being partially re-signed. That is, you have bits of the app that are signed by you and bits that are signed by your third-party vendor. With library validation enabled, the system prevents your app from loading those third-party libraries and that causes this crash. However, disabling library validation is just masking the problem. The correct fix is to sign each component of your app as your code.

Which brings me to this:

every guide I have found online for manually signing bundles from Pyinstaller recommends using [--deep]

And they are wrong.

people go for the quick and dirty

Right. And then they encounter packaging and distribution problems and complain that code signing is ‘mysterious’ |-:

I’m not claiming that code signing is simple. It’s a complicated process and if you get small details wrong you will encounter hard to debug issues. It’s exactly the sort of thing that computers are good at automating. Apple’s recommended development environment, Xcode, provides exactly that automation. If you’re using tools that require you to do this all by hand, that’s something you should discuss with your tools vendor.

Share and Enjoy

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

Oh, and one more thing. If you are a tools vendor and you’d like help improving your code signing and distribution features for Apple platforms, please get in touch. You can either start a thread here on DevForums — tag it with Code Signing so that I see it — or open a DTS tech support incident for private, one-on-one help.

Share and Enjoy

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

Hey @eskimo and @petered,

This thread's been closed for half a year now, but I am having the exact same problem and I can't seem to find the solution. I have a Python app that I ported to macOS and compiled (or froze) using PyInstaller. I am using a lot of custom submodules that don't get imported correctly using the PyInstaller algorithm, but I found a workaround. It's not a perfect solution, but it works for the arm64 version. So here's my general process.

I make the .app file using PyInstaller. <-- App working for arm64 and x86_64.

I create an .dmg file from this, codesign it and sent it to notarization. <-- App working for arm64 and x86_64.

The binaries that are not correctly signed I resign using a script and then I sign the complete .app again. <-- After resigning the x86_64 version stops working (with the above error message). The arm64 still works great.

After resigning I create a new .dmg file, codesign it and sent it to notarization, which is accepeted (for arm64 and x86_64). <-- The arm64 version is correctly executable, but the x86_64 version does not start resp. throws the libffi error, if executed via zsh.

I did this using my M1pro and an older Macbook Air with Intel CPU and macOS 11.7.6. Even if using the machine the app won't start after resigning. I am running out of ideas. Where is the difference here between arm64 and x86_64.

Any ideas left?

Two things:

  • Check that the ticket your get back from the notary service contains cdhashes that match both the architectures of your libffi library.

  • Look in the system log for info from the trusted execution system as to what’s gone wrong.

I have a bunch of info about this in a hierarchy of posts rooted at Resolving Trusted Execution Problems.

Share and Enjoy

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

  • Sorry, for the late reply. I am currently trying a different solution. This issue seems to be related to conda. At least that's what the PyInstaller developer assumes. I'll get back to you if I have run more tests :).

Add a Comment

I have a little report here:

The libffi error originates from sklearn, I assume and I could indeed get rid of the libffi error by switching from conda to the distribution von Python.org. However, also here I run into a similar error, when trying to run the executable (slightly changed since the original error and not an .app file, but just an executable):

rosetta error: unable to mmap __TEXT: 1 
 /var/db/oah/.../.../libomp.dylib.aotzsh: abort      PYTORCH_ENABLE_MPS_FALLBACK=1 ./diarize_x86_64 mps 

AFAIK libomp is from OpenMP, which I thought macOS does not support anymore. It's weird that it's used (I guess) by PyTorch (and also seems to work for arm64). I can't find anything about trusted execution in the system.log and the crash report is very long. Any hints what to look for?

The log from the notarytool came back without any warnings.

{
  "logFormatVersion": 1,
  "jobId": "<omitted>",
  "status": "Accepted",
  "statusSummary": "Ready for distribution",
  "statusCode": 0,
  "archiveFilename": "diarize_x86_64.dmg",
  "uploadDate": "2023-09-30T18:53:04.957Z",
  "sha256": "<omitted>",
  "ticketContents": [
    {
      "path": "diarize_x86_64.dmg",
      "digestAlgorithm": "SHA-256",
      "cdhash": "<omitted>"
    },
    {
      "path": "diarize_x86_64.dmg/diarize_x86_64",
      "digestAlgorithm": "SHA-256",
      "cdhash": "<omitted>",
      "arch": "x86_64"
    }
  ],
  "issues": null
}
  • By the way, what is this weird rosetta error? Is it really rosetta (1) or rosetta2?

Add a Comment