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

Use-after-free race between acpiioctl() hook dispatch and acpi_deregister_ioctl()

Summary

acpiioctl() captures pointer to registered ioctl hook (struct acpi_ioctl_hook *hp) while holding ACPI_LOCK(acpi) then releases ACPI_LOCK and invokes hp->fn(...) with only lwkt_token acpi_token held. acpi_deregister_ioctl() takes only ACPI_LOCK (not the token) removes hook from list and kfree()s hook struct. If deregister runs in window between acpiioctl ACPI_UNLOCK and hp->fn() call function pointer dereferenced through freed memory. lwkt_token does not help because deregister never acquires it. Requires kldunload of ACPI sub-driver during concurrent ioctl. Impact kernel UAF panic DoS or with heap grooming indirect call to attacker-chosen address. [VERIFIED pass2: race window source-proven dispatch drops ACPI_LOCK before hp-fn and deregister takes neither token nor lock, but 1.6M dispatches x 5175 dereg windows closed zero hits; token-serialize fix compile-validated]

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2374 Β· 7 files
FileTypeDescriptionSize
README.md β€” 767 B ↓ raw
VERDICT.md β€” 1.4 KB ↓ raw
env.txt β€” 145 B view raw
fix.diff β€” 514 B view raw
race.log β€” 556 B view raw
run.sh β€” 121 B view raw
verdict.json β€” 1.6 KB view raw

DF-2374 - ioctl hook UAF race between dispatch and deregister

Rig (ACHARNESS, op=hook_reg / hook_churn_start + hook_racer.c)

Kernel thread loops acpi_deregister_ioctl(0xDF740001) -> kmalloc(32) canary (0x4141414141414141 fn) -> kfree -> acpi_register_ioctl; userland spams ioctl(0xDF740001) on /dev/acpi for 90s.

Result (race.log): NOT closed in 5175 windows x 1.6M dispatches

The window (unlock -> hp->fn deref) is a few instructions; immediate re-registration reuses the same chunk and restores a valid fn before any dangling dispatcher dereferences it.

Fix (fix_2374.diff, compile-validated in the fixed ACHARNESS build)

acpi_deregister_ioctl takes acpi_token around unlink+free, excluding any in-flight dispatch that holds the token across hp->fn().

VERDICT.md
↓ download raw

DF-2374 VERDICT

Status: not_reproduced Impact: corruption Confidence: likely Attempts: 3

Race window is real by construction (acpiioctl drops ACPI_LOCK at acpi.c:3421 and calls hp->fn at :3426 holding only acpi_token, while acpi_deregister_ioctl at :3247-3260 takes neither) but could not be closed empirically: 1,607,354 hook dispatches against 5,175 deregister+canary-refill+reregister churn cycles produced zero faults. The ~10-instruction window plus immediate re-registration reusing the same 32-byte chunk (fn pointer rewritten to the valid function) makes a black-box hit a lottery; exploitation would need precise heap choreography.

Kernel references

Guest

DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #7: Sat Oct 24 00:08:04 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/ACHARNESS x86_64

Fix validation

See fix.diff and README.md for the baseline/fixed A/B outputs (run.log / fixed.log).

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_testable
↓ fix.diffper-fix-DF-2374

Confirmed kernel references

Detail

Evidence (decisive lines)

race.log (hook_racer: 1607354 ioctls in 90s; churn stopped: calls=1607354 regs=5175 deregs=5175, no panic)

PoC changes

rig ops hook_reg/hook_churn_start/hook_churn_stop + userland racer written for this batch

Verified recommended fix

see fix.diff

Verdict

Race window is real by construction (acpiioctl drops ACPI_LOCK at acpi.c:3421 and calls hp->fn at :3426 holding only acpi_token, while acpi_deregister_ioctl at :3247-3260 takes neither) but could not be closed empirically: 1,607,354 hook dispatches against 5,175 deregister+canary-refill+reregister churn cycles produced zero faults. The ~10-instruction window plus immediate re-registration reusing the same 32-byte chunk (fn pointer rewritten to the valid function) makes a black-box hit a lottery; exploitation would need precise heap choreography.