ich_pci_attach() publishes the 0666 /dev/mixerN (mixer_init) before five failure paths whose bad: unwind frees the AC97 codec and softc β dangling mixer cdev UAF on open/ioctl (DF-4156/4193/4299/4334/4344 family, 6th instance, widest window in family)
Summary
mixer_init creates the 0666 /dev/mixerN dispatching into sc->codec; five later attach failures (bus_dma_tag_create x2, bus_dmamem_alloc, bus_dmamap_load, pcm_register) goto bad: which ac97_destroy()s the codec and kfrees the softc without destroying the mixer cdev. Any local user opening/ioctl-ing the surviving node dispatches through freed memory. Widest window in the family (five failure paths; mixer_init return ignored). Hardware/alloc-pressure dependent trigger, matching family precedent. pcm_register-before-mixer_init reorder fix (compile-validated on guest toolchain).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4421 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 3.1 KB | β raw | |
| VERDICT.md | β | 8.7 KB | β raw | |
| run.sh | β | 196 B | view raw | |
| trigger.hw.sh | β | 1.1 KB | view raw | |
| guest_check.log | β | 509 B | view raw | |
| env.txt | β | 192 B | view raw | |
| build.log | β | 7.8 KB | view raw | |
| fix.diff | β | 1.3 KB | view raw | |
| manifest.json | β | 1.5 KB | view raw | |
| verdict.json | β | 4.4 KB | view raw |
DF-4421 β snd_ich attach-failure path leaves live 0666 /dev/mixerN over freed AC97 codec + softc (UAF on open/ioctl)
6th instance of the pcm attach-error family (DF-4156 maestro, DF-4193 es137x,
DF-4299 emu10kx-pcm, DF-4334 via8233, DF-4344 atiixp), this time in
sys/dev/sound/pci/ich.c.
TL;DR
ich_pci_attach() calls mixer_init() (ich.c:1031) β which publishes a
0666 /dev/mixerN whose si_drv1 chain reaches the AC97 codec β before
five more failure paths that all jump to bad: (ich.c:1045, 1053, 1060,
1065, 1069). bad: frees the codec (ac97_destroy, ich.c:1096-1097) and the
softc (kfree, ich.c:1118) but never tears the mixer cdev down. Any later
open/ioctl on /dev/mixerN dispatches through the freed codec's
kobj'd methods table β use-after-free (read/write primitives through
mixer_ioctl β mix_getdevinfo β freed struct ac97_info).
Not runtime-reproducible on the audit guest: QEMU (i440FX machine) presents
no Intel ICH AC'97 controller (no class=0x0401 device; only 8086 chips
1237/7000/7010/7113 β see guest_check.log). snd_ich.ko loads but
ich_pci_probe() never matches, so ich_pci_attach() never executes.
Source-certain by construction and by symmetry with the 4 runtime-verified
family members.
How to reproduce (on hardware with an ICH AC'97 controller)
Preconditions: any machine with an Intel ICH/ICH2/.../ICH7, SiS 7012, nForce, AMD-768/8111 AC'97 controller (PCI class 0401, e.g. 8086:2415), plus one of:
- memory pressure sufficient to fail
bus_dmamem_alloc/bus_dmamap_load(ich.c:1060/1065), or bus_dma_tag_createfailure (ich.c:1045/1053), or- 33+ sound devices so
pcm_registerhitsPCMMAXUNITβ ENODEV (sound.c:1027-1033), or - a boot where feeder init left
pcm_veto_load=1βpcm_registerreturns EINVAL (sound.c:1021-1024).
# with hw.snd.maxunit exhausted (33rd sound card) or any alloc failure:
kldload snd_ich # attach fails AFTER mixer_init succeeded
ls -l /dev/mixer0 # device still exists, 0666 root:wheel
dd if=/dev/mixer0 bs=64 count=1 | hexdump -C # UAF read path
# or any SNDCTL_MIXER_* ioctl via /dev/mixer0 β UAF through freed codec
On the audit guest this is impossible β no AC'97 PCI function exists, so
the trigger never reaches ich_pci_attach() (guest_check.log).
Artifacts
VERDICT.mdfull narrative + root-cause table + negative resultsguest_check.logproof of hardware gate (no 0401 PCI device, no pcm dev)run.shexact guest check commands (what was run)trigger.hw.shthe real-hardware trigger scriptfix.difffamily-consistent fix (pcm_register before mixer_init)env.txtguest environmentmanifest.jsonmachine-readable catalogverdict.jsonmachine verdict (consumed by audit/persist_poc.py)
Fix
Reorder pcm_register() ahead of mixer_init() (no failure path may remain
after the mixer cdev is published) and check mixer_init()'s return β the
same shape accepted for DF-4334 (via8233). See fix.diff.
DF-4421 VERDICT β snd_ich /dev/mixerN UAF on attach-failure path
Status: inconclusive (code defect certain; kernel runtime hardware-gated)
Impact ceiling: use-after-free reachable by any local user via a 0666 cdev
(open/ioctl), with kernel read/write through the freed struct ac97_info
methods table. Trigger requires the driver's attach to fail after
mixer_init() β hardware/alloc-pressure dependent, hence Low severity,
matching the family precedent.
Root cause (path:line)
| # | Fact | Reference |
|---|---|---|
| 1 | Codec created | ich.c:1004 sc->codec = AC97_CREATE(dev, sc, ich_ac97) |
| 2 | 0666 mixer cdev published, devinfo = codec | ich.c:1031 mixer_init(dev, ac97_getmixerclass(), sc->codec); mixer.c:687; make_dev(..., 0666, "mixer%d") mixer.c:726-727; pdev->si_drv1 = m mixer.c:728; m->devinfo = codec via mixer_obj_create mixer.c:711 |
| 3 | Failure paths AFTER the cdev exists: bus_dma_tag_create Γ2 |
ich.c:1045-1050, 1053-1058 β goto bad |
| 4 | bus_dmamem_alloc / bus_dmamap_load |
ich.c:1060-1063, 1065-1067 β goto bad |
| 5 | pcm_register (EINVAL via pcm_veto_load; ENODEV via PCMMAXUNIT) |
ich.c:1069-1070 β goto bad; sound.c:1021-1033 |
| 6 | bad: frees the codec |
ich.c:1096-1097 ac97_destroy(sc->codec) β kobj_delete + kfree (ac97.c:852-859) |
| 7 | bad: frees the softc |
ich.c:1118 kfree(sc, M_DEVBUF) |
| 8 | Nothing destroys /dev/mixerN | bad: path ich.c:1095-1119 has no mixer_uninit/destroy_dev; mixer teardown only happens via pcm_unregister (sound.c), which is never reached |
| 9 | Dangling dispatch | any open("/dev/mixerN")/ioctl β mixer_ops β si_drv1 (snd_mixer, whose backing m->devinfo was the codec) β ac97mix_... kobj methods on freed memory |
Note ich.c is worse than atiixp (DF-4344, which had exactly one
post-mixer failure point): five distinct gotos exist after mixer_init(),
and ich.c additionally ignores mixer_init()'s return value entirely
(ich.c:1031) β a failure there is silent (that direction is safe only
because mixer_init cannot fail after make_dev; verified mixer.c:726-764).
Why not reproduced (hardware gate)
The audit guest is QEMU i440FX (guest_check.log): PCI inventory contains
only 8086:1237/7000/7010/7113 β no class=0x0401 AC'97 audio function.
ich_pci_probe() (ich.c:861-879) matches none of the ich_devs[] IDs
against any device, so ich_pci_attach() never executes. snd_ich.ko
itself loads cleanly (kldstat shows it; module compiled from the same
source), proving the code is deployed in the module set β only the
trigger hardware is absent. Same situation as verified family members
DF-4334/DF-4344 on this guest.
Exploitability discussion (on real hardware)
- The dangling object is reachable through a 0666 cdev: any local UID (no group membership) can open it.
- Primitive: UAF read/write of moderate quality β mixer ioctls write
attacker-scaled values into codec registers (
ac97_wrcdβbus_space_write_2through freed-but-stale pointers), read back codec state. The kobj methods table itself is freed (kobj_delete), so the dispatch already dereferences freed memory on the first ioctl. - Grooming:
struct ac97_infoisM_AC97(kmalloc ~ small size class); between attach failure and the mixer open there is no window pressure β an attacker who can cause the alloc failure (memory pressure is the realistic remote-ish knob) then sprays the size class. - Realistic ceiling: local privesc UAF, but the trigger needs an attach-time failure β not attacker-schedulable at will. Family rated Low; consistent here.
Negative results (pass-2 classes hunted and killed β ich.c)
ichchan_initwritessc->ch[num]fields BEFORE thedefault: return (NULL)bounds check (ich.c:375-384 vs 410-411) β would be an OOB write into trailingsc_infofields (ac97rate/dtbl/ich_lock) for num>=3. Killed:channel_initis invoked exactly once perpcm_addchan(sound.c:585 via pcm_chn_create), and ich.c callspcm_addchanat most 3Γ (ich.c:1072-1075), so num β {0,1,2} always. Unreachable.ichchan_setspeed(speed * 48000)(ich.c:467): no uint32 overflow β pcm core clamps viaRANGE(hwspeed, caps->minspeed, caps->maxspeed)(channel.c:1983) with ich caps 8000..48000 β max 2.304e9 < 2^32.ac97rateguarded to (32000,64000) at ich.c:463-464.ichchan_getptrpos = civ*blksz(ich.c:557-560): in-bounds βich_filldtblclampsblksz*blkcnt <= sndbuf maxsize(ich.c:305-306) before the dtbl build; blkcnt β [2,32] always (clamped ich.c:969-972, default 2), so no div-by-zero either. Post-calibrate blksz/blkcnt mismatch (ich.c:733-737 vs 776) self-heals: any laterichchan_setblocksizeβich_filldtblre-clamps (ich.c:493-500), and even a hypothetical OOB hwptr is neutralized by the modulo inchn_dmaupdate(channel.c:387).ich_setmap(ich.c:818-824): reads onlysegs[0]; the BDL dma tag is created with nsegments=1 (ich.c:1045-1047), so the callback can never see more than one segment. No ICH_BD_CAPS-style seg-count issue.- BDLE length field (ich.c:316-317): blksz β€ maxsize/blkcnt β€ 32768 (blkcnt β₯ 2), SIS7012 sample_size=1 (ich.c:903) β β€ 32768 fits the 16-bit length field below IOC bit 31.
ich_rdcd/ich_wrcdreg mask& 0xff(ich.c:269, 280): stays inside the 256-byte NAMBAR/MMBAR mapping;ich_waitcdbounded 1000ΓDELAY(1) (ich.c:252-257). Ignoringich_waitcd's ETIMEDOUT (ich.c:270, 281) reads stale codec data on timeout β functional, not memory-unsafe (upstream-identical).ich_intrLVI arithmetic (ich.c:637-649): all quantities reduced modulo blkcnt/ICH_DTBL_LENGTH before the 8-bit register write; CIV read masked% blkcnt(ich.c:557); uninitch[2].imask==0(M_ZERO softc) skips cleanly when !hasmic.ich_pci_resumeich_init(sc) == -1(ich.c:1198) is a dead check (ich_init returns 0/ENXIO, never -1): failed re-init not caught, but latermixer_reinitfailure still returns ENXIO; no memory-safety consequence (upstream-identical cosmetic bug).1 << (i - 1)at ich.c:966-968: UB whenblocksizehint > bufsz makes blkcnt 0 β but hint is root-only (resource_int_valuefrom device.hints), and on x86-64 the shift count masks to 31 β 0x80000000 β clamped to ICH_MAX_BLKCNT=32 (ich.c:971-972). Self-healing; not attacker-reachable. (Hardening note only.)- Detach-vs-calibrate intrhook race (ich.c:1083-1090 hook established only during cold boot; ich_pci_detach ich.c:1122-1144 never disestablishes): the hook drains within seconds of boot before userland can kldunload β unreachable window.
- Suspend/resume trigger loop over ch[0..2] with uninitialized ch[2]
(ich.c:1176-1183, 1212-1219):
run_save/runare 0 via M_ZERO; loop bodies gated on them. Safe.
Fix validation
fix.diff applies the DF-4334 family shape to ich.c:
* move pcm_register() to before mixer_init() (all DMA/dtbl setup stays
before pcm_register, which needs sc->hasmic from the extcaps block β
order: codec β extcaps β dtbl/dma β pcm_register β mixer_init
(checked) β pcm_addchan),
* check mixer_init()'s return (its failure leaves no cdev β verified
mixer.c:726-764 has no failure path after make_dev).
git apply --check passes against the read-only sys/ tree. Additionally the
patched driver was COMPILE-validated on the guest: the patched ich.c was
pushed into the guest's /usr/src and make in
/usr/src/sys/dev/sound/driver/ich compiled it with -Werror (cc 8.3) and
linked snd_ich.ko (25784 bytes) β full untrimmed output in build.log
(BUILD_RC=0). Guest source restored to pristine afterward
(md5 2a290625f264317f5ce793fbb381a197 matches the audit tree). A behavioral
patched-kernel run remains impossible on this guest: ich_pci_attach
requires an ICH/SIS/nForce/AMD AC'97 controller the guest cannot present,
so baseline/patched behavior is not demonstrable here (fix_status:
not_testable, compile-validated). The semantics of the reorder were verified
against sound.c:1016-1040 (pcm_register has no mixer dependency) and mixer.c
(mixer_init has no channel dependency, and cannot fail after make_dev).
Guest state
snd_ich.ko was left loaded during the check (harmless: probes nothing,
attaches nothing). Not unloaded because kldunload of snd_* on this guest
panics in sndstat teardown β known DF-4116 (recorded in DF-4344's run);
no reset needed since no panic occurred and no persistent state changed.
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
not_testablefix.diff (git apply --check OK) reorders pcm_register() ahead of mixer_init() and checks its return; validated to COMPILE cleanly with -Werror on the guest toolchain (cc 8.3, patched snd_ich.ko built and linked, build.log). Behavioral baseline/patched runs are impossible on this guest: ich_pci_attach requires an AC'97 controller the guest cannot present, so the failure path never executes at runtime (fix_status not_testable, compile-validated).
fix.diff; build.log (full -Werror compile + link, BUILD_RC=0); VERDICT.md 'Fix validation' section citing mixer.c:726-764 (no make_dev failure path) and sound.c:1016-1040 (pcm_register has no mixer dependency)
Confirmed kernel references
Detail
Evidence (decisive lines)
VERDICT.md root-cause table (ich.c:1031-1118, mixer.c:687-737, sound.c:1016-1040); guest_check.log (no 0401 PCI device, snd_ich loads, 0 pcm devices); build.log (fix-compiled snd_ich.ko, -Werror clean); fix.diff (git apply --check OK)
PoC changes
no seed; family trigger shape (DF-4334/DF-4344) adapted; hardware absent so guest probe substituted
Verified recommended fix
Move pcm_register() before mixer_init() and check mixer_init()'s return (DF-4334 family shape), eliminating all failure paths after the 0666 mixer cdev is published
Verdict
Code defect certain, kernel runtime hardware-gated: ich_pci_attach() calls mixer_init() (ich.c:1031, publishing a 0666 /dev/mixerN whose si_drv1 devinfo chain reaches the AC97 codec, mixer.c:726-728) before five failure paths that all goto bad (bus_dma_tag_create x2 ich.c:1045/1053, bus_dmamem_alloc ich.c:1060, bus_dmamap_load ich.c:1065, pcm_register ich.c:1069 which fails EINVAL via pcm_veto_load or ENODEV via PCMMAXUNIT, sound.c:1021-1033); the bad: unwind then ac97_destroy()s the codec (ich.c:1096-1097, kobj_delete+kfree per ac97.c:852-859) and kfrees the softc (ich.c:1118) while the mixer cdev stays registered -- any later mixer open/ioctl dispatches through freed memory. Identical construct to the verified family members (DF-4156 maestro, DF-4193 es137x, DF-4299 emu10kx-pcm, DF-4334 via8233, DF-4344 atiixp); this is the 6th instance, with the widest window in the family (five post-mixer gotos vs atiixp's one) and mixer_init's return ignored entirely. Not runtime-reproducible on the audit guest: QEMU i440FX has no AC'97 controller (no class 0401 PCI function; only 8086:1237/7000/7010/7113 present), so ich_pci_probe never matches and ich_pci_attach never executes -- snd_ich.ko itself loads cleanly (kldstat), proving deployment (guest_check.log). Ceiling on real hardware: local unprivileged UAF read/write through a 0666 cdev, trigger requires an attach-time failure (memory pressure / 33rd sound unit / broken feeder boot) -- hence Low, matching family precedent.
No comments yet.