sccngetch(): 8-byte size_t store through (size_t *)&fkeycp pun writes 4 bytes past a kernel static; unclamped bcopy into the 17-byte static fkey trusts the DF-1503-corruptible fkeytab length
Summary
The kernel-console input path keeps two function-scope statics, struct fkeytab fkey (17 bytes) and int fkeycp (4 bytes), and passes (size_t *)&fkeycp to kbd_get_fkeystr(). genkbd_get_fkeystr performs *len = kb_fkeytab[fkey].len - an 8-byte size_t store into the 4-byte int: 4 bytes are written past the object on every console function-key read. The shipped kernel binary proves the store survives codegen (cross-TU): genkbd_get_fkeystr+0x4c is 'mov %rax,(%r12)'. The same code then does fkey.len = fkeycp (int->u_char truncation) and bcopy(p, fkey.str, fkey.len) with NO clamp to sizeof(fkey.str)==16; since kb_fkeytab[].len is corruptible to arbitrary u_char values via SETFKEY (DF-1503), this sink becomes a static-buffer overflow of up to 238 bytes past fkey into adjacent kernel .bss. On the audited build the pun's stray bytes land in the 12-byte alignment gap before the next symbol, so impact there is latent; the victim depends purely on link layout. VERIFIED: objdump of the shipped kernel (both code sites) + canary-model harness on the guest (4 OOB bytes zeroed; 239 bytes past the 17-byte model with len=255; gcc warns). Trigger: Ctrl-Alt-Esc enters DDB, function key at the db> prompt; or a console user corrupts a fkeytab len via SETFKEY on an inherited fd. Panic-on-next-use ceiling; DDB/boot context makes weaponization marginal.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3747 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| pun_harness.c | β | 4.2 KB | view raw | |
| build.sh | β | 65 B | view raw | |
| run.sh | β | 89 B | view raw | |
| build.log | β | 368 B | view raw | |
| run.log | β | 1.2 KB | view raw | |
| disasm.txt | β | 1.3 KB | view raw | |
| env.txt | β | 506 B | view raw | |
| fix.diff | β | 835 B | view raw | |
| VERDICT.md | β | 5.4 KB | β raw | |
| verdict.json | β | 4.0 KB | view raw |
DF-3747 β VERDICT
Status: inconclusive at runtime (kernel path untriggerable on this guest); defect, codegen and layout proven certain from the shipped kernel binary + executed userland replica.
The defect (source)
sys/dev/misc/syscons/syscons.c:2004-2005 declares two function-scope
statics:
static struct fkeytab fkey; /* { u_char str[16]; u_char len; } == 17 bytes */
static int fkeycp; /* 4 bytes */
At syscons.c:2054 the console input path passes a type-punned pointer:
p = kbd_get_fkeystr(scp->sc->kbd, KEYCHAR(c), (size_t *)&fkeycp);
kbd_get_fkeystr dispatches to genkbd_get_fkeystr()
(sys/dev/misc/kbd/kbd.c:1110-1127), which executes
*len = kbd->kb_fkeytab[fkey].len; /* kbd.c:1123 β size_t store */
an 8-byte size_t store into a 4-byte object β 4 bytes written past
fkeycp (upper half always 0, since the value is a zero-extended u_char).
Then syscons.c:2055-2057:
fkey.len = fkeycp; /* int -> u_char truncation */
if ((p != NULL) && (fkey.len > 0))
bcopy(p, fkey.str, fkey.len); /* length never clamped to 16 */
The copy length is kb_fkeytab[].len β an untrusted u_char. DF-1503
(pass-1, kbd.c) proved SETFKEY can leave len set to arbitrary values up
to 255 (imin(-1, MAXFK) wraps into a u_char). Through this sink that
corrupted length drives bcopy up to 255 bytes into the 17-byte static
fkey.13184 β up to 238 bytes of attacker-influenced overflow into
adjacent kernel .bss statics of syscons.ko-adjacent objects. Even
without DF-1503, the pun alone is a genuine OOB write whose victim depends
purely on link layout.
Binary proof (guest /boot/kernel/kernel, objdump/nm)
- Layout:
fkeycp.13185 @ 0xffffffff817a6da0; the next symbol isfkey.13184 @ 0xffffffff817a6db0(+0x10). The pun's stray 4 bytes ([0xβ¦6da4, 0xβ¦6da8)) therefore land in the 12-byte alignment gap on this build β no functional corruption on this exact link, but the write is outside any C object. genkbd_get_fkeystr+0x4c(0xffffffff80b7c10c):mov %rax,(%r12)β the 8-byte store through the pun is really emitted (the pun crosses translation units, so GCC cannot shrink it; an in-TU replica IS shrunk to 32 bits, which the harness initially reproduced and then defeated withvolatile).sccngetch+0x228(0xffffffff80b8e2a8):mov fkeycp.13185,%ecx ; movzbl %cl,%edx ; mov %cl,fkey.13184+0x10 ; β¦ callq memmove(&fkey.str, p, len)β the unclamped copy into the 17-byte static is a realmemmovecall with the untrusted length.
Harness (executed on the guest, build.log / run.log)
pun_harness.c models the exact layout (int @+0, gap, 17-byte struct,
canary guards) and performs both stores verbatim:
* [1] the 8-byte store zeroes &fkeycp+4..+8 (the OOB bytes) β shown as
[4]=00 [5]=00 [6]=00 [7]=00;
* [2] with a corrupted len=255 (DF-1503 style), 239 bytes are written past
the model of fkey into the guard region. GCC flags the replicated copy
itself: warning: 'memcpy' forming offset [101, 303] is out-of-bounds.
Why not reproduced
The trigger is the kernel-console cngetc path: a function-key press
read through the kernel console (DDB db> prompt via Ctrl-Alt-Esc, boot
"press any key", panic/dump prompts). On the audit guest:
- the active console driver is
sio0(serial);sc0is attached but itscngetc/cncheckcare never dispatched (dmesg: "sio0: console"), - there is no physical keyboard (headless QEMU), and
machdep.enable_panic_key=0.
So the kernel sink cannot be exercised at runtime on this guest β evidence
is binary-level certainty + the executed replica (same convention as
DF-3746). On a desktop DragonFly install where sc is the console, any
physical-console user can enter DDB (Ctrl-Alt-Esc, no privilege required)
and press F1 to fire the pun store; overflowing fkey additionally
requires the DF-1503 corruption (console-logged-in user via SETFKEY on the
inherited tty fd).
Exploitability assessment
- Standalone pun: 4 zero bytes at a fixed link-layout-dependent offset, DDB/boot context only. On the audited build: padding. Low.
- With DF-1503: up to 238 attacker-chosen bytes past a kernel .bss static
(
fkey.13184), DDB/boot context. Corrupting adjacent syscons statics (e.g.sc_console,cctl_dev,scrn_blank_time) yields at best panic-on-next-use on this no-KASLR guest; context (debugger prompt) makes weaponization marginal. Low overall β the fix is cheap defense-in-depth and DF-1503's own fix removes the length corruption.
Fix
fix.diff (never applied to sys/): declare static size_t fkeycp and
clamp fkeycp to sizeof(fkey.str) before the copy. Compiles by
inspection; not kernel-build-validated because the path cannot be
exercised on this guest β fix_status: not_testable.
Cross-file note
The store lives in kbd.c (already covered by DF-1503/DF-1504 territory); this finding is filed against syscons.c for the pun creation and the unclamped sink, both of which are in this file.
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 authored after binary-level verification (size_t fkeycp + clamp); cannot be kernel-build-validated on this guest because the sink (kernel-console cngetc with a function key) cannot be exercised at runtime (active console is sio0, no keyboard, enable_panic_key=0)
fix.diff (unified, against read-only sys/ tree; never applied)
Confirmed kernel references
Detail
Exploit chain
physical-console user enters DDB via Ctrl-Alt-Esc (no privilege) and presses a function key -> genkbd_get_fkeystr 8-byte store at &fkeycp (4 OOB zero bytes, layout-dependent victim); combined with DF-1503 (SETFKEY-corrupted len=255 via inherited tty fd): bcopy of up to 255 attacker bytes into static fkey.str[16] -> up to 238-byte overflow of adjacent kernel .bss statics -> best-case panic-on-next-use; weaponization marginal given DDB/boot context
Evidence (decisive lines)
disasm.txt (objdump of genkbd_get_fkeystr+0x4c and sccngetch+0x228 from the shipped kernel), run.log (nm layout fkeycp.13185/fkey.13184 + harness sections [1] OOB-zero bytes and [2] 239-byte overflow), build.log (GCC -Warray-bounds on the replica), VERDICT.md
PoC changes
no seed PoC; harness written from scratch: canary-guarded static-layout model of sccngetch's statics performing the exact stores. Initial pun demo was optimized to 32 bits in-TU (matching no byte changes); fixed with a volatile pun to reproduce the kernel's cross-TU 8-byte codegen, and fixed the [1] probe to scan &fkeycp+4..+8
Verified recommended fix
make fkeycp a size_t and clamp the fkeytab length to sizeof(fkey.str) before bcopy (fix.diff)
Verdict
sccngetch() (sys/dev/misc/syscons/syscons.c:2004-2057) passes (size_t *)&fkeycp to kbd_get_fkeystr; genkbd_get_fkeystr (kbd.c:1123) executes an 8-byte size_t store into the 4-byte static int β a genuine 4-byte OOB write proven emitted in the shipped kernel binary (genkbd_get_fkeystr+0x4c 'mov %rax,(%r12)'; nm: fkeycp.13185 @0xffffffff817a6da0, next symbol fkey.13184 @+0x10, so the stray bytes land in alignment padding on this build). The same code then copies kb_fkeytab[].len bytes into the 17-byte static fkey without clamping (sccngetch+0x228 'callq memmove(&fkey.str,p,len)') β with DF-1503's corruptible len (up to 255) that is up to 238 bytes of overflow past a kernel .bss static. Runtime path is the kernel-console cngetc function-key read (DDB/boot prompt); the guest's active console is sio0 (serial) with no keyboard, so the kernel sink cannot be triggered here β defect/codegen/layout are instead proven from /boot/kernel/kernel plus an executed verbatim userland replica (build.log/run.log; GCC itself flags the replicated OOB copy).
No comments yet.