Killed: 9 for the binary signed with endpoint-security entitlement

Hello,

The application I'm working on has started requiring endpoint-security permissions. Before the changes it has followed signing (without additional entitlements) and ".pkg."-packaging processes for several years without issues.

The Security Endpoint entitlement was requested and approved. After that "Security Extension" was enabled for the App ID we use. The build process (without Xcode) was updated to use the entitlement file during signing. After the update the signing and packaging steps were successful. The package can be installed without issues as well. Running the application results in an immediate "Killed: 9".


During troubleshooting it turned out that even a dummy helloworld C binary behaves after signing the same way.

The C code (just for reference):

$ cat test.c
#include <stdio.h>

int main(void) {
    printf("Hello world\n");
    return 0;
}

The entitlement file:

$ cat entitlements.plist
<?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.developer.endpoint-security.client</key>
  <true/>
</dict>
</plist>

For signing we use keys imported to a temporary keychain from a developer profile (just for reference):

security delete-keychain temp-keychain
security create-keychain -p ******** temp-keychain
security unlock-keychain -p ******** temp-keychain
security list-keychains -d user -s login.keychain temp-keychain
security import /path/to/developer/identities/<Developer ID Application>.p12 -k temp-keychain -P ******** -T /usr/bin/codesign -T /usr/bin/productsign temp-keychain
security import /path/to/developer/identities/<Developer ID Installer>.p12 -k temp-keychain -P ******** -T /usr/bin/codesign -T /usr/bin/productsign temp-keychain
security show-keychain-info temp-keychain
security set-key-partition-list -S apple-tool:,apple: -s -k ******** temp-keychain
security default-keychain

Result:

Without entitlements

$ codesign -vvvvv -s "Developer ID Application: ..." --verbose --deep --force --timestamp --options=runtime  test
test: signed Mach-O thin (x86_64) [test]

$ codesign -dv test
Executable=/private/tmp/1/test
Identifier=test
Format=Mach-O thin (x86_64)
CodeDirectory v=20500 size=304 flags=0x10000(runtime) hashes=4+2 location=embedded
Signature size=9099
Timestamp=18 Aug BE 2564 23:37:54
Info.plist=not bound
TeamIdentifier=XXXXXXXXXX
Runtime Version=10.15.4
Sealed Resources=none
Internal requirements count=1 size=164

$ codesign -d --entitlements :- test
Executable=/private/tmp/1/test

$ ./test
Hello world

With entitlements

$ codesign -vvvvv -s "Developer ID Application: ..." --verbose --deep --force --timestamp --options=runtime --entitlements entitlements.plist test
test: signed Mach-O thin (x86_64) [test]

$ codesign -dv test
Executable=/private/tmp/1/test
Identifier=test
Format=Mach-O thin (x86_64)
CodeDirectory v=20500 size=400 flags=0x10000(runtime) hashes=4+5 location=embedded
Signature size=9099
Timestamp=18 Aug BE 2564 23:40:00
Info.plist=not bound
TeamIdentifier=XXXXXXXXXX
Runtime Version=10.15.4
Sealed Resources=none
Internal requirements count=1 size=164

$ codesign -d --entitlements :- test
Executable=/private/tmp/1/test
<?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.developer.endpoint-security.client</key>
  <true/>
</dict>
</plist>

$ ./test
Killed: 9

I'm under impression that missing something trivial but out of ideas already. Any piece of advice what should be verified is welcome.

During troubleshooting it turned out that even a dummy helloworld C binary behaves after signing the same way.

Right. The Endpoint security entitlement must be allowlisted by a provisioning profile. How you proceed here depends on the final disposition of your ES client:

  • If your ES client is a system extension, you can bundle the profile within your sysex. Xcode can handle all the details here.

  • If your ES client is not a system extension — for example, if you’re trying to integrate ES client functionality into an existing daemon — follow the instructions in Packaging a Daemon with a Provisioning Profile.

Share and Enjoy

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

Hi Eskimo,

Thank you for the reply. It is definitely a breakthrough.

macOS does not support this directly, but you can get things working by packaging your daemon in an app-like structure.

Could you please confirm that I understand correctly that if our application:

  • follows non-Xcode-based build process
  • is packaged with "pkgbuild"
  • is installed from ".pkg" to a subdirectory in "/opt"

, the only solution for us is to reorganize the packaging and usage process?

Thanks!

Accepted Answer

OK, so, just to clarify, my previous post was not using the term package to refer to an installer package, but rather to the directory structure in which your daemon is embedded. Once you embed your daemon in an app-like structure, the system will be able to find your provisioning profile at the Contents/embedded.provisionprofile and will use that to authorise your daemon’s use of the ES entitlement.

This is true regardless of what:

  • Tools you use to create the product

  • Installer technology you use to write it to disk

Using an Apple installer package is cool, but any other installer mechanism will also work.

Share and Enjoy

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

Thanks Quinn. I've been trying to put signed binaries under the directory structure generated as the result of your how-to at https://developer.apple.com/forums/thread/129596 but getting the results that I cannot explain:

$ cd /path/to/daemon.app/Contents/MacOS

# Running the binary built by Xcode is successful but cannot run a copy of it (which might be expected though)

$ ./daemon
2021-08-21 11:15:38.888 daemon[25514:2808215] entitlements: {
    "com.apple.application-identifier" = "<TEAM_ID>.<BUNDLE_ID>";
    "com.apple.developer.endpoint-security.client" = 1;
    "com.apple.developer.team-identifier" = <TEAM_ID>;
    "com.apple.security.get-task-allow" = 1;
    "keychain-access-groups" =     (
        "<TEAM_ID>.<BUNDLE_ID>"
    );
}

$ cp daemon daemon2
$ ./daemon2
Killed: 9

# I'm able to run my helloworld binary that was built separately but cannot do it predictably

$ cp /path/to/original/helloworld good

$ codesign -dv good
Executable=/path/to/daemon.app/Contents/MacOS/good
Identifier=<BUNDLE_ID>.test
Format=Mach-O thin (x86_64)
CodeDirectory v=20500 size=409 flags=0x10000(runtime) hashes=4+5 location=embedded
Signature size=9099
Timestamp=20 Aug BE 2564 18:21:12
Info.plist=not bound
TeamIdentifier=<TEAM_ID>
Runtime Version=10.15.4
Sealed Resources=none
Internal requirements count=1 size=176

$ codesign -d --entitlements :- good
Executable=/private/tmp/nxlog.app/Contents/MacOS/good
<?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.developer.endpoint-security.client</key>
  <true/>
    <key>com.apple.application-identifier</key>
    <string><TEAM_ID>.<BUNDLE_ID></string>
    <key>com.apple.developer.team-identifier</key>
    <string><TEAM_ID></string>
    <key>com.apple.security.get-task-allow</key>
    <true/>
    <key>keychain-access-groups</key>
    <array>
        <string><TEAM_ID>.<BUNDLE_ID></string>
    </array>
</dict>
</plist>

$ ./good
Hello World

$ cp good good2
$ ./good2
Killed: 9

$ rm good good2
$ cp /path/to/original/helloworld test
$ ./test
Killed: 9

A bundle structure is signed as a unit. Its signature covers the bundle’s root directory (DaemonInAppsClothing.app in my example) and everything below it. However, a bundle can have at most one main code item, identified by the CFBundleExecutable property in the Info.plist. If you put a second code item in the bundle, that item is considered to be nested code. This must be signed separately, and then the bundle references that signature as nested code.

Consider the hierarchy from my example:

DaemonInAppsClothing.app/
  Contents/
    Info.plist
    MacOS/
      DaemonInAppsClothing
    PkgInfo
    _CodeSignature/
      CodeResources
    embedded.provisionprofile

The DaemonInAppsClothing.app directory is a bundle and the Contents/Info.plist identifies it as such. That contains a CFBundleExecutable property whose value is DaemonInAppsClothing, and that identifies Contents/MacOS/DaemonInAppsClothing as the bundle’s main executable. It’s this executable that’s covered by the bundle’s signature, and by the embedded provisioning profile.

If you duplicate Contents/MacOS/DaemonInAppsClothing, you have two code items:

  • The DaemonInAppsClothing.app bundle.

  • The DaemonInAppsClothing copy nested tool.

The latter’s code signature is all messed up:

  • It says that it’s part of a bundle but it isn’t, because the bundle structure references DaemonInAppsClothing via its Info.plist.

  • It uses entitlements that aren’t authorised to use, because the provisioning profile is for the bundle, not for this nested code.

Share and Enjoy

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

Ok, thanks for the info Quinn. I did try to experiment with bundling some pre-compiled binaries and got issues similar to https://developer.apple.com/forums/thread/53348.

What we are trying to achieve is to provide the security-endpoint entitlement to an existing pre-compiled C daemon which currently uses the following self-contained directory structure:

/opt/thedaemon/
├── bin
├── etc
├── lib - third-party libraries
├── modules - some internal libraries
├── share
└── var

The reply in the thread mentioned above suggested to open a DTS tech support incident for a similar question. Should we follow that way too?

Thanks.

which currently uses the following self-contained directory structure

You will have to make changes here. Probably the easiest option is something like this:

/opt/thedaemon/
  bin/
    … existing stuff …
    thedaemon -> ../thedaemon.app/Contents/MacOS/thedaemon
  etc/
    … existing stuff …
  lib/
    … existing stuff …
  modules/
    … existing stuff …
  share/
    … existing stuff …
  var/
    … existing stuff …
  thedaemon.app/
    Contents/
      MacOS/
        thedaemon
      Info.plist
      embedded.provisionprofile

That is, replace your existing daemon with a symlink that points into your app-like structure.

Should we follow that way too?

I’m happy to continue discussing this here but a DTS tech support incident would let me allocate the time to look at your specific issue in depth.

Share and Enjoy

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

Thanks Quinn for the help. I've filled a support request.

Killed: 9 for the binary signed with endpoint-security entitlement
 
 
Q