Rosetta 2 on a Mac with Apple silicon
A Mac with Apple silicon is capable of running code compiled for the x86_64 instruction set using a translation mechanism called Rosetta 2. There are two types of translation offered: just in time and ahead of time.
Just-in-time translation
In the just-in-time (JIT) translation pipeline, an x86_64 Mach object is identified early in the image execution path. When these images are encountered, the kernel transfers control to a special Rosetta translation stub rather than to the dynamic link editor, dyld(1)
. The translation stub then translates x86_64 pages during the image’s execution. This translation takes place entirely within the process. The kernel still verifies the code hashes of each x86_64 page against the code signature attached to the binary as the page is faulted in. In the event of a hash mismatch, the kernel enforces the remediation policy appropriate for that process.
Ahead-of-time translation
In the ahead-of-time (AOT) translation path, x86_64 binaries are read from storage at times the system deems optimal for responsiveness of that code. The translated artifacts are written to storage as a special type of Mach object file. That file is similar to an executable image, but it’s marked to indicate it’s the translated product of another image.
In this model, the AOT artifact derives all of its identity information from the original x86_64 executable image. To enforce this binding, a privileged userspace entity signs the translation artifact using a device-specific key that’s managed by the Secure Enclave. This key is released only to the privileged userspace entity, which is identified as such using a restricted entitlement. The code directory created for the translation artifact includes the code directory hash of the original x86_64 executable image. The signature on the translation artifact itself is known as the supplemental signature.
The AOT pipeline begins similarly to the JIT pipeline, with the kernel transferring control to the Rosetta runtime rather than to the dynamic link editor, dyld(1)
. But the Rosetta runtime then sends an interprocess communication (IPC) query to the Rosetta system service, which asks whether there’s an AOT translation available for the current executable image. If found, the Rosetta service provides a handle to that translation, and it’s mapped into the process and executed. During execution, the kernel enforces the code directory hashes of the translation artifact which are authenticated by the signature rooted in the device-specific signing key. The original x86_64 image’s code directory hashes aren’t involved in this process.
Translated artifacts are stored in a Data Vault which isn’t runtime-accessible by any entity except for the Rosetta service. The Rosetta service manages access to its cache by distributing read-only file descriptors to individual translation artifacts; this limits access to the AOT artifact cache. This service’s interprocess communication and dependent footprint are kept intentionally very narrow to limit its attack surface.
If the code directory hash of the original x86_64 image doesn’t match with the one encoded into the AOT translation artifact’s signature, this result is considered the equivalent of an invalid code signature, and appropriate enforcement action is taken.
If a remote process queries the kernel for the entitlements or other code identity properties of an AOT-translated executable, the identity properties of the original x86_64 image are returned to it.
Static trust cache content
macOS 11 or later ships with Mach “fat” binaries that contain slices of x86_64 and arm64 computer code. On a Mac with Apple silicon, the user may decide to execute the x86_64 slice of a system binary through the Rosetta pipeline—for example to load a plug-in that has no native arm64 variant. To support this approach, the static trust cache that ships with macOS, generally, contains three code directory hashes per Mach object file:
A code directory hash of the arm64 slice
A code directory hash of the x86_64 slice
A code directory hash of the AOT translation of the x86_64 slice
The Rosetta AOT translation procedure is deterministic in that it reproduces identical output for any given input, irrespective of when the translation was performed or on what device it was performed.
During the macOS build, every Mach object file is run through the Rosetta AOT translation pipeline associated with the version of macOS being built, and the resulting code directory hash is recorded into the trust cache. For efficiency, the actual translated products don’t ship with the operating system and are reconstituted on demand when the user requests them.
When an x86_64 image is being executed on a Mac with Apple silicon, if that image’s code directory hash is in the static trust cache, the resulting AOT artifact’s code directory hash is also expected to be in the static trust cache. Such products aren’t signed by the device-specific key, because the signing authority is rooted in the Apple secure boot chain.
Unsigned x86_64 code
A Mac with Apple silicon doesn’t permit native arm64 code to execute unless a valid signature is attached. This signature can be as simple as an ad hoc code signature (cf. codesign(1)
) that doesn’t bear any actual identity from the secret half of an asymmetric key pair (it’s simply an unauthenticated measurement of the binary).
For binary compatibility, translated x86_64 code is permitted to execute through Rosetta with no signature information at all. No specific identity is conveyed to this code through the device-specific Secure Enclave signing procedure, and it executes with precisely the same limitations that native unsigned code executing on an Intel-based Mac.