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

crom_add_simple_text() includes the 12-byte csrtext header in crc_len: crom_load()/crom_crc() read one quadlet past chunk->data[] (sub-object OOB read for 69..72-char text) and publish a spurious trailing quadlet + IEEE-1212-incorrect length in every broadcast text leaf

Summary

crc_len must count quadlets AFTER the crc quadlet (the directory builder's semantic, re-added as 1+crc_len at load) but the text builder includes the 3 header quadlets - double-counting one: every locally-built text leaf is broadcast with a spurious trailing quadlet (stale data.buf bytes) and a spec-off length; 69..72-char text reads one quadlet past chunk->data (KASAN-visible sub-object over-read whose bytes land in the host's ROM). Userspace model of the production builder: 0xCAFEBABE planted past data[] emerges verbatim in the generated ROM (29 vs 28 quadlets); count-after-crc fix validated (spec-correct, pattern gone).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5498 Β· 12 files
FileTypeDescriptionSize
README.md β€” 1.4 KB ↓ raw
VERDICT.md β€” 3.7 KB ↓ raw
verdict.json β€” 4.5 KB view raw
c5498_user.c β€” 3.7 KB view raw
build.sh β€” 131 B view raw
run.sh β€” 107 B view raw
build.log β€” 195 B view raw
fix_build.log β€” 0 B ↓ download
run.log β€” 797 B view raw
fix_run.log β€” 798 B view raw
env.txt β€” 280 B view raw
fix.diff β€” 395 B view raw

DF-5498 β€” crom_add_simple_text() crc_len includes the header quadlet; crom_load()/crom_crc() read one quadlet past chunk->data[] and publish it in the Config ROM

File: sys/bus/firewire/fwcrom.c β€” pass 2. Userspace-model verification (fwcrom.c's own non-_KERNEL build path, as fwcontrol(8) links it).

Build / run (guest, any user with /usr/src readable)

sh build.sh      # cc -DTEST (pulls in the builders) -Dmain=fwcrom_test_main
sh run.sh        # exit 42 == REPRODUCED

Expected (baseline)

case1 len=72: builder crc_len=21 (data holds 21 quads; 1+crc_len=22 quads copied/CRC'd)
case1: PATTERN FOUND in generated ROM (OOB quadlet published)
case2 len=68 (control): crc_len=20, PATTERN absent
RESULT: REPRODUCED ...

The 4 bytes past chunk->data[] (tail of struct crom_chunk) are read by crom_crc() (fwcrom.c:517-518) and crom_copy() (fwcrom.c:520-522) and copied verbatim into the host's generated Config ROM. Also visible: the published leaf crc_len is off by one against IEEE 1212 at EVERY text length (21 vs 20 for len=72; 20 vs 19 for len=68) β€” every text leaf this host broadcasts carries a spurious trailing quadlet and a spec-incorrect length.

Fix validation

fix.diff (tl->crc_len = howmany(len, 4) + 2) applied to guest /usr/src, model rebuilt: crc_len=20 == IEEE-1212-correct, PATTERN absent, ROM one quadlet shorter per leaf (fix_run.log).

VERDICT.md
↓ download raw

DF-5498 VERDICT β€” REPRODUCED (mechanism certain; Info-grade impact in current layouts)

Root cause

The directory builder counts quadlets after the crc quadlet (crom_add_quad() bumps crc_len per entry, fwcrom.c:394-407), and crom_load() adds the crc quadlet back when emitting (fwcrom.c:520-522, len = 1 + chunk->data.crc_len). The text-leaf builder violates that semantic by including the whole 12-byte struct csrtext header in crc_len:

  • sys/bus/firewire/fwcrom.c:459 β€” tl->crc_len = howmany(sizeof(struct csrtext) + len, sizeof(u_int32_t)); = 3 + howmany(len,4) instead of the correct 2 + howmany(len,4) (spec/type, lang_id, text β€” excluding the crc quadlet).

Consequences, for every text length:

  1. crom_load() copies 1 + crc_len quadlets β€” exactly one quadlet past the chunk's real content β€” and crom_crc() (fwcrom.c:517-518) CRCs the same extra quadlet, so every locally-built text leaf is broadcast with a spurious trailing quadlet (stale/uninitialized bytes of chunk->data.buf[]) and a spec-incorrect crc_len (off by one against IEEE 1212).
  2. For text length 69..72 (MAX_TEXT == 72, fwcrom.c:442), 1 + crc_len reaches 22 quadlets while chunk->data holds 21 (iec13213.h:232-241): a sub-object OOB read of 4 bytes past data[] β€” inside struct crom_chunk's tail padding today (in-object, zeroed by every current caller's M_ZERO/bzero), but a latent out-of-object read if the chunk layout/allocation ever changes, and a KASAN-visible defect.

Kernel callers pass static strings today (firewire.c:624 "DragonFly Project", firewire.c:626 the root-settable hostname, dcons_crom.c:117/ 119 "FreeBSD"/"dcons"); only a 69..72-char hostname reaches the sub-object OOB, so there is no attacker-controlled trigger β€” hence Info severity (hardening/correctness), not Low+.

Reproduction (userspace model, production code)

c5498_user.c + the unmodified production fwcrom.c (compiled via its non-_KERNEL/-DTEST path): a struct crom_chunk whose bytes past data[] (end of data at offset 116 of the struct) are pre-filled with 0xCAFEBABE, 72-char text via production crom_add_simple_text(), then production crom_load():

case1 len=72: builder crc_len=21 ... PATTERN FOUND in generated ROM
               (published leaf crc_len field=21, IEEE-1212-correct=20)
case2 len=68 (control): crc_len=20, PATTERN absent   [exact fit]
RESULT: REPRODUCED: the quadlet past chunk->data[] was read by
        crom_load/crom_crc and published in the Config ROM   (exit 42)

The ROM quadlet stream is 29 vs 28 quadlets (len=72) β€” the extra one is the pattern, i.e. the bytes past data[] verbatim.

Fix validation

fix.diff (tl->crc_len = howmany(len, 4) + 2) applied inside the guest /usr/src, model rebuilt: crc_len=20 == spec-correct for len=72, PATTERN absent, control likewise correct, ROM one quadlet shorter per leaf (fix_run.log).

Impact

Info: no confidentiality/integrity impact in current kernel layouts (the over-read bytes are the zeroed tail padding of a zero-initialized object); correctness/interop impact (spec-incorrect text-leaf lengths + spurious quadlet in every broadcast text leaf β€” visible to any bus peer that parses per IEEE 1212) and a latent OOB read + KASAN defect.

Artifacts

run.log, fix_run.log, build.log, fix_build.log, c5498_user.c, build.sh, run.sh, fix.diff, env.txt.

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

fixed
baseline reproduced→ patch + rebuild →patched clean

Baseline: pattern quadread past chunk->data[] published in the ROM (exit 42), leaf crc_len off by one at every length. Patched (fix.diff on /usr/src, model rebuilt): crc_len == IEEE-1212-correct (20/19), pattern absent in both cases, ROM one quadlet shorter per text leaf.

fix_run.log, fix_build.log, fix.diff
↓ fix.diffper-fix-DF-5498

Confirmed kernel references

Detail

Evidence (decisive lines)

run.log (baseline: crc_len=21/20 vs correct 20/19, PATTERN FOUND in generated ROM, exit 42; control absent), fix_run.log (patched: crc_len==spec-correct, PATTERN absent both cases), build.log/fix_build.log, c5498_user.c, fix.diff

PoC changes

Model written fresh. Bring-up fixes: (1) #include before iec13213.h (its -DTEST builder structs use STAILQ_*); (2) -Dmain=fwcrom_test_main applied only to the fwcrom.c translation unit (a global -D renamed the harness main too and duplicated the symbol); (3) offset arithmetic via offsetof(struct crom_chunk, data)+sizeof(data) so the pre-fill marks exactly the bytes past data[].

Verified recommended fix

tl->crc_len = howmany(len, sizeof(u_int32_t)) + 2; in crom_add_simple_text (fwcrom.c:459) so crc_len counts only the quadlets after the crc quadlet (fix.diff).

Verdict

Reproduced with the production ROM builder compiled for userland (fwcrom.c's own non-_KERNEL/-DTEST path, as fwcontrol(8) links it). crom_add_simple_text() sets tl->crc_len = howmany(sizeof(struct csrtext)+len, 4) = 3+ceil(len/4) (fwcrom.c:459), counting the 12-byte header although crc_len must count only the quadlets after the crc quadlet (the directory builder's semantic, fwcrom.c:394-407) and crom_load() adds the crc quadlet back (fwcrom.c:520-522). crom_load()/crom_crc() therefore always consume one quadlet past the text: every locally-built text leaf is published with a spurious trailing quadlet (stale bytes of chunk->data.buf[]) and a crc_len off by one against IEEE 1212 (demonstrated: leaf header crc_len field=21 where 20 is correct, and 20 where 19 is correct); for text lengths 69..72 (MAX_TEXT=72, fwcrom.c:442) the read reaches one quadlet past chunk->data[] -- a sub-object OOB read of 4 bytes (inside struct crom_chunk's tail padding; KASAN-visible, latent out-of-object read). Model: bytes past data[] pre-filled with 0xCAFEBABE -> after production crom_add_simple_text(72 chars)+crom_load(), the pattern is present verbatim in the generated ROM (29 vs 28 quadlets); 68-char control: exact fit, pattern absent. Impact Info: current kernel callers pass static strings / the root-settable hostname (firewire.c:624-626, dcons_crom.c:117-119), the over-read bytes are zeroed padding in every current layout (no leak), so this is correctness/interop (spec-wrong text leaves broadcast to bus peers) plus a latent hardening defect. Fix validated: with fix.diff (crc_len = howmany(len,4)+2) applied to guest /usr/src, crc_len=20 == spec-correct, pattern absent, ROM one quadlet shorter per leaf.