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)
PoC verification
Evidence pack
findings/poc/DF-1113 Β· 8 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 |
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.
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_testablecompile 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.
No comments yet.