After the release of the first developer preview of 10.13 DP1, I prepared a USB installer using createinstallmedia
and the latest revision of Clover on InsanelyMac. Booting the installer on my GA-Z77X-UD5H, I encountered the following panic:
Hmm, an AppleACPIPlatform panic. Maybe the updated AppleACPIPlatform kernel extension didn’t like my stripped DSDT?
After experimenting with the original system DSDT and SSDTs (both with and without various patches), I couldn’t find a way to get past the kernel panic. Fortunately, wern apfel on InsanelyMac figured out a workaround - drop the MATS ACPI table.
Fortunately, this worked on my system, allowing me to boot macOS. I was curious as to why this previously harmless (and unknown) ACPI table caused panics on 10.13.
With the backtrace, it was clear that AppleACPIPlatform was the culprit. Specifically, it appeared that the panic occurred within the _isprint
function. After some reversing, the functionality of kext’s _isprint
was somewhat clear:
int _isprint(char c)
{
return (_ctype[c] & 0x97) != 0;
}
_isprint
used the value of the char as an index for the _ctype
table. The complete implementation (including the _ctype
table) can be found here.
Initially, it appeared that this could be the acpica re-implentation of _isprint
, as macOS uses acpica in AppleACPIPlatform.kext, and acpica also used a lookup table (with the char value as the index). Further testing showed that this wasn’t the same as the implementation in AppleACPIPlatform, as they produced different results. The acpica _isprint
matched the system _isprint
for char values 0x00 to 0xFF, while AppleACPIPlatform’s _isprint
produced different results for several char values. Nevertheless, these inaccuracies in the AppleACPIPlatform _isprint
implementation alone couldn’t be the sole cause of the panic.
I moved onto the function that _isprint
was called from, which was AcpiTbPrintTableHeader
. Fortunately, this function is from acpica, so I didn’t have to do much reversing.
static void
AcpiTbFixString (
char *String,
ACPI_SIZE Length)
{
while (Length && *String)
{
if (!_isprint ((int) *String))
{
*String = '?';
}
String++;
Length--;
}
}
static void
AcpiTbCleanupTableHeader (
ACPI_TABLE_HEADER *OutHeader,
ACPI_TABLE_HEADER *Header)
{
memcpy (OutHeader, Header, sizeof (ACPI_TABLE_HEADER));
AcpiTbFixString (OutHeader->Signature, ACPI_NAME_SIZE);
AcpiTbFixString (OutHeader->OemId, ACPI_OEM_ID_SIZE);
AcpiTbFixString (OutHeader->OemTableId, ACPI_OEM_TABLE_ID_SIZE);
AcpiTbFixString (OutHeader->AslCompilerId, ACPI_NAME_SIZE);
}
void
AcpiTbPrintTableHeader (
ACPI_PHYSICAL_ADDRESS Address,
ACPI_TABLE_HEADER *Header)
{
[...]
else
{
/* Standard ACPI table with full common header */
AcpiTbCleanupTableHeader (&LocalHeader, Header);
ACPI_INFO ((
"%-4.4s 0x%8.8X%8.8X"
" %06X (v%.2d %-6.6s %-8.8s %08X %-4.4s %08X)",
LocalHeader.Signature, ACPI_FORMAT_UINT64 (Address),
LocalHeader.Length, LocalHeader.Revision, LocalHeader.OemId,
LocalHeader.OemTableId, LocalHeader.OemRevision,
LocalHeader.AslCompilerId, LocalHeader.AslCompilerRevision));
}
}
While the AcpiTbCleanupTableHeader
and AcpiTbFixString
functions weren’t present in the binary, their contents were present within the else
statement, probably due to compiler optimizations. The backtrace showed that the call to _isprint
that led to the panic was at AcpiTbPrintTableHeader+0x101
, which was the last _isprint
call in the function. In the acpica source, this would’ve been AcpiTbFixString (OutHeader->AslCompilerId, ACPI_NAME_SIZE)
, as _isprint
was called from AcpiTbFixString. This meant that one of the chars in MATSHeader->AslCompilerId
caused _isprint
to panic.
The iasl
compiler disassembled the MATS ACPI table and showed its header, revealing that the second character (0x98) in the AslCompilerId
was unprintable. This unprintable character was probably causing the panic in _isprint
.
[000h 0000 4] Signature : "MATS"
[004h 0004 4] Table Length : 00000034
[008h 0008 1] Revision : 02
[009h 0009 1] Checksum : 67
[00Ah 0010 6] Oem ID : "ALASKA"
[010h 0016 8] Oem Table ID : "A M I"
[018h 0024 4] Oem Revision : 00000002
[01Ch 0028 4] Asl Compiler ID : "w x2"
[020h 0032 4] Asl Compiler Revision : 00000000
**** Unknown ACPI table signature [MATS]
Raw Table Data: Length 52 (0x34)
0000: 4D 41 54 53 34 00 00 00 02 67 41 4C 41 53 4B 41 // MATS4....gALASKA
0010: 41 20 4D 20 49 00 00 00 02 00 00 00 77 98 78 32 // A M I.......w.x2
0020: 00 00 00 00 B2 00 00 00 01 00 00 00 98 7D 2F CB // .............}/.
0030: EE FF 00 00 // ....
While messing around with a userspace C reimplementation of the AcpiTb
and _isprint
functions for easier debugging, I made a simple mistake that ended up being quite helpful:
char c = 0x98
printf("%02X\n", c); // 0xFFFFFF98, not 0x98!
Initially confused by the output, I remembered that chars are signed by default, which is why I didn’t get the expected output. But wait - what if the same thing is happening in AppleACPIPlatform?
Further testing showed that this was the probable cause of the panic. Modifying the MATS ACPI table header showed that the system would boot with char values less than 0x80 (remember than 0x7F is the max value for a signed char). Anything above that would cause a panic.
It appears that 0x98 was never cast to a unsigned char, so _isprint
ended up being called with a negative char value. And since it uses the char value as the index, the out-of-bounds memory access caused the panic. In conclusion, if any ACPI table has a char value greater than 0x7F (max value for a signed char) in the Signature
, OemId
, OemTableId
, or AslCompilerId
strings in the table header, it’ll cause a panic with 10.13 DP1’s AppleACPIPlatform.