• Home
  • About
    • Alex James photo

      Alex James

      theracermaster's blog

    • Learn More
    • Email
    • Twitter
    • Github
    • Keybase
  • Posts
    • All Posts
    • All Tags

Debugging AppleACPIPlatform on 10.13

10 Jun 2017

Reading time ~4 minutes

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:

Kernel backtrace with keepsyms=1

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.

wern apfel's post on InsanelyMac in the 10.13 prerelease thread

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.



acpihackintoshmacos