It’s common knowledge that app signatures are only checked on first run. Once an app has passed through Gatekeeper’s stringent initial checks, when that app is launched again, the signature isn’t checked. This claim is repeated extensively wherever macOS Gatekeeper and signature checks are discussed, and it appears to be wrong, at least as of Mojave 10.14.2.
In this article I will demonstrate that app signatures are checked whenever an app is opened, and that when those checks return serious errors, those errors may be ignored, and app launch allowed to proceed normally. This is based on log examination of many app launches made in 10.14.2.
Key to this is Jeff Johnson’s information that it is possible to modify the Info.plist, executable code, and other components of a notarized app, but for it then to open and run normally in spite of the fact that those modifications have broken validation of the app’s signature. I have instinctively performed such modifications away from ‘real’ apps in /Applications, and that was my error.
Jeff revealed that the security database recognises apps by their path. If you copy an app to another folder, change it, and then try opening it there, this forces a signature check, and if that returns in an error, the app is crashed or terminated.
This can be seen as a means of allowing apps to modify their own bundles once they have passed checks on first open – something which shouldn’t really happen now, as it means that malicious apps can also modify the bundle, which brings us back to the question of checking bundle signatures again.
If you want to craft yourself an app which runs fine but generates signature errors, place it in /Applications without modification (other than changing the name of the app, if you wish). Run it from there so that it registers with the security database, quit it, then perform the modifications in place. Chances are that it will then open normally in spite of the fact that its app bundle now has signature errors, as reported by What’s Your Sign or Signet.
Claims that such malicious tampering must be followed by the generation of new signatures are therefore wrong: macOS will happily run a subverted app in this way. This applies to signed apps and those which have been notarized.
Look at the log, though, and the picture is very different. Here are the relevant entries from opening an app whose signature validates without error:
06.644329 lsd Security SecTrustEvaluateIfNecessary
06.645934 com.apple.securityd rvc trustd trustd asynchronously fetching CRL (http://crl.apple.com/root.crl) for client (lsd/0#-1 LF=0)
06.645961 com.apple.securityd policy trustd trustd cert: AnchorTrusted =(leaf)[force]> 0
No error is returned, and app launch proceeds normally as expected.
But is that certificate check actually a proper signature check? Look at what appears in the log when our altered app is opened:
00.486402 launchservicesd Security SecTrustEvaluateIfNecessary
00.488468 com.apple.securityd rvc trustd trustd asynchronously fetching CRL (http://crl.apple.com/root.crl) for client (launchservicesd/0#-1 LF=0)
00.488498 com.apple.securityd policy trustd trustd cert: AnchorTrusted =(leaf)[force]> 0
00.490739 com.apple.securityd security_exception launchservicesd Security MacOS error: -67030
Error -67030 means “invalid Info.plist (plist or signature have been modified)”, so is exactly what we’d expect after changing the app’s Info.plist.
The same error is reported slightly later in response to a check by TCC:
00.495847 tccd Security SecTrustEvaluateIfNecessary
00.497237 com.apple.securityd rvc trustd trustd asynchronously fetching CRL (http://crl.apple.com/root.crl) for client (tccd/0#-1 LF=0)
00.497343 com.apple.securityd policy trustd trustd cert: AnchorTrusted =(leaf)[force]> 0
00.498542 com.apple.securityd security_exception tccd Security MacOS error: -67030
and this repeats again for sharedfilelistd, and for tccd again. Despite this error being returned in four separate checks, the app is allowed to open normally, and no warning is given to the user. Only if the app has been launched from a directory from which it has not previously been run, or if its quarantine flag is set, does macOS appear to react by stopping the launch or terminating the app.
This is also true for an app with a signature using revoked certificates, provided that the app isn’t flagged as being in quarantine. For Microsoft Messenger, for example:
00.380029 syspolicyd Security SecTrustEvaluateIfNecessary
00.380286 com.apple.securityd pinningQA trustd trustd could not enable test hierarchy: no UAT pinning preferences set
00.380439 com.apple.securityd policy trustd trustd cert: WeakSignature =(leaf)> 0
00.380447 com.apple.securityd policy trustd trustd cert: WeakSignature =(leaf)> 0
00.380756 com.apple.securityd policy trustd trustd cert: WeakSignature =(leaf)> 0
00.380760 com.apple.securityd policy trustd trustd cert: MissingIntermediate =(leaf)[force]> 0
00.380767 com.apple.securityd policy trustd trustd cert: WeakSignature =(leaf)> 0
00.380777 com.apple.securityd policy trustd trustd cert: WeakSignature =(path)> 0
00.380795 com.apple.securityd policy trustd trustd cert: BlackListedKey =(path)[force]> 0
00.380935 com.apple.securityd SecError syspolicyd Security Trust evaluate failure: [root BlackListedKey MissingIntermediate WeakSignature]
00.381003 syspolicyd Security SecStaticCode: verification failed (trust result 6, error -2147409652)
00.381007 com.apple.securityd security_exception syspolicyd Security MacOS error: -2147409652
00.381107 com.apple.securityd security_exception syspolicyd Security MacOS error: -2147409652
00.381150 syspolicyd syspolicyd URL failed validity check (-2147409652): <private>
That is repeated for lsd, launchservicesd, tccd three times, then that whole sequence is repeated. Despite these many errors, the app continues to launch without any warning to the user.
When an app is terminated due to a signature check error, these are the distinctive log entries:
00.383124 com.apple.securityd MacOS error: -67030
00.383692 com.apple.MobileFileIntegrity <private>: Broken signature with Team ID fatal.
00.383781 mac_vnode_check_signature: /Applications/SignetTest.app/Contents/MacOS/Signet: code signature validation failed fatally: When validating /Applications/SignetTest.app/Contents/MacOS/Signet:
The code contains a Team ID, but validating its signature failed.
Please check your system log.
00.383800 proc 17245: load code signature error 4 for file "Signet"
and the app is terminated abruptly.
My conclusions from these observations are therefore:
- Whenever an app is opened in macOS 10.14.2, its signature is checked asynchronously, sometimes several times for different client processes.
- If the signature checks return no error, the app launch proceeds uninterrupted.
- If the signature checks return an error, the response is determined by security policy for that app. If it has a quarantine flag, or is running from that path for the first time, the app will normally fail to open, if necessary by crashing out.
- If an app has run successfully from the same path, and doesn’t have a quarantine flag, signature errors don’t normally result in any app termination or the user being informed. The app normally continues to open as if no error had occurred.
- Security policy is also influenced by the type of error. For example, running for the first time from a different path appears to be tolerated when the signature error is of a revoked certificate, but not if changes are detected in the Info.plist or executable code.
- Provided that it doesn’t result in setting the quarantine flag or altering the app’s path, an app can modify its own bundle, and it could be modified by another process, without re-signing the app; the user should still be able to open that app after modification, and not be alerted to such change. This also appears to apply to apps which have been notarized, at present.
- Apps which wish to be protected from modification need to use other mechanisms (permissions, SIP) and shouldn’t expect their signatures to protect them.
- For an app which checks it own signature to detect modification, it needs to check not only whether its signature is valid, but also whether that certificate is unchanged.
- Users who discover that an app crashes when it is opened for the first time from a different folder should suspect that it has a signature error as a potential cause for that behaviour.
I am, once again, greatly indebted to Jeff Johnson @lapcatsoftware who made sense of things for me.