Secure Boot in the Era of the T2
00. Introduction
Today, we are continuing our series on Apple’s new T2 platform and examining the role it plays in Apple’s vision of Secure Boot. The T2 was first introduced with the release of the iMac Pro and has now found its way into every new 2018 Macbook Pro. This article covers the security properties and technical implementation details of what makes this platform unique.
We believe the T2 platform is a leap forward in platform security in the Apple ecosystem, and it begins to bring exciting security properties like Secure Boot capabilities to the mass market. Secure Boot on new Apple platforms raises the bar significantly for attackers. That said, we have identified areas that we believe the community, and Apple, should continue to research and improve upon. Because of the T2's privilege and extended use cases beyond Secure Boot, we have concerns about the potential increased attack surface this exposes and believe more research is needed to determine whether or not these security capabilities can be undermined by this larger surface.
01. eSPI SAF
Enhanced Serial Peripheral Interface (eSPI) is a relatively new interface in the Intel world. It is meant to supersede the traditional Low-Pin-Count (LPC) interface. Known in the Intel world as a Embedded Controller (EC), the Apple T2 can control many aspects of the platform over a single unified bus.
Source: https://www.intel.com/content/dam/support/us/en/documents/software/chipset-software/327432-004_espi_base_specification_rev1.0_cb.pdf
This broad range of control extends to the system's firmware and changes the way that Unified Extensible Firmware Interface (UEFI) support is implemented. One of the traditional challenges in UEFI is how to perform firmware validation when you can’t trust the integrity of the firmware validation routine itself.
One of the newer eSPI extensions, Slave Attached Flash (SAF), replaces the traditional firmware-containing SPI flash chip with a wrapped interface that the Embedded Controller can implement to provide the firmware dynamically. SAF is typically only found on server SKUs, but both the iMac Pro and 2018 Macbook Pro lines contain this functionality. This allows the T2 to perform additional firmware validation in a trusted execution environment before supplying it to the chipset for execution.
Source: https://downloadmirror.intel.com/27055/eng/329957-001_eSPI_Spec_Server_Addendum_Rev0_7.pdf
As security researchers, seeing Apple leverage this functionality does raise several questions. First, does utilizing SAF from an EC actually improve platform security and make bootkits harder to deploy? Unequivocally, yes. Even if an attacker were able to bypass firmware write protection mechanisms offered by the X86 platform, without a durable storage medium such as a traditional SPI flash chip for the malware to reside in, bootkits should not be able to survive a full reboot.
Second, does this approach realize the vision of fully trusted boot? Not yet. The eSPI SAF protocol that wraps the firmware page requests has no means of authenticating the responses it receives. This means that, with implanted hardware, it is still possible to effectively man-in-the-middle the firmware image in flight and modify it.
Finally, does the T2 present additional risk to platform integrity? You betcha. The T2 is in a class along with other PCIe-connected, DMA-capable processors. While user-space code on the T2 is not intrinsically or architecturally capable of altering the behavior of System Management Mode (SMM), the Intel Management Engine (ME), or UEFI, by virtue of being the code responsible for bootstrapping them, the kernel on the T2 is intrinsically more powerful than SMM/ME/UEFI. While it is based on an immense but mature code base and years of adversarial testing, the T2 exposes new interfaces to the host OS that have traditionally been out of reach for attackers. The most prominent of these interfaces is the RemoteXPC facility exposed by a USB-attached network interface. While direct access to the interface is gated by entitlements, advertised services can be communicated with directly without root permissions or binary entitlements. The iMac Pro firmware hinted to the T2 eventually supporting the installation of apps, but it appears to be a simple artifact of the platform porting process. The consequences of persistent privileged code execution on the T2 are serious and, in addition, are likely forensically impossible to detect. This makes the T2 an extremely appealing target for a motivated attacker.
02. Boot Process
So what actually happens under the hood to get the system to boot? When power is first applied before the power button is pressed, the T2 wakes up and initializes. As the T2 can be reasoned as a highly privileged iPhone soldered to the motherboard, the boot process is quite familiar: An immutable masked ROM locates and initializes iBoot, the iOS bootloader, off of on-die flash storage, sets up the BridgeOS kernel and userland and then invokes the userland launch-daemon, launchd
. This process establishes the same cryptographically verified chain-of-trust found on the iPhone that ensures platform integrity.
MacEFIManager
As part of BridgeOS (the iOS-like platform running on the T2) kernel initialization, the MacEFIManager.kext
is initialized in the kernel. MacEFIManager
’s (Appendix 0) responsibilities fall into several categories:
- Set up DMA eSPI hardware engine to provide validated UEFI firmware and Management Engine (ME) images to the PCH upon request.
- Maintain volatile and non-volatile nvram variables for the system.
- Monitor system power transitions and gate actions based on power level.
- Instruct the SMC to allow the X86 CPU to come out of RESET.
03. Memory Map
As part of the MacEFIManger::start
IOService routine, the manager sets up three distinct DMA memory maps based off of entries in the device tree.
eSPIAPMemoryMap
The first of these located at index 0, eSPIAPMemoryMap
, is the DMA interface for controlling the hardware that forwards memory over SAF to the chipset. It is a relatively small and simple interface consisting of the following DWORD members:
Offset | Direction | Name | Description |
---|---|---|---|
0x104 | in/out | regIntFlashEnable | Enables eSPI engine when OR-ed with interrupt mask 0x830 |
0x108 | in | ESPI_INT_SRC | Specifies source interrupt in ISR |
0x10C | out | unknown0 | Unknown, probably marks interrupt handled when touched from ISR |
0x114 | out | unknown1 | Unknown, zero written immediately before EFI firmware availability |
0x118 | out | PMAofEFIImage | Physical memory address of the EFI firmware image to provide over eSPI SAF |
0x11C | out | EFIImageSize | Size in bytes of EFI image |
0x12C | in | ESPI_ERROR | Details of ESPI error, provided during ISR |
0x130 | in | ESPI_FAIL | Details of ESPI failure, provided during ISR |
The effective usage of this interface by MacEFIManager
can be boiled down to the following:
/* 00 */ #define IR_MASK = 0x830; //b100000110000
/* 01 / extern dword PMA_UEFIFirmware;
/ 02 */ extern dword UEFIFirmwareLength;
/* 03 / struct eSPIAPMemoryMap espi;
/* 04 / void isr() {
/ 05 */ espi->unknown0 = IR_MASK;
/* 06 / if(espi->ESPI_INT_SRC & IR_MASK)
/ 07 / if(espi->ESPI_FAIL | ESPI_ERROR)
/ 08 / panic(“ESPI Failure”);
/ 09 */ }
/* 10 / void service_main(IOService provider) {
/* 11 / espi = provider->mapDeviceMemoryWithIndex(0,0).getPhysicalAddress();
/ 12 */ provider->registerInterrupt(0,0,isr);
/* 13 */ espi->regIntFlashEnable = IR_MASK;
/* 14 */ // validate signature of UEFIFirmware here.
/* 15 / espi->unknown1 = 0;
/ 16 / espi->PMAofEFIImage = PMA_UEFIFirmware;
/ 17 / espi->EFIImageSize = UEFIFirmwareLength;
/ 18 */ }
The manager obtains the physical memory address (PMA) of the eSPI engine configuration region, registers an interrupt handler to capture errors, and registers the PMA of validated firmware. Of particular note is the strange ordering of engine enablement (lines 11-13) and the setting of the pointer of the firmware (lines 15-17). Depending on how the the reset vector works internally, there may be opportunity for a race condition to occur that may lead to reading and writing of privileged T2 memory.
eSPISMCMemoryMap
The second mapped memory region, eSPISMCMemoryMap
, is loaded into a class member and not referenced from anywhere.
macEFILandingMap
The final third memory-mapped region, macEFILandingMap
, provides the actual physical memory backing the region registered in the PMAofEFIImage
region of the eSPIAPMemoryMap
. T2 validated firmware is copied to, and subsequently fixed-up in this region.
04. Early Boot
Launchd
contains an embedded property list of commands to invoke in the early-boot phase. One of which is ‘mac-efi
’:
<key>mac-efi</key>
<dict>
<key>RequireSuccess</key>
<true/>
<key>ProgramArguments</key>
<array>
<string>MacEFIUtil</string>
<string>-i</string>
</array>
<key>Program</key>
<string>/usr/bin/MacEFIUtil</string>
</dict>
After performing internal filesystem checks, setting up data protection and checking for a pending data wipe, launchd invokes MacEFIUtil -i
. MacEFIUtil
, along with the nvram
executable are the primary means of interacting with the UEFI kernel components from userland.
Usage: MacEFIUtil [-hVc] [-i [MEFI path]] [-s <plist file name>]
[-v <nvram variable name>] [-w <nvram variable name>=<nvram variable value>]
-i, --init
Initialize the eSPI interface with an optional fully qualified path to the MEFI IMG4.
-s, --x86Vars <plist file name>
Parse the passed in plist and add the requested NVRAM variables.
-v, --readVar <nvram variable name>
Read the specified NVRAM variable.
-w, --writeVar <nvram variable name>=<nvram variable payload>
Write the specified payload to the NVRAM variable.
-c, --clear
Clear the entire NVRAM dictionary.
-V, --version
Display version information about this application. -h, --help
Display this list of options.
You may issue multiple commands per invocation (ie: MacEFIUtil -i -s <plist file>)
Of note, MacEFIUtil
has some additional undocumented command line flags to save and restore the Management Engine (ME) region of an already-loaded UEFI payload:
Option | Description |
---|---|
-g | Creates a hash file of original firmware in /var/db and captures ME partitions (see -m) |
-n | Capture snapshot of nvram to /var/db/ |
-r | Restore snapshot of nvram from /var/db/NVRAM_NEW.snapshot |
-m | Captures snapshot of ME partitions IVBP, MFS, FLOG, UTOK, SWBG, UEP in to /var/db/ |
-q | Restores ME snapshots NVRAM_NEW.snapshot, ME_PSVN.snapshot, ME_IVBP.snapshot, ME_MFS.snapshot, ME_FLOG.snapshot, ME_UTOK.snapshot from /var/db/ to the loaded firmware image. |
When MacEFIUtil -i
is invoked by launchd
, it is done so without the optional image path, invoking the imported MacEFIHostInterface::EFIHostInterface::init_espi_firmware(char const*)
with a null
argument. MacEFIHostInterface
(Appendix 1) is a shared library originally located at /usr/lib/libMacEFIHostInterface.dylib
, but in the runtime, it is baked into the dynamic loader cache ( /System/Library/Caches/com.apple.dyld
)
init_espi_firmware
queries the device tree (IODeviceTree:/arm-io/espi,saf-capable
) for slave-attached-flash support and, if found, loads the UEFI firmware package (MacEFI.img4
) and a sha2-384 hash file (MacEFI.hash
) out of the /usr/standalone/firmware/MacEFI
directory into memory buffers and invokes MacEFIManagerUserClientLib::initeSPI
. This initeSPI
call ends up in MacEFIManagerUserClient
’s (Appendix 2) initeSPI
, which marshals the data into a kernel context and calls into MacEFIManager::loadAndVerifyPayload
.
As part of the MacEFIManager::start
initialization routine, the Apple Firmware Update kernel extension (AFU.kext
) is initialized and a post-validation callback is registered for ‘mefi
’ sections. loadAndVerifyPayload
passes a MemoryDescriptor
containing the contents of MacEFI.img4
to AppleFirmwareUpdate::loadFirmware
. After performing signature validation using the same routines that validate firmware updates, the post-validation callback, MacEFIManager::verifyPayloadCallback
is executed.
Assuming validation succeeded, verifyPayloadCallback
copies over the contents of the ‘mefi
’ section to the macEFILandingMap
and configures the eSPI DMA engine.
Back in loadAndVerifyPayload
, MacEFIManager
initializes its NVRAM storage mechanisms and populates sections out of the firmware image before hot-patching an indicator in to the UEFI image based on a bootp arguments and production fuse status:
void MacEFIManager_patchLockIndicator(MacEFIManager *manager)
{
__uint64 lockIndicatorValue;
bool isProdFused = manager->Fuse_ApProductionStatus != 0;
bool isRomLocked = 0;
void* ESPIVirtualBaseAddress = manager->espiEFILandingVirtualBaseAddress;
__uint64 macEFIPayloadRomSize = manager->macEFIPayloadSize;
if (isProdFused)
isRomLocked = 1;
PE_parse_boot_argn("macefi.locked", &isRomLocked, 1);
if ( isRomLocked )
lockIndicatorValue = 0x4E4F223198E57BA1LL;
else
lockIndicatorValue = 0x4E15E2F599858AC6LL;
*(_QWORD *)(macEFIPayloadRomSize + ESPIVirtualBaseAddress - 128) = lockIndicatorValue;
}
This lock indicator value gets hot patched in near the very end of the image. Its utilization in the UEFI boot process is not currently understood.
05. Power State Transition Monitoring / Getting to S0
Many functions are gated by current power state. You wouldn’t want to rewrite the UEFI firmware mid-boot. As such, MacEFIManager
registers to receive several power state transition events from the System Management Controller (SMC):
this->AppleSMC->smcRegisterNotificationController(
OSSymbol(“system-state-notify”),
MacEFIManager::handleSMCNotification
);
The handleSMCNotification
routine is primarily looking for two specific event types coming in from the SMC. kSMCNotifyBootStatus (0xB)
is delivered when transitioning to and from lower power states like run, sleep, deep sleep and hibernate. kSMCNotifyPltRstLChange (0x2)
is delivered when the PLTRST#
virtual pin is asserted. PLTRST#
is an active-low platform reset similar to the reset button on your PC tower.
Getting to S0
At this point, the system is almost ready to begin booting. Back in the userland MacEFIUtil
after the init_espi_firmware
call, allowX86ToBoot
is invoked. This eventually leads to MacEFIManager::allowX86ToBoot
which locates the Apple System State Manager (AppleSSM
) kernel extension and sets the “AllowCSoCBoot
” property to True
through the AppleSSM::setProperties
interface.
The system state manager is responsible for monitoring internal wake and sleep events and instructing the SMC to place the system in the respective power state. Setting AllowCSoCBoot
leads to a call to AppleSSM::SMCNotifyBootRomReady
, and in turn a call to AppleSSM:sendSystemStateNotifcationToSMC
with an argument of BootRomReady (0x82)
. This calls a platform function on the AppleSMC
service for “system-state-notify” and we finally end up in AppleSMC::callPlatformFunction
which transforms that BootRomReady
notification argument in to an SMC write of 0x00008270
to key ‘NSEN
’.
At this point, the job of the T2 is done setting up for boot and SMCNotifyBootRomReady
triggers a thread to allow T2 BridgeOS to go idle and itself go to sleep while it waits for a power button pressed event.
When the physical power button is pressed by the user, either the AppleSSM
service’s sleepWakeHandler
routine gets invoked due to registering for notification through a call to registerSleepWakeInterest
or a SMC notification callback comes in. Both of these code paths eventually lead to another call to sendSystemStateNotifcationToSMC
with an argument of DoS0 (0x89)
. This leads to an SMC write of 0x00008970
to the same ‘NESN
’ key which brings the X86 platform out of reset and allows the system to boot the UEFI image prepared by the Manager and forwarded by the DMA engine over eSPI. From this point on, the regular UEFI and macOS bootloader execute as they always have.
06. Analysis
Until very recently, Apple has been tight-lipped about implementation details and given the lack of instrumentation, the details above are primarily based off of static analysis of BridgeOS update packages. Some of the dynamic control flow details may be incomplete or incorrect, but it is the best that can be done at this time. Taking that into account, now that we have seen how the T2 boots the X86 side of the platform, let’s discuss the implications. Due to the lack of integrity validation on the underlying physical eSPI transport, physical attacks are still possible, though more challenging than the classic evil-maid attacks that reflash a single SPI flash chip.
Likely due to the way ME configuration and NVRAM regions are utilized, the firmware image is not reloaded from a known good source every use and is hot patched on first initialization. This means that the loaded UEFI firmware image is persistent across X86 reboots and is mutable. Which in turn means that if an attacker were able to execute kernel-mode code on the T2 they would be able to modify the earliest of X86 code (SEC phase) to be executed before any of the platform integrity features are enabled, effectively taking control of the boot process.
This sounds like a pretty big ‘if’ but there lies the crux of the T2: it does too much. The T2 is a fully featured and bulky platform. It shares many common components and drivers from the iOS and macOS platforms that frankly, just should not be there. The approach of performing integrity validation of firmware-at-rest is a no-brainer, security-wise, and something we are excited about. Ideally, the secure boot operations would have been isolated to a much simpler and more tightly-scoped system-on-a-chip. While T2 updates are bundled alongside macOS system updates, there have been issues in the past where the bundled updates only partially applied, leaving the software stack secure, but the firmware vulnerable.
With all of this in mind, it is felt that there is a good chance a vulnerability in one of the XNU-derived devices like the iPhone or Apple TV could impact the entire ecosystem which now includes BridgeOS running on the T2. While not directly reachable on the T2, CVE-2018-4407 recently highlighted such potential impact as it affected several disparate products and architectures. That’s not to say there is no benefit to gleaned from a unified codebase. Apple’s masked-ROM rooted chain-of-trust, through the efforts of Apple’s engineers and security researchers worldwide, has matured in to one of the hardest targets in existence. Apple should be lauded for trying to bring their laptop and desktop lines into the same defensive posture as their mobile offerings.
The next article in this series will focus on the new attack surface and underlying transports exposed by the T2 and how to interface with them.
07. Appendix 0 - MacEFIManager Symbols
InitFunc
TermFunc
start
lookupDTProperty
allowX86ToBoot
handleSMCNotification
InitializeAFUKext
RegisterPowerDriver
TDMHandlerThreadEntry
resetX86BootState
setupInterrupts
stop
loadAndVerifyPayload
loadIsmData
initNvramManager
initNonVolatileVariables
initVolatileVariables
patchLockIndicator
verifyPayloadCallback
newUserClient
getIsmDataSection
unknownImg4TagValidationCallback
updateX86BootState
setPowerState
systemWillShutdown
getSectionSnapshot_internal
restoreSectionSnapshot
clearNvramDRAM
nvramGetVariable
nvramGetVariableBridgeOS
nvramSetVariable
nvramSetVariableBridgeOS
nvramGetNextVariable
nvramClear
nvramVolatileClear
returnNvramStatus
IOInterruptActionHandler
nvramSafeSetSecureVolatileVariableBridgeOS
deleteFirstBootVariables
08. Appendix 1 - MacEFIHostInterface Symbols
init(void)
cleanup(void)
EFIHostInterface::save_me_region(void)
detail::DeviceTreeInterface::is_saf_supported(void)
detail::FileInterface::save_section_to_file(uint)
EFIHostInterface::get_espi_snapshot(void)
EFIHostInterface::modify_first_boot_key(uchar,detail::KeyModifyType)
detail::SMCInterface::get_key_info(uint,uint *,uint *,SMCParamStruct *,SMCParamStruct *,ulong,ulong *)
detail::SMCInterface::read_smc_key(uint,uint *,SMCParamStruct *,SMCParamStruct *,uint,ulong,ulong *)
detail::SMCInterface::write_smc_key(uint,uint *,SMCParamStruct *,SMCParamStruct *,uint,ulong,ulong *)
EFIHostInterface::restore_me_region(void)
detail::FileInterface::restore_section_from_file(uint)
EFIHostInterface::init_espi_firmware(char const*)
detail::FileInterface::get_file_size(char const*)
detail::FileInterface::load_file_buffer(char const*,char *,uint)
NvramHostInterface::setCriticalVariables(void)
EFIHostInterface::allowX86ToBoot(void)
EFIHostInterface::x86StateInterface::getx86Sstate(void)
detail::SMCInterface::readKeySimple(uint,std::vector<uchar> &)
NvramHostInterface::setNVRAMVariablesFromPropertyListFile(std::string const&,bool,bool)
NvramHostInterface::getNVRAMVariableValue(std::string const&)
NvramHostInterface::VariableNameTypeInterface::getType(std::string const&)
NvramHostInterface::setNVRAMVariableValue(std::string &)
NvramHostInterface::process_nvram_command(uchar)
NvramHostInterface::setBridgeOSVersion(void)
NvramHostInterface::save_section_to_file(uint)
NvramHostInterface::restore_section_from_file(uint)
detail::FileInterface::getPathForSignature(uint)
detail::DeviceTreeInterface::get_device_tree_property(char const*,char const*,void *,int)
detail::SMCInterface::fourccToString(uint)
detail::SMCInterface::writeKeySimple(uint,std::vector<uchar> &)
detail::SMCInterface::read_nvram_smc_key(uint,uchar *)
detail::SMCInterface::write_nvram_smc_key(uint,uchar)
stream_error(std::string &,std::string,int)
09. Appendix 2 - MacEFIManagerUserClient Symbols
initeSPI
getSectionSnapshot
restoreSectionSnapshot
getSectionSize
getFirstBootDetected
getROMSize
getBaseROMHash
nvramGetVariable
nvramSetVariable
nvramGetNextVariable
nvramGetVariableBridgeOS
nvramSetVariableBridgeOS
nvramClear
nvramVolatileClear
returnNvramStatus
allowX86ToBoot
initWithTask
start
stop
terminate
willTerminate
didTerminate
clientClose
free
externalMethodDispatch
externalMethod
parseGuidStringToGUID
GUIDToGuidString