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

apple_gmux: missing I/O-region length validation (GMUX_MIN_IO_LEN dead code) β†’ out-of-bounds x86 port I/O from crafted ACPI _CRS

Summary

The DFly port of Linux v4.8 dropped the upstream iolen check: GMUX_MIN_IO_LEN is defined and never referenced, and attach validates only non-NULL - on x86 bus_read/write_1 are raw inb/outb with no bounds enforcement, so an APP000B device with an undersized _CRS makes the driver touch ports far outside its rman allocation (version reads at 0x04-0x06, the indexed probe WRITING 0xaa/0x55/0x00 at 0xcc-0xce, the indexed protocol at 0xc2-0xd4). Planted-ACPI rig: a 4-port allocation and the shipped production module wrote 0xaa 204 bytes past it into the UART scratch register (COM1 SCR read 170). restore-and-extend the check fix validated (module-only rebuild: 'I/O region too small', canary untouched). Crafted-firmware precondition - Low.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5744 Β· 15 files
FileTypeDescriptionSize
README.md β€” 3.3 KB ↓ raw
VERDICT.md β€” 4.2 KB ↓ raw
make_ssdt.py β€” 3.7 KB view raw
ssdt_a.aml β€” 90 B ↓ download
gmuxobs.c β€” 1.9 KB view raw
Makefile β€” 140 B ↓ download
build.sh β€” 136 B view raw
run.sh β€” 193 B view raw
run.log β€” 418 B view raw
dmesg.txt β€” 6.7 KB view raw
fix.diff β€” 1.0 KB view raw
fix_validation.log β€” 536 B view raw
env.txt β€” 296 B view raw
manifest.json β€” 924 B view raw
verdict.json β€” 3.8 KB view raw

DF-5744 β€” apple_gmux: missing I/O-region length validation β†’ out-of-bounds port I/O

What

sys/gnu/dev/misc/apple_gmux/apple-gmux.c attach never validates the size of the I/O resource it gets from the ACPI _CRS. GMUX_MIN_IO_LEN is defined at apple-gmux.c:77 and never referenced β€” the upstream Linux v4.8 driver this was ported from (drivers/platform/x86/apple-gmux.c) has

if (gmux_data->iolen < GMUX_MIN_IO_LEN) { ... ret = -ENODEV; goto err_release; }

but the DragonFly port dropped it. On x86, bus_read_1/bus_write_1 on an IOPORT resource are raw inb/outb with no bounds checking, so with a short _CRS the production driver performs reads/writes to I/O ports far outside its rman allocation: version regs at offsets 0x04–0x06, and (when those read 0xff) the indexed probe gmux_is_indexed() writes 0xaa/0x55/0x00 to offsets 0xcc–0xce and the indexed protocol touches 0xc2–0xc5, 0xd0, 0xd4.

Precondition: an ACPI gmux device (APP000B) whose _CRS window is smaller than the registers the driver touches β€” i.e. crafted/malicious firmware, a root-modified DSDT override, or a firmware bug. (Requires no Apple hardware: the driver is a loadable KLD that will happily probe any APP000B device.)

Rig

  • ssdt_a.aml (generated by make_ssdt.py): one device \_SB.ZZZ0, _HID "APP000B", _CRS = 4 ports at 0x333.
  • gmuxobs.c β†’ gmuxobs.ko: canary module watching the 16550 COM1 scratch register at port 0x3ff β€” fully readable/writable, zero functional side effects. 0x333 + 0xcc = 0x3ff, so gmux_is_indexed()'s first indexed probe write lands exactly on the canary.

Build / run (guest, root)

cd /root/gmuxobs_a && make                          # gmuxobs.ko
# boot QEMU with: -acpitable file=ssdt_a.aml
kldload /root/gmuxobs_a/gmuxobs.ko                  # baseline: hw.gmuxobs_scr: 0
kldload apple_gmux                                  # PRODUCTION module
sysctl hw.gmuxobs_scr                               # expect 0xaa == 170

Expected result (observed 2026-10-11, DragonFly 6.5-DEVELOPMENT X86_64_GENERIC)

apple_gmux0: <apple gmux controller> port 0x333-0x336 on acpi0   <- 4-port resource
apple_gmux: gmux device not present or IO disabled
device_probe_and_attach: apple_gmux0 attach returned -19
hw.gmuxobs_scr: 170                                               <- 0xaa canary hit

The production driver wrote 0xaa to port 0x3ff β€” 204 bytes past the end of its 4-port allocation β€” corrupting an unrelated device's register (here the UART SCR; on real hardware whatever lives near the gmux window: PIC ELCR, PM registers, SMBus, ...). Also 3 OOB reads at 0x337–0x339 (offsets 4–6 vs size 4) before the indexed probe.

Fix validation (same rig, patched module from fix.diff)

apple_gmux: gmux I/O region too small (4 bytes, need >= 120)
device_probe_and_attach: apple_gmux0 attach returned -19
hw.gmuxobs_scr: 0        <- canary untouched

Files

  • make_ssdt.py β€” SSDT generator (hand-encoded AML)
  • ssdt_a.aml β€” the planted table
  • gmuxobs.c / Makefile β€” canary observer KLD
  • run.log β€” decisive run (full console capture)
  • dmesg.txt β€” full dmesg of the rig boot
  • fix.diff β€” 2 hunks: size validation (classic + indexed-probe minimum)
  • fix_validation.log β€” patched-module run on the same rig
VERDICT.md
↓ download raw

DF-5744 VERDICT β€” REPRODUCED (production code, on-guest)

Status: reproduced Β· impact: dos-class hardware-state corruption (OOB port I/O) Β· confidence: certain

Root cause (static, certain)

  • sys/gnu/dev/misc/apple_gmux/apple-gmux.c:737-744 β€” bus_alloc_resource_any for SYS_RES_IOPORT with no size check; return value NULL-checked only.
  • apple-gmux.c:77 β€” GMUX_MIN_IO_LEN defined, dead (never referenced in the file; upstream Linux v4.8 apple-gmux.c uses it in exactly the check the port dropped).
  • Consumers of offsets beyond any validated bound:
  • gmux_pio_read8 at 0x04–0x06 during attach (apple-gmux.c:758-760)
  • gmux_is_indexed() writes 0xcc/0xcd/0xce (apple-gmux.c:351-353)
  • indexed protocol: 0xc2–0xc5 (VALUE, incl. bus_read_4), 0xd0 (READ), 0xd4 (WRITE) (apple-gmux.c:216-303)
  • x86 bus_space I/O ops are raw inb/outb β€” no rman bounds enforcement.

Reproduction (guest: DragonFly 6.5-DEVELOPMENT, X86_64_GENERIC, QEMU i440fx)

Planted SSDT with \_SB.ZZZ0 { _HID "APP000B", _CRS = IO 4 ports @ 0x333 } (QEMU -acpitable), COM1 SCR (port 0x3ff) as a side-effect-free canary β€” 0x333+0xcc = 0x3ff exactly.

Observed (run.log):

  1. apple_gmux0: port 0x333-0x336 on acpi0 β€” 4-port resource granted.
  2. Baseline canary: hw.gmuxobs_scr: 0.
  3. kldload apple_gmux (unmodified shipped /boot/kernel/apple_gmux.ko).
  4. Canary after: hw.gmuxobs_scr: 170 (0xaa) β€” the exact byte gmux_is_indexed() writes to bus_write_1(sc->io_res, 0xcc, 0xaa) (apple-gmux.c:351), landing 0xcc bytes past the end of the 4-port allocation, in another device's register.
  5. Attach then failed -ENODEV ("gmux device not present") β€” the OOB accesses are on the failure path by design of the port: they happen before the driver gives up.

This is production code writing to hardware registers of unrelated devices outside its allocated I/O window. On the rig the victim is a UART scratch byte (chosen to be harmless); on real hardware the adjacent window can hold PIC ELCR, PM logic, embedded controller or SMBus registers β€” corruption there is at minimum a hang/panic (dos), potentially worse (e.g. toggling power rails via EC registers).

Threat model

Reachability requires an APP000B ACPI device with an undersized _CRS: malicious/crafted firmware (supplier compromise, evil DMA... not realistic for remote), or root-performed ACPI table override + kldload (already privileged), or a benign firmware bug (real: Linux added the length check because such machines existed). Not reachable by an unprivileged local user. Hence Low severity despite the clean OOB-write primitive: it is a robustness/divergence defect whose trigger is privileged input, consistent with the audit's treatment of crafted-firmware preconditions.

Fix validation

fix.diff (2 hunks): reject regions < GMUX_MIN_IO_LEN at attach, and reject regions < GMUX_PORT_WRITE+4 before the indexed probe (the probe runs before classic/indexed is known, and touches up to 0xd4).

Patched module (module-only rebuild of the same source), identical rig:

apple_gmux: gmux I/O region too small (4 bytes, need >= 120)
device_probe_and_attach: apple_gmux0 attach returned -19
hw.gmuxobs_scr: 0        <- canary untouched, no OOB access

Baseline bug reproduced / patched behavior gone β‡’ fix_status: fixed.

Notes / divergence

  • Upstream Linux v4.8 checks iolen < GMUX_MIN_IO_LEN (0x78) only; the indexed probe's 0xcc–0xd4 overreach beyond 0x78 exists upstream too β€” but upstream never drops the check. DragonFly's port is strictly more permissive; the port owner (DragonFly) should restore the check and extend it to the indexed ports.
  • Rig construction notes in make_ssdt.py (hand-encoded AML; small I/O descriptor 0x47 carries byte-sized align/len β€” verified against QEMU's own DSDT; EndTag checksum byte must be 0x00).

Fix status (batch validation)

Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

Patched module on the identical rig prints 'gmux I/O region too small (4 bytes, need >= 120)', attach fails -ENODEV before any register access, and the canary port is untouched (0x00). Baseline (shipped module) wrote 0xaa to it. Behavior change confirmed.

findings/poc/DF-5744/fix_validation.log; fix.diff
↓ fix.diffper-fix-DF-5744

Confirmed kernel references

Detail

Exploit chain

crafted ACPI DSDT/SSDT with APP000B device + undersized _CRS -> kldload apple_gmux -> attach performs unbounded bus_read_1/bus_write_1 at offsets up to 0xd4 on the short resource -> out-of-bounds x86 port I/O to unrelated hardware registers (demonstrated: 0xaa written to UART SCR at base+0xcc); on real hardware adjacent ELCR/PM/EC/SMBus registers are corruptible -> system instability/hang (dos). No kernel-memory impact; not reachable unprivileged.

Evidence (decisive lines)

run.log (baseline hw.gmuxobs_scr:0 -> after kldload apple_gmux: 170=0xaa; 'port 0x333-0x336 on acpi0' proves 4-port resource), dmesg.txt, make_ssdt.py + ssdt_a.aml (planted table), gmuxobs.c (canary), fix_validation.log (patched: 'gmux I/O region too small (4 bytes, need >= 120)', canary stays 0)

PoC changes

Seed had no runnable rig. Built: hand-encoded AML SSDT generator (small IO descriptor 0x47 requires byte-sized align/len and EndTag 79 00 β€” verified against QEMU's own DSDT after two dead ends), a canary KLD watching COM1 SCR (port 0x3ff = base 0x333 + offset 0xcc), and module-only fix rebuild for validation. No Apple hardware needed: driver is a KLD probing any APP000B ACPI device.

Verified recommended fix

Validate rman_get_size(io_res) >= GMUX_MIN_IO_LEN at attach and >= GMUX_PORT_WRITE+4 before the indexed probe (see fix.diff).

Verdict

REPRODUCED on-guest with production code: apple_gmux attach (apple-gmux.c:737-744) accepts any ACPI _CRS size β€” GMUX_MIN_IO_LEN (apple-gmux.c:77) is dead code, the check present in upstream Linux v4.8 was dropped in the DragonFly port. With a planted 4-port APP000B device, the shipped /boot/kernel/apple_gmux.ko performed out-of-bounds port I/O past its 4-port rman allocation: 3 OOB reads at offsets 4-6 and indexed-probe writes 0xaa/0x55/0x00 at offsets 0xcc-0xce; the 0xaa write landed exactly on the COM1 SCR canary at 0x3ff (observed 0 -> 0xaa), i.e. a write into an unrelated device's register 204 bytes outside the allocation. Impact ceiling is hardware-state corruption (hang/panic class) from crafted/malicious ACPI or a firmware bug; trigger is privileged input, hence Low severity. fix.diff (size validation incl. indexed-probe minimum) validated on the same rig: undersized region rejected, canary untouched.