How Catalina handles app first run

Apart from the read-only system volume, one of the biggest structural changes in macOS Catalina is the requirement for notarization and, next year, hardening in newly-notarized apps, other than Apple’s and those supplied by the App Store. I’ve been looking forward to seeing how this is reflected in the logs since the first beta of Catalina. This article shows how apps undergo their first run when they’ve got a quarantine flag set, and how this has changed with notarization.

For this purpose, I built two versions of my app Taccy. One is the current release, which is fully hardened and notarized, the other is identical in its code and contents but not hardened at all and only signed using my developer signature, not notarized. I then set a quarantine flag on each, and opened it by double-clicking in a Finder window. The log excerpts were obtained using Consolation, of course, and times shown are in decimal seconds, with the double-click occurring just after 1 second had elapsed from the start.

1. Signed but not hardened or notarized

As I wasn’t using the Finder’s Open command, trying to run the un-notarized version of the app failed with the dialog reporting that Apple cannot check the app for malicious software.

cantcheck

The app was launched by double-clicking in a Finder window, as recorded in the following entry:
1.400 Finder sendAction:

The first major step in security protection is to translocate the app, and run it from that hidden folder. This disrupts any expected external paths which could be exploited by malware:
1.420 lsd SecTranslocateCreateSecureDirectoryForURL: created /private/var/folders/dc/p5l3qqtn5wbb_snq36008jzc0000gn/T/AppTranslocation/CD6A9ADA-C017-4D1C-AD6E-F2ACECAA530A/d/TaccySoft.app
1.429 com.apple.launchservices LaunchApplication: appToLaunch={ … "CFBundleExecutablePath"="/private/var/folders/dc/p5l3qqtn5wbb_snq36008jzc0000gn/T/AppTranslocation/CD6A9ADA-C017-4D1C-AD6E-F2ACECAA530A/d/TaccySoft.app/Contents/MacOS/Taccy", … }

One of the earliest and most important trust evaluations is that called by amfid, the service component in Apple Mobile File Integrity:
1.439 amfid SecTrustEvaluateIfNecessary

Following shortly afterwards is the start of the Gatekeeper (GK) assessment:
1.449 syspolicyd GK process assessment: …
1.449 syspolicyd Gatekeeper assessment rooted at: <private>

The privacy sub-system TCC is also hard at work:
1.451 com.apple.TCC AttributionChain: ACC:{ID: co.eclecticlight.Taccy … }, REQ:{ID: com.apple.syspolicyd, PID[160], auid: 0, euid: 0, binary path: '/usr/libexec/syspolicyd'}

An apparently new entrant to the log is RunningBoard, which appears responsible for process management:
1.452 com.apple.runningboard [executable<Taccy(501)>:767] This process will not be managed.

Gatekeeper gets on with its task, which includes syspolicyd checking the notarization ticket:
1.473 syspolicyd GK performScan: <private>, 1, 0
1.474 syspolicyd looking up ticket: <private>, 2, 1

Here, the XProtect scan is also started:
1.474 com.apple.xprotect Xprotect is performing a direct malware and dylib scan: <private>

It’s then discovered that the app has no notarization ticket, and this is checked repeatedly:
1.608 syspolicyd ticket not available: <private>
1.609 syspolicyd completing lookup: <private>, 3
1.609 com.apple.securityd MacOS error: 3
1.609 com.apple.securityd Error checking with notarization daemon: 3
1.640 syspolicyd looking up ticket: <private>, 2, 0
1.640 syspolicyd completing lookup: <private>, 3
1.640 syspolicyd com.apple.securityd MacOS error: 3
1.641 syspolicyd com.apple.securityd Error checking with notarization daemon: 3

and many other similar entries.

Checks are now being completed, and decisions made on the dialog to be shown:
1.834 gk com.apple.securityd rule 6 applies - allow=1
1.963 syspolicyd GK scan complete: <private>, 0, 0
1.963 syspolicyd scan finished, waking up any waiters: <private>
1.963 syspolicyd App gets first launch prompt because responsibility: <private>, <private>
1.963 syspolicyd GK evaluateScanResult: 0, <private>, 1, 0, 1, 0, 0, 0

This is the crucial answer, though:
1.963 syspolicyd GK eval - was allowed: 0, show prompt: 1
where the 0 indicates that opening the app won’t be allowed, and a dialog will be shown to the user.
1.964 com.apple.launchservices present prompt: uid=501, conn=yes, type=6, op.ident=99B53CEF-41F2-4FA5-8478-9B9711898069, info.ident=6D971C4B-22C1-4F23-A120-23F5CD59EC4D, info={<private>}
1.964 syspolicyd Prompt shown, waiting for response: <private>

Once the user clicks to accept the decision:
7.693 syspolicyd Code evaluation completed: 2
7.694 syspolicyd Terminating process due to Gatekeeper rejection: 767, <private>
7.694 kernel Security policy would not allow process: 767, /Users/hoakley/Documents/TaccySoft.app/Contents/MacOS/Taccy

2. Notarized and hardened

Essentially the same steps occur, but early on, it’s discovered that the security database already contains an entry for this app:
1.298 syspolicyd Newer ticket (1567886523) present in db, ignoring ticket (1567886523)
1.298 syspolicyd looking up ticket: <private>, 2, 1
1.361 syspolicyd GK Xprotect results: <private>, <private>

When the notarization ticket is required, it’s found immediately:
1.585 syspolicyd looking up ticket: <private>, 2, 0
1.585 syspolicyd completing lookup: <private>, 0

and extensive checks are performed against that ticket.

The outcome is thus to open the app according to the user’s choice in the dialog:
1.646939 syspolicyd scan finished, waking up any waiters: <private>
1.646998 syspolicyd App gets first launch prompt because responsibility: <private>, <private>
1.647003 syspolicyd GK evaluateScanResult: 0, <private>, 1, 0, 1, 0, 4, 0

So Gatekeeper has decided to allow the app to run, at the user’s discretion:
1.647004 syspolicyd GK eval - was allowed: 1, show prompt: 1
1.647227 com.apple.launchservices present prompt: uid=501, conn=yes, type=5, op.ident=3DCC6CA6-D954-4CE9-89DA-72FCC0F1B6BD, info.ident=709173BF-45C3-494B-B675-E690666FD7D8, info={<private>}
1.647229 syspolicyd Prompt shown, waiting for response: <private>

When the user agrees to open the app:
6.067920 syspolicyd Code evaluation completed: 2
6.067929 syspolicyd Allowing code due to user approval
6.067935 syspolicyd Updating flags: <private>, 518
6.068098 syspolicyd Updating scan time: <private>, 1570621406

Summary of the steps involved in app first run:

  • App translocation
  • Trust evaluation by AMFI to check signatures
  • Gatekeeper assessment
  • TCC checks
  • RunningBoard involvement
  • Locate notarization ticket; if no evidence of notarization, error 3
  • XProtect scan for malware
  • Decision to display dialog
  • Decision by Gatekeeper whether app meets requirements, including notarization.

Although some of us have been speculating that XProtect has taken on a larger role in Catalina, there’s no evidence here that it has, at least not when performing these first run checks, in which it is limited to scanning for known signatures of malware and malicious dylibs. The rest of the checking, in particular that of notarization tickets, falls to Gatekeeper. I’d be very interested to know what RunningBoard does, though.