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)
PoC verification
Evidence pack
findings/poc/DF-5744 Β· 15 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 bymake_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, sogmux_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 tablegmuxobs.c/Makefileβ canary observer KLDrun.logβ decisive run (full console capture)dmesg.txtβ full dmesg of the rig bootfix.diffβ 2 hunks: size validation (classic + indexed-probe minimum)fix_validation.logβ patched-module run on the same rig
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_anyfor SYS_RES_IOPORT with no size check; return value NULL-checked only.apple-gmux.c:77βGMUX_MIN_IO_LENdefined, dead (never referenced in the file; upstream Linux v4.8apple-gmux.cuses it in exactly the check the port dropped).- Consumers of offsets beyond any validated bound:
gmux_pio_read8at 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_spaceI/O ops are rawinb/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):
apple_gmux0: port 0x333-0x336 on acpi0β 4-port resource granted.- Baseline canary:
hw.gmuxobs_scr: 0. kldload apple_gmux(unmodified shipped/boot/kernel/apple_gmux.ko).- Canary after:
hw.gmuxobs_scr: 170(0xaa) β the exact bytegmux_is_indexed()writes tobus_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. - 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
fixedPatched 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
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.
No comments yet.