dcons_load_buffer() trusts wire-supplied buffer geometry (sizes, ooffset/ioffset, 24-bit pos) with an overflowable sum-only sanity check β latent arbitrary-displacement OOB read/write primitive in dead-but-exported module code
Summary
Loads osize/isize, sets buf pointers with free-form u32 displacement, accepts pos up to 0xFFFFFF, guarded only by an int accumulation that wraps (osize=isize=0x80000000 -> addend 0) - offsets and pos never bounded. Dead code today (zero callers; live path uses dcons_init) but it is the design-intent entry for adopting a peer-initialized buffer: any future rewiring turns FireWire DMA writes into arbitrary-displacement kernel R/W. 64-bit-bounded validation fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5147 Β· 5 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 3.1 KB | β raw | |
| VERDICT.md | β | 4.1 KB | β raw | |
| fix.diff | β | 1.6 KB | view raw | |
| verdict.json | β | 3.8 KB | view raw | |
| manifest.json | β | 1.2 KB | view raw |
DF-5147 β dcons_load_buffer() trusts wire-supplied buffer geometry with an overflowable sanity check (latent, dead code in-tree)
- File:
sys/dev/misc/dcons/dcons.c:157-191 - Severity: Info (latent β no in-tree caller)
- Confidence: certain (code defect), reachability: none in DragonFly tree today
What the code does
dcons_load_buffer(buf, size, sc) adopts an externally-initialized dcons buffer
(the header fields are network-order words in the FireWire-shared buffer, i.e.
data a peer can DMA-write). It loads, with no bounds validation:
dc->o.size / dc->i.sizefrombuf->osize[]/isize[](dcons.c:169-170)dc->o.buf / dc->i.buf=(char *)buf + ntohl(buf->ooffset[]/ioffset[])(dcons.c:171-172) β free-form u32 displacement frombufdc->o.pos / dc->i.pos= wire ptr word &DCONS_POS_MASK(24-bit) (dcons.c:175-176)
The only sanity check (dcons.c:184-186):
s = DCONS_HEADER_SIZE;
for (port ...) { ...; s += dc->o.size + dc->i.size; }
if (s > size) return (-1);
fails to constrain the geometry:
- Integer wrap:
sisint,dc->o.size + dc->i.sizeis u32 arithmetic.osize[0]=isize[0]=0x80000000(and likewise for port 1) makes every per-port addend 0 βsstays ~56 β passes while each channel claims a 2 GB size. - Offsets never checked:
ooffset/ioffsetcan be any u32 (up to +4 GB frombuf); the check sums only sizes. - pos never checked:
posup to 0xFFFFFF is accepted against whatever size was loaded.
Consequence if ever wired to a peer-controlled buffer
dcons_putc()writesch->buf[ch->pos] = c(dcons.c:118) β single-byte kernel write at peer-chosen displacement (buf + u32 offset + 24-bit pos), content = console output stream; pos auto-increments β linear scribing.dcons_checkc()readsch->buf[ch->pos](dcons.c:101) and the byte is delivered to the console/gdb reader β kernel memory disclosure to the peer (same sink as the known, live DF-2142 read bug, but reachable via baked-in geometry rather than the live wire ptr word).
Why Info / no PoC
rg over the whole sys/ tree finds no caller of dcons_load_buffer
(only the prototype at dcons.h:105 and the definition). The live attach path
(dcons_os.c:453/464) always uses dcons_init(), which computes all geometry
from the fixed DCONS_BUF_SIZE (16 KB). The function is, however, compiled
into the loadable dcons module and globally exported, and the family's
threat model (FireWire peers DMA-writing the published buffer β DF-2142,
DF-5116..5118) makes it a live trap if any future change re-wires it.
Per the audit contract, Low/Info hardening findings are not PoC-verified; this pack records the defect and the hardening fix only.
Reproduce (analysis-level)
Read-only; the decisive references are:
- sys/dev/misc/dcons/dcons.c:169-186 (unvalidated loads + wraparound-able check)
- sys/dev/misc/dcons/dcons.c:101 / :118 (read/write sinks at ch->buf[ch->pos])
- caller search: rg -n 'dcons_load_buffer' sys/ β only dcons.c:158 and dcons.h:105
Fix
Apply fix.diff in this directory (bounds + 64-bit accumulation + pos<size).
VERDICT β DF-5147
Status: untested (Info-severity hardening finding β PoC skipped per audit contract; no Critical/High actionability, no in-tree reachability).
Narrative
Pass-2 deep audit of sys/dev/misc/dcons/dcons.c (beyond known DF-2142 wire-pos
OOB read and the DF-4781/4782 dcons_os siblings) established:
-
The live write path cannot be poisoned by a FireWire peer.
dc->o.{size,pos,buf}are set only bydcons_init_port()(dcons.c:138-141) from kernel-computed values derived from the fixedDCONS_BUF_SIZE= 16 KB (dcons_os.c:75-77);optr[]is written only by the kernel viaDCONS_MAKE_PTR(dcons.c:124); the wire-controlled resync branches (dcons.c:67-68, 96-98) touch onlydc->i. Hence the output ring cursor is confined to[0, o.size)anddcons_putc's store atch->buf[ch->pos](dcons.c:118) is always in-bounds on every live path. The known DF-2142 covers the input-side OOB read. -
dcons_load_buffer()(dcons.c:157-191) is a certain latent memory-safety defect. It adopts wire-format geometry (sizes, offsets, generation and 24-bit positions) from the shared buffer header into the softc with a single "sanity check" that (a) never boundsooffset/ioffsetagainstsize(dcons.c:171-172), (b) never checkspos < size(dcons.c:175-176), and (c) accumulatess += dc->o.size + dc->i.size(dcons.c:181) asintover u32 addends that wrap β e.g.osize=isize=0x80000000for both ports yields addend 0 and the check passes with 2 GB channel sizes. If any code ever calls it on a peer-writable buffer,dcons_putc(dcons.c:118) becomes a byte-wise kernel write at peer-chosen displacement (buf + u32 offset + 24-bit pos) anddcons_checkc(dcons.c:101) a peer-steered kernel read whose result is delivered to the console/gdb reader. -
Reachability today: none.
rg -n 'dcons_load_buffer' sys/finds only the definition (dcons.c:158) and the prototype (dcons.h:105). The DragonFly attach path always callsdcons_init()(dcons_os.c:453, 464). The function is still compiled into thedconsmodule and exported, one re-wiring away from the primitive above.
Because the defect is real at the code level but unreachable in this tree, it
is filed as Info (hardening / latent OOB), confidence certain about the
defect itself, and no guest PoC was run (nothing observable β dead code).
fix.diff hardens the loader: 64-bit accumulation, offset+size bounds, and
pos < size per channel.
Classes considered and killed (full-file sweep)
- OOB write via wire on
dc->o: impossible live (see 1). dcons_ischar(dcons.c:47-73): dead code (no callers), identical resync defect to DF-2142 β already recorded in DF-2142's summary; not re-reported.- Unlocked concurrent
dcons_putc(console vs tty drain): unsynchronizedch->pos++can drop/duplicate console bytes; both racers storeN+1(neverN+2), aligned u32*ch->ptrstore is atomic β console-stream corruption only, no memory-safety consequence. dcons_init/dcons_init_portarithmetic (dcons.c:136, 199-204): fixed 16 KB input, port regions exactly tile[DCONS_HEADER_SIZE, size), no overflow.- Info leak via shared header: kernel stores only offsets/sizes/ptr-words in
struct dcons_buf; no kernel pointers published from dcons.c (paddr publication is dcons_crom.c, tracked as DF-5116..5118). dcons_checkcwrap logic (dcons.c:102-106):pos++then>= sizewrap β no off-by-one;buf[size]never touched on kernel-maintained cursors.
Fix validation
not_testable β latent dead-code defect; the fix is hardening validated by inspection. No baseline misbehavior exists to reproduce and no guest run was performed for this finding (guest untouched).
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_testablenot_testable: latent dead-code defect, no baseline misbehavior exists to observe on any in-tree path; fix.diff is inspection-validated hardening (bounds + uint64 accumulation + pos<size), never applied to the read-only sys/ tree.
findings/poc/DF-5147/fix.diff; caller search 'rg -n dcons_load_buffer sys/' -> only sys/dev/misc/dcons/dcons.c:158 and sys/dev/misc/dcons/dcons.h:105.
Confirmed kernel references
- sys/dev/misc/dcons/dcons.c:158
- sys/dev/misc/dcons/dcons.c:169-176
- sys/dev/misc/dcons/dcons.c:181
- sys/dev/misc/dcons/dcons.c:184-186
- sys/dev/misc/dcons/dcons.c:101
- sys/dev/misc/dcons/dcons.c:118
- sys/dev/misc/dcons/dcons.c:138-141
- sys/dev/misc/dcons/dcons.c:124
- sys/dev/misc/dcons/dcons_os.c:453
- sys/dev/misc/dcons/dcons_os.c:464
Detail
Evidence (decisive lines)
findings/poc/DF-5147/README.md (defect mechanics + why Info); findings/poc/DF-5147/VERDICT.md (full narrative incl. write-side live-path kill and caller search); findings/poc/DF-5147/fix.diff (hardening). Decisive lines: sys/dev/misc/dcons/dcons.c:169-176 (unvalidated wire loads), :181 (wrapping accumulation), :184-186 (sum-only check), :101/:118 (read/write sinks), dcons_os.c:453+464 (live path uses dcons_init instead).
PoC changes
none - no PoC authored; Info-severity hardening finding (dead code in-tree), guest not used
Verified recommended fix
Harden dcons_load_buffer: validate ooffset/ioffset+size against the buffer size with 64-bit arithmetic and require pos < size per channel (see findings/poc/DF-5147/fix.diff).
Verdict
Certain latent memory-safety defect with no in-tree reachability: dcons_load_buffer() (sys/dev/misc/dcons/dcons.c:157-191) bakes wire-supplied osize/isize/ooffset/ioffset and 24-bit positions into the dcons softc with a sanity check that never bounds offsets or pos and whose 'int s' accumulation of u32 size pairs wraps (dcons.c:181, e.g. osize=isize=0x80000000 -> addend 0, check passes). If any future caller passes a FireWire-peer-writable buffer (the function's design intent), dcons_putc (dcons.c:118) becomes a byte-wise kernel write at peer-chosen displacement (buf + u32 offset + 24-bit pos, content = console stream) and dcons_checkc (dcons.c:101) a peer-steered kernel read delivered to the console/gdb reader. rg over sys/ shows zero callers today (only dcons.c:158 and dcons.h:105); the live path always uses dcons_init() (dcons_os.c:453,464) whose geometry is kernel-computed from the fixed 16KB DCONS_BUF_SIZE, and the live write channel dc->o cannot be poisoned by the wire (resync at dcons.c:67/96 touches only dc->i; optr is kernel-written only at dcons.c:124). Filed as Info hardening; PoC skipped per contract for Info severity - there is no observable misbehavior on any reachable path. fix.diff validates geometry with 64-bit math, offset+size bounds and pos<size per channel.
No comments yet.