Launching apps in Sonoma 14.6.1: Full security

This is the first of a series of three articles that look in detail at the launch process of apps in macOS Sonoma 14.6.1, with the emphasis on security checks. This follows my earlier look in 14.4.1, and covers a wider range of situations, including the effects of disabling SIP and Gatekeeper, and how known malicious software is handled.

Methods

All tests were performed in a series of Sonoma 14.6.1 virtual machines (VMs) running on a Mac Studio M1 Max host, also running 14.6.1. VMs are preferred as they enable a consistent environment and easy control of boot security and security settings, together with relatively low rates of log entries. Log extracts were obtained using Ulbow and analysed in their entirety for the first 5-7 seconds after launching apps in the Finder.

Apps used were:

SystHist – notarized, quarantined, moved from its landing folder to avoid app translocation;
SilentKnight – notarized, not quarantined, previously run;
Sparsity – notarized, not quarantined, not previously run;
DelightEd3 – not notarized, signed with a Developer certificate, not quarantined, not previously run;
DelightEd3resigned – not notarized, ad hoc signed, not quarantined, not previously run.

None of the apps run in an app sandbox, and those notarized use a hardened runtime.

Four variants of the same 14.6.1 VM were run:

Full Security, with SIP and Gatekeeper/XProtect enabled;
Full Security, with Gatekeeper/XProtect disabled;
Permissive Security, with SIP disabled;
Permissive Security, with both SIP and Gatekeeper/XProtect disabled.

All had bridged network access to the network and internet, and shared folders with the host, when running these non-malicious apps.

This article describes what happens in the log in the first of those conditions, full security with both SIP and Gatekeeper/XProtect enabled.

Quarantined notarized app

This underwent the fullest checks of these tests. Once LaunchServices announces that it’s opening the app, the following sequence of events is recorded.

CDHashes from the app are copied, here only those for the Arm architecture. As the app is unknown, it’s next registered with LaunchServices. Gatekeeper assessment is then started just 0.07 seconds after announcement of the launch, in the log entry
GK process assessment: <private> <– (<private>, <private>)
com.apple.syspolicy.exec then starts work on scanning for code, followed by the first mention by LaunchServices that the app is quarantined.

The Gatekeeper scan is announced in
GK performScan: PST: (vuid: 7C5C43BF-A338-4228-B61E-5038F1D93EDB), (objid: 62947), (team: QWY4LRW926), (id: (null)), (bundle_id: (null))
followed by the XProtect scan in
Xprotect is performing a direct malware and dylib scan: <private>
and assignment of the risk category according to its quarantine
QUARANTINE: Setting risk category to LSRiskCategoryUnsafeExecutable
XProtect states the Yara rules it’s using
Using XProtect rules location: /Library/Apple/System/Library/CoreServices/XProtect.bundle/Contents/Resources/XProtect.yara

com.apple.syspolicy next processes the app’s notarization ticket
looking up ticket: <private>, 2, 1
by trying to fetch its record using CloudKit. That’s followed by entries indicating the network access required to connect with iCloud and check the ticket. Success is reported by com.apple.syspolicy in
CKTicketStore network reachability: 1, Mon Aug 26 09:15:45 2024
looking up ticket: <private>, 2, 0
and further lookups.

A little later, Gatekeeper announces the XProtect results
GK Xprotect results: PST: (vuid: 7C5C43BF-A338-4228-B61E-5038F1D93EDB), (objid: 62947), (team: QWY4LRW926), (id: (null)), (bundle_id: (null)), XPScan: 0,-7676743164328624005,2024-08-26 08:19:01 +0000,(null)
and its scan is complete
GK scan complete: PST: (vuid: 7C5C43BF-A338-4228-B61E-5038F1D93EDB), (objid: 62947), (team: QWY4LRW926), (id: (null)), (bundle_id: (null)), 4, 4, 0

Because this is the first launch of a quarantined app, com.apple.syspolicy.exec decides it gets a first launch or “code-evaluation” prompt “because responsibility”. If the user gives approval, the app is allowed to proceed. Its quarantine flag is updated, and the bundle record registered as trusted. The final step is then to create and save its provenance data
Created provenance data for target: TA(e8217440d9326f59, 2), PST: (vuid: 7C5C43BF-A338-4228-B61E-5038F1D93EDB), (objid: 62947), (team: QWY4LRW926), (id: co.eclecticlight.SystHist), (bundle_id: co.eclecticlight.SystHist)
Handling provenance root: TA(e8217440d9326f59, 2)
Wrote provenance data on target: TA(e8217440d9326f59, 2), PST: (vuid: 7C5C43BF-A338-4228-B61E-5038F1D93EDB), (objid: 62947), (team: QWY4LRW926), (id: co.eclecticlight.SystHist), (bundle_id: co.eclecticlight.SystHist)
Putting executable into provenance with metadata: TA(e8217440d9326f59, 2)
Putting process into provenance tracking with metadata: 692, TA(e8217440d9326f59, 2)
Tracking process with attributes: 692, TA(e8217440d9326f59, 2)

Without quarantine

A notarized app that hasn’t been run previously on that system and isn’t quarantined undergoes a similar sequence, but without the first launch or “code-evaluation” prompt. Its bundle record is registered as trusted, rather than being classified as an Unsafe Executable, but it still gets a full XProtect scan and ticket lookup using CloudKit.

Subsequent launches

The briefest launch process is that for an app that has only recently been run. That appears to skip Gatekeeper and XProtect assessments, and there’s no ticket lookup either. Pre-launch processes can then take less than 0.1 second.

Launching a known app following a cold boot can be as quick, although in this case there is a brief Gatekeeper assessment reported in the log. The key entry here comes from com.apple.syspolicy.exec:
Code already evaluated, using results.
Those are checked by Gatekeeper before launch proceeds, with the kernel reporting
evaluation result: 2, exec, allowed, cache, 1724654056, 4, c0a2e35c20a69dfd, /Applications/SilentKnight.app

Signed with developer certificate

An unquarantined app that isn’t notarized but is correctly signed using a Developer certificate is similar to its notarized equivalent, except that looking up the ticket using CloudKit is of course unsuccessful. Repeated attempts are made to find it, though, before going on to check “the legacy list” and check “legacy policy”. This results in the decision
Match downgraded from DevID to None based on legacy policy for: PST: (vuid: 7C5C43BF-A338-4228-B61E-5038F1D93EDB), (objid: 60118), (team: QWY4LRW926), (id: (null)), (bundle_id: (null))
but the kernel decides to allow launch to proceed
evaluation result: 6, exec, allowed, cache, 1724660700, 0, 9576bac3e248c07b, /Applications/DelightEd3.app

Ad hoc signature

This is detected early during pre-launch checks by AMFI (Apple Mobile File Integrity), despite the bundle record being registered as trusted. The kernel reports
AMFI: ‘/Applications/DelightEd3resigned.app/Contents/MacOS/DelightEd’ is adhoc signed.
AMFI then records
No certificate chain found
Failure getting cert chain
Basic requirement validation failed, error: Error Domain=NSOSStatusErrorDomain Code=-67050 UserInfo={SecCSArchitecture=<private>}
and an error code of -423, given as “The file is adhoc signed or signed by an unknown certificate chain”.

Despite that, Gatekeeper assessment continues, with an XProtect scan. Attempts to look up the app’s ticket inevitably fail despite many attempts, and an error code of -67018 “Code did not match any currently allowed policy” is awarded. Launch then proceeds.

In the next article I’ll show how those are affected by disabling SIP and Gatekeeper assessments.