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

kvsscanf silently accepts unknown conversion specifiers, desynchronizing the varargument list

Field Value
ID DF-2884
Status new
Severity Info
CVSS 3.1 CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:L/A:N
CWE CWE-476 / CWE-838
File sys/kern/subr_scanf.c
Lines 148-253 (fallthrough :283)
Area kern
Confidence certain
Discovered 2026-09-02
Pass 2 (GLM 5.3 second pass)
Bucket base:kern
Reported pending
Known CVE none
CVE match novel

Summary

The conversion switch has no default case: an unsupported specifier (%f/%e/%g/%a/%z/typo) falls through, consumes leading whitespace as a side effect, takes NO varargument, and silently does nothing β€” every subsequent conversion then binds to a shifted argument (%d stores through the pointer meant for %f; a later %s would write through a scalar argument pointer = caller-object overflow hazard). Userland vfscanf rejects unknown conversions; cc's __scanflike rejects such formats at compile time, but runtime-built fmts and wrapper calls reach the defect. No in-tree caller uses an unsupported specifier (21 sites surveyed) β€” hardening. In-kernel: "5" with "%f%d" β†’ ret=1, a=5 (written through &a); patched kernel #1 β†’ ret=0, nothing written. Fix: default: goto match_failure; (validated).

Timeline

  • 2026-09-02 Discovered during pass-2 audit of subr_scanf.c (GLM 5.3); in-kernel argument-misassociation reproduced + fix validated same run.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2884 Β· 12 files
FileTypeDescriptionSize
poc_dfscanf.c β€” 6.4 KB view raw
Makefile β€” 56 B ↓ download
build.sh β€” 167 B view raw
run.sh β€” 219 B view raw
build.log β€” 835 B view raw
run.log β€” 1.1 KB view raw
run.2.log β€” 1.1 KB view raw
run_patched.log β€” 935 B view raw
env.txt β€” 530 B view raw
fix.diff β€” 578 B view raw
VERDICT.md β€” 1.4 KB ↓ raw
verdict.json β€” 3.6 KB view raw
VERDICT.md
↓ download raw

DF-2884 VERDICT β€” REPRODUCED (impact: none β€” hardening)

Root cause

sys/kern/subr_scanf.c:148-253: the conversion-character switch (case '%', '*', l, q, h, 0-9, d, i, o, u, x, s, [, c, p, n) lacks a default: arm. An unrecognized specifier falls through with c set to that character; the code then runs the input-failure check and the whitespace skip (side effects on input), and the second switch at :283-535 (cases CT_CHAR/CT_CCL/CT_STRING/CT_INT only) silently does nothing. No varargument is consumed β†’ all subsequent conversions bind to shifted arguments.

Proof

Stock kernel #0 (E5, through kvsscanf because cc's __scanflike(2,3) on ksscanf rejects "%f%d" outright β€” itself demonstrating the specifier is unsanctioned):

dfpoc: E5 '5' %f%d: ret=1 a=5 b=-1   ← %d stored through &a; &b never written

Patched kernel #1 (fix.diff hunk 1, default: goto match_failure):

dfpoc: E5 '5' %f%d: ret=0 a=-1 b=-1  ← rejected, nothing written

Impact

Argument misassociation: a later %s would write a string through the pointer intended for a scalar conversion (caller-object overflow hazard), later %c/%d write through wrong pointers. Reachable only via runtime-built formats or unannotated wrappers, and no in-tree caller uses an unsupported specifier (21 sites surveyed) β†’ Info (hardening), certain confidence. Userland vfscanf rejects unknown conversions; this aligns the kernel engine.

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

fix.diff hunk 1 (default: goto match_failure) validated on kernel #1: the unknown-specifier format is rejected (ret=0) and no argument is written; all other experiments behave per their own fixes/controls.

['run_patched.log: "dfpoc: E5 \'5\' %f%d: ret=0 a=-1 b=-1"', 'run.log (baseline kernel #0): ret=1 a=5 b=-1', 'fix.diff hunk 1']
↓ fix.diffDragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #1: Wed Sep 2 21:17:17 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64

Confirmed kernel references

Detail

Exploit chain

none in-tree; for a hypothetical caller with a runtime-built fmt containing an unsupported specifier, later %s writes a NUL-terminated string through a non-buffer argument pointer

Evidence (decisive lines)

['run.log: "dfpoc: E5 \'5\' %f%d: ret=1 a=5 b=-1" + RESULT varargument list desynchronized REPRODUCED', 'run.2.log: identical on second load', 'run_patched.log: kernel #1 with fix.diff hunk 1 -- "E5 \'5\' %f%d: ret=0 a=-1 b=-1"', 'build attempt record: cc rejects ksscanf(..., "%f%d", int*, int*) via __scanflike -- %f is not a supported kernel conversion']

PoC changes

authored fresh (no seed): experiment E5 of the shared dfpoc.ko KLD harness; called through an unannotated kvsscanf wrapper because the __scanflike(2,3) annotation on ksscanf rejects '%f' at compile time

Verified recommended fix

add 'default: goto match_failure;' to the conversion switch at sys/kern/subr_scanf.c:252 (fix.diff hunk 1)

Verdict

kvsscanf's conversion switch (sys/kern/subr_scanf.c:148-253) has no default case, so an unsupported specifier (%f/%e/%g/%a/%z/typo) silently falls through: leading whitespace is consumed as a side effect, NO varargument is taken, and every subsequent conversion binds to a shifted argument -- %d then stores through the pointer meant for %f, and a following %s would write a string through a scalar pointer (caller-object overflow hazard). Proven in-kernel on the stock kernel via kvsscanf (E5: ksscanf-style '5' with '%f%d' returns ret=1, a=5, b=-1 -- %d wrote through &a, &b untouched); cc's own __scanflike(2,3) annotation on ksscanf rejects such formats at compile time (the PoC had to bypass it), confirming the specifier is unsanctioned, but runtime-built formats and unannotated wrappers reach the defect. No in-tree caller uses an unsupported specifier (21 sites surveyed) -- Info/hardening. fix.diff hunk 1 (default: goto match_failure, mirroring userland vfscanf) validated on kernel #1: ret=0, nothing written.