Booting an M1 Mac from hardware to kexts: 2 LLB and iBoot

In the first of this series of articles explaining Secure Booting an M1 Mac, I focussed on the first stage, Boot ROM, and closed as it has verified the executable for the second stage, the Low-Level Bootloader, LLB. Confusingly, you will also see this referred to as Stage 1, or iBoot1, because of its close relationship to the third stage of the boot sequence, iBoot (alias iBoot2). As with the first article, this relies on the information provided in Apple’s Platform Security Guide, documentation generously provided by Hector Martin @marcan42 and the team developing Asahi Linux, and my own small explorations.

LLB

LLB is stored in Flash memory, and is replaced when you perform a restore in DFU mode. Unfortunately its version number isn’t readily accessible to the user, and it’s not known whether or how frequently it might be updated within macOS updates or upgrades.

In this context, its primary role is to discover the intended boot volume and prepare for iBoot to verify what’s needed to boot from that.

The currently intended boot volume is set by the user in one of two places: either in the Startup Disk pane, or in Recovery. These write that intention to NVRAM, so that when that Mac next starts up, LLB can realise that intention after reading that setting from the NVRAM. Armed with that information, LLB then obtains the hash for that boot volume’s LocalPolicy from the Secure Enclave, where it’s stored in the xARTS volume.

LocalPolicy is a collection of settings for Secure Boot, together with hashes for key items used during the boot process. These are stored, like so much other boot data, in an Image4 file, encoded in binary form in a format commonly used for security certificates. Detailed listings of the contents of LocalPolicy files are given by Apple, and by the Asahi Linux project. They include:

  • SHA-384 hashes for other key Image4 files and components used during booting;
  • Settings for each of the elements in that boot policy, such as whether the SSV is enabled, the level of security set, and SIP settings (stored in NVRAM in Intel models).

Most of the latter settings can be viewed in the Controller item in System Information, and are also listed by SilentKnight. If you want to access them at the command line, or in a script,
system_profiler SPiBridgeDataType
returns them as text.

Armed with that information, LLB can then locate the target Preboot volume in the boot macOS container, either on the internal SSD or an external disk. It then decrypts and verifies the Image4 file containing iBoot from that volume. If that verifies correctly, it loads it and hands over to that copy of iBoot to continue the boot sequence.

It’s time for another look at the diagram, this time showing just Boot ROM, LLB and iBoot, from the full chart.

SecureBootM1v2fw

iBoot

The priority now is to verify the components which the kernel is going to need to boot the Mac, which is the job of iBoot, also known as iBoot2, in case you prefer calling LLB iBoot1. iBoot isn’t really firmware at all, but the third stage loaded from the Preboot volume in the boot macOS container, which could be on the internal SSD or an external bootable disk.

Because iBoot is better-known to the user, and can and does change in updates more readily, it’s the only one of the three boot stages whose version number is made public, and is readily viewed in System Information, and utilities such as SilentKnight. If you’d rather check your Mac’s version number manually, this page lists that current. All M1 models, original, M1 Pro and Max run the same version of iBoot, which makes life much simpler.

Several of iBoot’s most important verifications are made against what Apple terms signatures, which are SHA-384 hashes. Perhaps the best illustration of these is how iBoot verifies the root hash for the SSV.

Directly verifying a hash would normally involve comparing the hash value, in this case a SHA-2 hash of 384 bits size, with a freshly computed hash for the data used to compute that hash. That demonstrates that the data currently matches that used to calculate the original hash. That’s not the concern of iBoot, though, but something for the kernel to do later when it mounts the SSV.

Instead, what iBoot does is verify that its copy of the root hash is intact by hashing that copy of the root hash, and comparing that with a hash of the root hash (its ‘signature’) made previously. If the two match, the kernel can then rely on that saved root hash or its signature. This is an ingenious mechanism for ensuring that the root hash never changes from that checked against Apple’s master value when macOS was last updated/installed, as I’ll explain in the next article.

Similar verifications are performed for the kernel extensions to be loaded later. Standard kernel extensions are gathered into the Boot Kernel Collection, and any authorised by the owner in that LocalPolicy form the Auxiliary Kernel Collection.

Once it has completed those verifications, iBoot verifies and loads the macOS kernel (‘XNU’) and hands over to it to complete the boot process, which will be the subject of the next and final article in this series.

Errors and failures in verification in LLB and iBoot are thankfully exceptionally rare. When they do happen, there’s little that can be done, other than to put the Mac into DFU mode ready for a restore, or put it into Recovery, depending on the nature and severity of the problem.

Here once again is the tear-out PDF chart: SecureBootM1v2

I am very grateful to Hector Martin @marcan42 for his patience in getting me to understand that iBoot performs signature-based verification.