Ok gentlemen, after some reasonably extensive research we were able to mostly understand several problems preventing the users of AMI APTIO have working NVRAM in macOS. As a result we have a reasonably reliable yet dirty solution. As a side issue we suppose we may have discovered a terrible memory corruption issue due to a of AMI APTIO and boot.efi, which we suppose we also fixed.
Some of the information was mentioned in this topic and/or on applelife, we consider it important to give a summary that covers all the details. Especially because during the research the information obtained was very confusing, which led to certain misunderstandings .
1. ASUS APTIO IV Z97 Motherboards
Described here: http://www.insanelym...-6#entry2535040
After the disassembling it was discovered that several APTIO IV drivers including the presented one implement a variable whitelist, and disallow writing anything but the variables from the list. It is unclear whether it was intentional or just an logical mistake, but a most reasonable solution will be to just replace the NvramSmi driver with the working one from a previous firmware and reflash.
Lang, Timeout, PlatformLang, ConIn, ConOut, ErrOut, BootOrder, BootNext, DriverOrder, HwErrRecSupport, OsIndications, PK, KEK, FTMActiveFlag
2. APTIO V firmwares with working NVRAM
Some APTIO V firmwares (pre-KabyLake at least) do not use a new NVRAM driver implementation, but rely on a driver that can be found on older Z87 or Z77 APTIO IV motherboards. For this reason NVRAM works fine on them. Notably the extensive changelog of the NVRAM driver covers a lot of issues that arose during the development.
Yet it should be noted that once the firmware was upgraded it is no longer possible to use the older driver, like on Z97 motherboards, due to a completely new stack.
3. APTIO V firmwares with not working NVRAM
a) Before explaining the details of the new bugs we have to go back to describing NVRAM issues on APTIO IV.
As everyone knows during the loading process:
- boot.efi discards all the memory that is not EFI_MEMORY_RUNTIME
- boot.efi physically moves EfiRuntimeServicesCode and EfiRuntimeServicesData regions regions to go one by one and zeroes the original area
- boot.efi assigns virtual addresses to EFI_MEMORY_RUNTIME regions
- XNU maps EfiRuntimeServicesCode as RX memory and everything else supplied as RW memory
However, AMI SMM drivers preserve the original physical address of EfiRuntimeServicesData, and use this memory for communication. Since SMM drivers cannot be easily changed, the AptioFix driver prohibits EfiRuntimeServicesData from being moved by marking it as a EfiMemoryMappedIO region.
To our surprise the AptioFix driver does not revert "temporarily" changed types back to EfiRuntimeServicesData before starting XNU, which undesirably leads to VM_MEM_NOT_CACHEABLE | VM_MEM_GUARDED flags being used in the XNU mapping, yet it not known to cause practical issues.
What did the original solution not care about?
There exists a certain FlashDriver that exposes AMI_FLASH_PROTOCOL (755B6596-6896-4ba3-B3DD-1C629FD1EA88).
On APTIO IV this protocol is not used after ExitBootServices (if not earlier) by any EFI_RUNTIME_SERVICES, which was proven by RW mapping it without the X bit, yet is part of the EfiRuntimeServicesCode.
It was discovered that on APTIO IV AMI SMM drivers have a physical address of one of the static variables of this driver, and they use this variable for write access during all the three calls to NVRAM-related EFI_RUNTIME_SERVICES (GetVariable/SetVariable/GetNextVariableName).
Therefore it effectively leads to arbitrary memory corruptions (which happen to be used for RW access by XNU and thus do not trigger a kernel panic with a page fault) when invoking any NVRAM procedures.
c) What did APTIO V do?
On APTIO V they appear to have additionally changed the implementation to work via a shared SMM/DXE buffer. However, unlike APTIO IV, which used EfiRuntimeServicesData, this shared buffer become a static variable in some driver too. Once again since SMM uses physical addresses to the buffer, after boot.efi moves the problematic driver, it will no longer is able to communicate with SMM.
For quite some time (probably due to the lack of the hardware) we thought that the problematic driver was the same FlashDriver just like on APTIO IV. However, this is not the case, and on APTIO V it is some other unknown driver. For this reason we prevented the whole EfiRuntimeServicesCode area from being moved just like with EfiRuntimeServicesData. After we mapped all the EfiRuntimeServicesCode memory as RWX in XNU NVRAM started to work fine. As a result we discovered the memory corruption mentioned in ( due to WP page faults. Summary follows.
FlashDriver RW EfiRuntimeServicesData & force same address → nvram works FlashDriver R X EfiRuntimeServicesCode & force same address → write page fault FlashDriver RWX EfiRuntimeServicesCode & force same address → nvram works FlashDriver RWX EfiRuntimeServicesData & force same address → nvram works RTCode RWX EfiRuntimeServicesCode & force same address → nvram worksAPTIO V:
FlashDriver RW EfiRuntimeServicesData & force same address → nvram does not work FlashDriver R X EfiRuntimeServicesCode & force same address → write page fault FlashDriver RWX EfiRuntimeServicesCode & force same address → nvram does not work FlashDriver RWX EfiRuntimeServicesData & force same address → nvram does not work RTCode RWX EfiRuntimeServicesCode & force same address → nvram worksd) What did we do?
Firstly, we prohibited all the EfiRuntimeServicesCode from being moved by boot.efi. It was a tough decision whether to try researching the exact driver not move just it or the entire code area. The choice was made for the latter, because -1- the NVRAM bugs clearly show that AMI APTIO does not support this and -2- if there exist any more write accesses from SMM to DXE we will discover them by triggering a kernel panic (with a WP page fault) instead of just silently corrupting arbitrary memory area.
Secondly, we created dedicated shims for GetVariable/SetVariable/GetNextVariableName which unset the WP bit during the execution of the UEFI code. This is safe to do in an SMP environment, because AppleEFIRuntime kext performs a lock call during any UEFI code calls, and thus effectively disables CPU preemption. On the contrary patching the kernel to map all the UEFI memory as RWX is an unnecessary risk, which is furthermore prone to errors due to instruction changes. It should be noted, that it is not possible to perform memory region protection upgrade from a kext either (vm_protect fails due to maximum protection being set to a too low value).
The relevant changes for OsxAptioFix2 driver and a prebuilt OsxAptioFix2 driver version, which contains the fix:
Signed off: Download-Fritz, vit9696, and everyone who shared their wisdom and helped to test
06.01.17 4:30 MSK Go to the full post