β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-1113

Unbounded ACPI table Length in sdt_sdth_map enables deterministic boot-time panic DoS

Summary

sdt_sdth_map at acpi_sdt.c:137-151 trusts firmware-supplied uint32 ACPI_TABLE_HEADER.Length with no upper bound and passes to pmap_mapdev. Only validation is mapsz<sizeof(*sdth) lower bound at :147. Length near UINT32_MAX forces multi-GiB kmem_alloc_nofault -> fails -> panic("pmap_mapdev: Couldnt alloc kernel virtual memory") at pmap.c:6203. sdt_search_xsdt/rsdt derive nent from same unbounded Length, so fat XSDT/RSDT drives per-entry loop calling sdt_sdth_map on attacker-named physical addresses. Boot-only SI_BOOT2_PRESMP SYSINIT. Attacker: malicious/buggy firmware or loader hint.acpi.0.rsdp. Impact: deterministic boot panic/hang. Fix: clamp Length to SDT_MAX_TABLE_SIZE=1MB.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1113 Β· 8 files
FileTypeDescriptionSize
df1113.c trigger-source documentation-only source trace stub 1.8 KB view raw
build.sh build-script no compilation needed (boot-time panic) 274 B view raw
run.sh run-script no runtime trigger 192 B view raw
fix.diff suggested-fix clamp mapsz to SDT_MAX_TABLE_SIZE (1 MB) in sdt_sdth_map 1.3 KB view raw
VERDICT.md verdict full source-level trace + Phase 6 hard-blocker analysis 4.0 KB ↓ raw
README.md readme claim summary + reproducibility notes 2.8 KB ↓ raw
../fix_build_combined.log build-log Combined 41-finding kernel build (rc=0, -Werror clean) 5.6 MB ↓ download
../fix_build_summary.txt build-summary Summary of the combined 41-finding kernel build 826 B view raw
README.md readme claim summary + reproducibility notes
↓ download raw

DF-1113 β€” Unbounded ACPI table Length in sdt_sdth_map (boot panic)

Claim

sdt_sdth_map() at sys/platform/pc64/acpica/acpi_sdt.c:137-151:

void *
sdt_sdth_map(vm_paddr_t paddr)
{
    ACPI_TABLE_HEADER *sdth;
    vm_size_t mapsz;

    sdth = pmap_mapdev(paddr, sizeof(*sdth));     /* map the header */
    mapsz = sdth->Length;                          /* line 144 β€” firmware uint32 */
    pmap_unmapdev((vm_offset_t)sdth, sizeof(*sdth));

    if (mapsz < sizeof(*sdth))                     /* line 147 β€” lower bound only */
        return NULL;

    return pmap_mapdev(paddr, mapsz);              /* line 150 β€” unbounded */
}

The only validation is mapsz < sizeof(*sdth) (a lower bound of 36 bytes). mapsz is the firmware-supplied uint32 Length field of an ACPI table header β€” there is no upper bound.

pmap_mapdev_attr at sys/platform/pc64/x86_64/pmap.c:6191-6222 does:

size = roundup(offset + size, PAGE_SIZE);
va = kmem_alloc_nofault(kernel_map, size, VM_SUBSYS_MAPDEV, PAGE_SIZE);
if (va == 0)
    panic("pmap_mapdev: Couldn't alloc kernel virtual memory");

A Length near UINT32_MAX causes kmem_alloc_nofault to try to allocate ~4 GiB of kernel virtual memory. On a 4 GiB-RAM guest this fails deterministically β†’ panic.

sdt_search_xsdt / sdt_search_rsdt (acpi_sdt.c:159-279) compute nent from the same unbounded Length (line 194-195 / 255-256), so a "fat" XSDT or RSDT also drives a per-entry loop that calls sdt_sdth_map on attacker-controlled physical addresses β€” every iteration can panic.

sdt_sdth_map is invoked from acpi_sdt_* initialisation, which runs at SI_BOOT2_PRESMP (SYSINIT) β€” i.e. boot-only, before userland starts. Attacker controls the ACPI tables via: - malicious / buggy firmware (the realistic case), - loader hint hint.acpi.0.rsdp= pointing at a crafted RSDP, - a malicious hypervisor controlling guest ACPI.

Reproducibility on this guest

The bug is boot-time only. The audit guest's BOCHS firmware supplies well-formed ACPI tables (visible in dmesg: RSDT ... 000034, DSDT ... 001AF8, etc. β€” all reasonable lengths). The kernel boots cleanly and reaches multi-user. To trigger the panic, we would need to reboot under a malicious firmware or a loader.conf override pointing at a crafted RSDP physical address β€” neither is in the threat model of an audit guest that must keep running for the other tests.

The bug is latent β€” confirmed by source trace. The fix is purely defensive against malicious/buggy firmware.

Fix

fix.diff: clamp mapsz to an upper bound SDT_MAX_TABLE_SIZE = 1 MB inside sdt_sdth_map, after the existing lower-bound check. Any ACPI table larger than 1 MB is malformed (the largest legitimate table on this guest is <8 KB), so the clamp rejects attacker-controlled lengths near UINT32_MAX without breaking well-formed firmware.

VERDICT.md verdict full source-level trace + Phase 6 hard-blocker analysis
↓ download raw

DF-1113 β€” VERDICT

Verdict: NOT REPRODUCED at runtime (latent β€” boot-time-only panic; guest's BOCHS firmware supplies well-formed tables and boots cleanly). Bug confirmed by source trace.

Mechanism (source-confirmed)

sdt_sdth_map() at sys/platform/pc64/acpica/acpi_sdt.c:137-151:

void *
sdt_sdth_map(vm_paddr_t paddr)
{
    ACPI_TABLE_HEADER *sdth;
    vm_size_t mapsz;

    sdth = pmap_mapdev(paddr, sizeof(*sdth));     /* map header */
    mapsz = sdth->Length;                          /* :144 β€” firmware uint32 */
    pmap_unmapdev((vm_offset_t)sdth, sizeof(*sdth));

    if (mapsz < sizeof(*sdth))                     /* :147 β€” lower bound only */
        return NULL;

    return pmap_mapdev(paddr, mapsz);              /* :150 β€” unbounded */
}

The only validation is mapsz < sizeof(*sdth) β€” a lower bound of 36 bytes. There is no upper bound. mapsz is the firmware-supplied uint32 Length of an ACPI table header.

pmap_mapdev_attr at sys/platform/pc64/x86_64/pmap.c:6191-6222 does:

size = roundup(offset + size, PAGE_SIZE);
va = kmem_alloc_nofault(kernel_map, size, VM_SUBSYS_MAPDEV, PAGE_SIZE);
if (va == 0)
    panic("pmap_mapdev: Couldn't alloc kernel virtual memory");

A Length near UINT32_MAX makes kmem_alloc_nofault try to allocate ~4 GiB of kernel virtual memory. On a 4 GiB-RAM guest (the audit config), this fails deterministically β†’ panic.

sdt_search_xsdt / sdt_search_rsdt (acpi_sdt.c:159-279) compute nent from the same unbounded Length (lines 194-195 / 255-256), so a "fat" XSDT/RSDT also drives a per-entry loop calling sdt_sdth_map on attacker-controlled physical addresses β€” every iteration can panic.

sdt_sdth_map runs from sdt_probe (SYSINIT SI_BOOT2_PRESMP, acpi_sdt.c:118) β€” boot-only, before userland starts. Attacker controls the tables via: - malicious / buggy firmware, - loader hint hint.acpi.0.rsdp=<phys> pointing at a crafted RSDP (loader.conf override), - a malicious hypervisor controlling guest ACPI.

Why it can't be triggered on this guest

The guest's BOCHS firmware supplies well-formed ACPI tables (visible in dmesg: RSDT ... 000034, DSDT ... 001AF8, APIC ... 0000A0, etc. β€” all reasonable sizes). The kernel boots cleanly to multi-user.

The bug fires only at boot, only with malformed firmware, only if the guest is rebooted under malicious firmware or with a loader.conf override pointing hint.acpi.0.rsdp= at a crafted physical address. Re-booting the audit guest under malicious firmware is outside the runtime threat model (it would invalidate every other PoC running on the guest).

Exploit chain

Primitive class: DoS (deterministic boot panic). There is no write primitive β€” pmap_mapdev_attr panics inside kmem_alloc_nofault before any mapping is established, so no kernel memory is corrupted. The realistic attacker is firmware/malicious hypervisor, not a runtime process.

Valid hard blocker ("vulnerable code path is dead code at runtime on this guest AND no harness can exercise it without rebooting under malicious firmware"). Documented as latent. No uid=0 achievable.

Fix

fix.diff adds an upper-bound check mapsz > SDT_MAX_TABLE_SIZE (1 MB) after the existing lower-bound check, returning NULL for any table claiming to be larger than 1 MB. Legitimate ACPI tables are well under 1 MB (the DSDT on this guest is 0x1AF8 bytes), so the clamp rejects only attacker-controlled / malformed lengths near UINT32_MAX. Validated as applies + compiles in a clean GENERIC kernel build.

Fix validation

not_testable β€” path is boot-only and not reproducible on a guest with honest firmware. Validated fix.diff applies cleanly and compiles with -Werror in a full make nativekernel KERNCONF=X86_64_GENERIC (acpi_sdt.o rebuilt fresh, no warnings/errors β€” see ../DF-1096/all_fixes_build.log).

PoC changes

  • Wrote df1113.c (documentation-only stub explaining the source trace).
  • Wrote fix.diff, build.sh, run.sh.

Fix verification

not_testable

compile validated

see evidence pack

Confirmed kernel references

β€”

Detail

Exploit chain

none

Evidence (decisive lines)

β€”

Verdict

Source-confirmed. ACPI sdt_sdth_map unbounded Length -> boot panic. Boot-time only. Compile validated.