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

tokenize() writes one pointer per token into the fixed 64-entry static token[] with no bound: kernel static-data overflow β†’ control-flow hijack (kernel executes attacker bytes)

Summary

parse_config passes the static token[MAXTOKEN=64] to tokenize(), which executes token[tokennr]=cptr for every token with no bound check - MAXTOKEN is enforced nowhere. A VINUM_CREATE config line with >64 tokens overwrites kernel .bss with pointers into the attacker's ioctl buffer; the smashed statics include the command_fail jmp_buf, so the parse-error longjmp restores an attacker-smashed RIP. LIVE-REPRODUCED twice from fresh boots: 'Fatal trap 1: privileged instruction fault', RIP inside the kmalloc'd ioctl buffer, 'Stopped at popal' (0x61 = the token byte) - KERNEL EXECUTING ATTACKER-SUPPLIED BYTES. Quoted tokens permit NOP sleds/shellcode. Also reachable from a vinum-labelled disk (vinum read, boot autodetect). Fix validated on rebuilt kernel: PoC returns cleanly, guest stable. Root-gated via 0600 /dev/vinum/control but the crafted-disk vector makes it attacker-reachable.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4132 Β· 8 files
FileTypeDescriptionSize
vinum_tokensmash.c β€” 2.7 KB view raw
vinum_codeexec.c β€” 1.8 KB view raw
panic.txt β€” 1.4 KB view raw
serial_full_crash1.txt β€” 2.3 KB view raw
run.log β€” 1.1 KB view raw
fix.diff β€” 2.0 KB view raw
verdict.json β€” 3.7 KB view raw
env.txt β€” 388 B view raw

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

Applied fix.diff to guest /usr/src, rebuilt kernel (#1 Fri Sep 18 04:22) + vinum.ko, rebooted, re-ran ./vinum_tokensmash 500: tokenize returns -1, reply.error=-1, guest stays up (run.log). Bad behavior gone.

run.log fixed section; build.log (BUILD_RC=0); dmesg clean after run.
↓ fix.diffper-fix-DF-4132

Confirmed kernel references

Detail

Exploit chain

VINUM_CREATE ioctl (0xC4004640) with 500-token line -> sys_ioctl kmalloc(1024)+copyin -> parse_user_config -> parse_config -> tokenize writes token[64..499] past the 512-byte static array (~3.5KB of .bss smashed with heap pointers) -> command_fail jmp_buf saved RIP/RSP replaced by token pointers -> config error -> throw_rude_remark -> longjmp(command_fail) -> kernel jumps into the attacker buffer -> executes token bytes (0x61='a'=POPAL observed; quoted tokens allow arbitrary opcodes incl. NOP sleds). Escape/impact ceiling: arbitrary kernel code execution for anyone with /dev/vinum/control (0600 root:wheel) or with their media read by vinum.

Evidence (decisive lines)

panic.txt (two crash signatures), serial_full_crash1.txt, run.log (baseline panic vs fixed-kernel clean return), fix.diff, vinum_tokensmash.c + vinum_codeexec.c

PoC changes

Added lowercase /dev/vinum/control fallback (non-debug kernels reject the uppercase Control node); computed VINUM_CREATE via _IOC macros; staged token counts for reach control.

Verified recommended fix

Pass the token array size to tokenize() and return -1 instead of writing past it (fix.diff: signature + bound check + call site).

Verdict

tokenize() (vinumparser.c:163) writes one pointer per token into the caller's fixed 64-entry static array token[MAXTOKEN] (vinumconfig.c:78) with no bound check, via parse_config (vinumconfig.c:1627). A VINUM_CREATE ioctl line with >64 tokens overwrites kernel static data with pointers into the attacker's ioctl buffer; on the tested build the smashed statics include the command_fail jmp_buf, so the parse-error longjmp restores an attacker-smashed RIP and the kernel EXECUTES attacker-supplied bytes (demonstrated twice: Fatal trap 1, RIP inside the kmalloc'd ioctl buffer, executing 0x61 'a' token bytes decoded as popal). Same parser runs on config text read verbatim from vinum-labelled disks (vinum read / vinum.drives autodetect), so malicious media reaches the identical overflow. Root/operator-gated on the ioctl path; malicious-media vector for the disk path. Fix (bound the write by the caller's array size) validated on a rebuilt kernel: identical PoC returns cleanly, no overflow, guest stable.