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

probeCard() consumes uninitialized stack eeprom[256] when readEEProm() fails β€” uninitialized stack bytes reach console/dmesg and drive card identification (CWE-457)

Summary

probeCard declares eeprom[256] on the kernel stack; readEEProm fills it incrementally over i2c and returns -1 on the first NAK (possibly before writing anything). All four call sites ignore the return, then derive identity from and print the uninitialized bytes (subsystem ids at bootverbose AND unconditional 'card vendor unknown' prints; eeprom[0..3] unconditionally printed on the Bt848 path; the Hauppauge re-parse reads block_1 of the same never-written buffer, feeding DF-1569's walk with uninitialized offsets). Flaky-EEPROM card -> stack bytes disclosed to dmesg on every probe + nondeterministic misidentification. Same pattern/ceiling as DF-2092. memset-zero fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4377 Β· 11 files
FileTypeDescriptionSize
README.md β€” 2.9 KB ↓ raw
VERDICT.md β€” 4.4 KB ↓ raw
chk_bktr.c β€” 2.4 KB view raw
build.sh β€” 80 B view raw
run.sh β€” 91 B view raw
build.log β€” 446 B view raw
run.log β€” 446 B view raw
env.txt β€” 497 B view raw
fix.diff β€” 502 B view raw
manifest.json β€” 1.1 KB view raw
verdict.json β€” 2.8 KB view raw

DF-4377 β€” bktr_card.c probeCard() consumes uninitialized stack eeprom[256] when readEEProm() fails; uninitialized bytes reach console/dmesg

  • File: sys/dev/video/bktr/bktr_card.c
  • Class: CWE-457 (use of uninitialized memory) β†’ kernel stack info disclosure to console/dmesg (kernleak, Low)
  • Pass: 2 (GLM 5.3 re-audit)
  • Verdict: not_reproduced β€” hardware-gated; the audit QEMU guest has no Bt848/878 card, so /dev/bktr* does not exist and the probe path cannot run (see run.log). The code defect itself is certain from source.

What a maintainer needs to know

probeCard() declares u_char eeprom[256] on the kernel stack (bktr_card.c:605) and passes it to readEEProm() ignoring the return value at four sites (:667, :794, :1001, :1129). readEEProm() fills the buffer incrementally over the i2c bus and returns -1 on the first NAK β€” everything past the failure point (or the whole array) stays uninitialized. probeCard then derives identity data from those bytes and prints them:

  • :668-674 β†’ subsystem_id/subsystem_vendor_id from eeprom[252..255];
  • :676-678 β†’ bootverbose kprintf("subsystem 0x%04x 0x%04x");
  • :757-758 β†’ unconditional kprintf("card vendor 0x%04x (model 0x%04x) unknown") whenever the (garbage) vendor id is not in the table;
  • :794-815 β†’ Bt848 path: eeprom[0..3] compared, then unconditionally printed (kprintf("%s: %x %x %x %x"), :814-815) when unrecognized;
  • :1001/:1004-1015 β†’ Hauppauge re-parse reads block_1[1..15] of the same buffer (feeding the DF-1569 block walk with uninitialized offsets).

Impact: a handful of uninitialized kernel-stack bytes are disclosed to the console and dmesg on every probe with a flaky EEPROM, and the card is misidentified nondeterministically. Requires a Bt848/878 card whose EEPROM ACKs the probe read (locate_eeprom_address succeeds) but NAKs the sequential data read β€” plausible hardware behavior (that is exactly the failure readEEProm is written to detect).

Not re-reported (pass-1/known, filed from the ioctl side or sibling files): DF-1569 (block_2/3 OOB), DF-3785 (BT848_REEPROM negative-offset heap overflow), DF-3786 (BT848_SIGNATURE unbounded count), DF-1217 (raw I2C access), DF-4361 (select_tuner bound), DF-2092 (same pattern in cxm_eeprom.c).

Build / Run / Expected

build.sh   # on the DragonFly guest: cc -o chk_bktr chk_bktr.c
run.sh     # ./chk_bktr

Expected on hardware: probe output visible in dmesg; the uninit disclosure manifests as the "subsystem 0x… / card vendor 0x… unknown / %x %x %x %x" lines carrying stack garbage when the EEPROM read fails mid-way.

Observed on this guest (run.log): DF-4377: /dev/bktr0 not present β†’ EXIT=2 (target device absent β€” probe cannot run).

Fix

fix.diff β€” zero-initialize eeprom[] in probeCard() so a failed EEPROM read yields deterministic (zero) vendor/model IDs instead of stack bytes.

VERDICT.md
↓ download raw

DF-4377 VERDICT β€” not_reproduced (hardware-gated), defect certain from source

Reproduced?

No β€” the target hardware does not exist on the audit guest. chk_bktr.c compiled and ran on the guest (BUILD_OK_4377, run.log); the open of /dev/bktr0 fails with ENOENT because the QEMU guest has no Brooktree PCI device and the bktr driver is not attached (pciconf -l | grep -c brooktree β†’ 0, no /dev/bktr* /dev/tuner* /dev/vbi* nodes). The vulnerable code only executes inside probeCard() during device attach (bktr_core.c:505) or on the first open after a hw.bt848.card/hw.bt848.tuner sysctl change (bktr_os.c:463/473) β€” i.e. only on a machine with a real Bt848/Bt878 capture card.

Why the defect is certain anyway (path:line)

  1. sys/dev/video/bktr/bktr_card.c:605 β€” u_char eeprom[256]; kernel-stack array, not initialized.
  2. bktr_card.c:420-448 β€” readEEProm() writes data[x] only for bytes whose i2c read succeeded; on the first i2cRead() == -1 it returns -1 with the buffer partially (or entirely) uninitialized. It can also fail at the bounds check (:433-434) or the address-set write (:437) before touching the buffer at all β€” in those cases nothing is initialized.
  3. Every call site in probeCard() ignores the return value: - :667 readEEProm(bktr, 0, 256, (u_char *)&eeprom); (Bt878/879 path) - :794 readEEProm(bktr, 0, 128, ...) (Bt848 Hauppauge/Osprey path) - :1001 readEEProm(bktr, 0, 128, ...) (Hauppauge tuner re-parse) - :1129 readEEProm(bktr, 0, 128, ...) (AVerMedia re-parse)
  4. Uninitialized bytes are then consumed and printed: - :668-674 subsystem_id/subsystem_vendor_id from eeprom[252..255]; - :676-678 bootverbose print of both; - :757-758 unconditional "Warning - card vendor 0x%04x (model 0x%04x) unknown" for any garbage vendor id; - :797/:805 eeprom[0..3] compared, :812-815 unconditional print of the four bytes when unrecognized; - :1004-1019 block_1[1..15] of the (possibly never-written) buffer drive the Hauppauge block walk β€” with DF-1569's missing bounds this turns uninitialized stack into wild offsets (that specific OOB remains DF-1569's scope; the uninitialized source is this finding).

Trigger condition on hardware: locate_eeprom_address() (:525-530) must see an ACK at 0xa0/0xac/0xae while the subsequent sequential read NAKs β€” i.e. exactly the "EEPROM present but read fails" case the helper models. Then :667 returns early, and :668-674/:676/:757 print uninitialized stack. On the :794 path the probe read at :788 (i2cRead(PFC8582_RADDR)) succeeded, so a mid-readEEProm failure leaves eeprom[0..3] uninitialized and :814-815 prints them.

Impact assessment

  • Kernel-stack disclosure: 4-16 uninitialized bytes per probe printed to console/dmesg (readable by unprivileged users unless dmesg is restricted). Same class and ceiling as DF-2092 (cxm_eeprom.c), which was triaged Low.
  • Secondary: nondeterministic card misidentification β†’ wrong tuner/audio config (correctness, not a memory-safety primitive).
  • No userland trigger exists for the uninit path itself: the BT848_REEPROM ioctl copies the argument through a zeroed kernel buffer (mapped_ioctl, sys/kern/sys_generic.c:674-697, bzero's IOC_OUT), so the ioctl-driven variants of readEEProm cannot disclose stack; only the probe-time kprintfs do.

Exploit chain

None β€” information disclosure of a few stack bytes; not a corruption primitive.

Fix validation

fix.diff (zero-init eeprom[] in probeCard) is authored against the read-only sys/ tree and not applied; fix validation is not_testable on this guest (no hardware to observe the disclosure before/after). The change is trivially reviewable: it makes a failed read produce vendor id 0x0000 (prints as "vendor 0x0000 unknown") instead of stack garbage.

Guest evidence

  • run.log: uname, BUILD_OK, /dev/bktr0 not present, EXIT=2, no bktr/tuner/vbi nodes, pciconf -l | grep -ci brooktree = 0.
  • env.txt: guest identity + device-absence summary.

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_testable
baseline no→ patch + rebuild →patched clean

fix.diff authored (memset of eeprom[] in probeCard, read-only tree, never applied); cannot build/validate on the guest - no bktr hardware to observe the disclosure on a baseline kernel.

fix.diff in this pack; guest device-absence evidence in run.log/env.txt.
↓ fix.diffper-fix-DF-4377

Confirmed kernel references

Detail

Evidence (decisive lines)

VERDICT.md path:line trace; run.log shows BUILD_OK_4377 then '/dev/bktr0 not present' EXIT=2 and pciconf brooktree count 0; env.txt guest state.

PoC changes

Probe written from scratch (finding is probe-time, no seed PoC existed); added after first guest compile failed; used guest-side b64decode for transfer.

Verified recommended fix

Zero-initialize eeprom[256] in probeCard() so failed EEPROM reads yield deterministic ids (fix.diff); alternatively honor readEEProm()'s return at all four call sites.

Verdict

probeCard() uses stack eeprom[256] uninitialized whenever readEEProm() fails (return ignored at bktr_card.c:667/794/1001/1129); garbage subsystem/model bytes are printed to console/dmesg at :676/:757/:814 and drive card selection. Not reproduced on the audit guest: no Bt848/878 hardware, /dev/bktr* absent (chk_bktr built and ran, exit 2). Defect is certain from source; manifestation is hardware-gated (EEPROM probe-ACK + read-NAK). Low-severity kernel-stack disclosure, same class as DF-2092 (cxm).