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

Hauppauge EEPROM block-parse reads block_2/block_3 via attacker-influenced offsets without bounds checks (kernel stack OOB read / panic on probe)

  • File: sys/dev/video/bktr/bktr_card.c
  • Lines: 1001, 1005, 1006, 1008, 1009, 1010, 1012, 1019
  • Severity: Low
  • CVSS: CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U:C:N/I:N/A:H
  • CWE: CWE-125 Out-of-bounds Read
  • Confidence: likely

Summary

In probeCard()'s CARD_HAUPPAUGE branch, the Hauppauge EEPROM data-block parser computes block_2 = &eeprom[block_1_total_size] and block_3 = &eeprom[block_1_total_size + block_2_total_size] directly from bytes read off the card EEPROM (block_1[1..2], block_2[1..2]) and then dereferences block_2[1], block_2[2], and block_3[3] without any bounds validation.

Because only 128 bytes are actually read into the 256-byte stack buffer eeprom[] (and readEEProm's return value is not checked), a crafted EEPROM can drive these pointers anywhere from the uninitialized tail of eeprom[] out past the kernel stack frame, causing silent uninitialized-stack reads into driver state or β€” for large offsets β€” a fault into the kernel-stack guard page that panics the system at probe time.

Root cause

bktr_card.c:1001 calls readEEProm(bktr, 0, 128, &eeprom) which loads at most 128 bytes into the 256-byte stack buffer u_char eeprom[256] declared at bktr_card.c:605; eeprom[128..255] is left uninitialized and the readEEProm return value (-1 on failure) is never checked.

bktr_card.c:1005-1006 then derive:

block_1_data_size  = (block_1[2] << 8 | block_1[1]);   /* range 0..65535 */
block_1_total_size = block_1_data_size + 3;             /* range 3..65538 */

directly from EEPROM contents.

bktr_card.c:1008 sets block_2 = &eeprom[block_1_total_size] with no check that block_1_total_size + 2 < 128 (the data actually read), and line 1009 immediately dereferences block_2[1] and block_2[2] to compute block_2_data_size.

bktr_card.c:1012 then sets block_3 = &eeprom[block_1_total_size + block_2_total_size] (offset up to ~131079) and bktr_card.c:1019 dereferences block_3[3].

All three dereferences are unbounded; only block_1[9..15] reads (bktr_card.c:1014-1017) happen to be in-bounds.

Threat

Attacker position: anyone who can influence the contents of an I2C EEPROM-like device responding at one of {0xa0, 0xac, 0xae} on a Brooktree Bt848/Bt878 capture card installed in the target machine (e.g. a reprogrammed Hauppauge EEPROM, a custom/faulty I2C device on the bktr bus, or any I2C responder at 0xa0 once card detection is forced via the root-settable hw.bt848.card=2 sysctl combined with bktr_card.c:638-641 override path).

The probe runs unconditionally at boot (bktr_core.c:505, probeCard(TRUE, unit)) and again on every open of /dev/bktr0 (bktr_os.c:458-476).

Impact: with block_1[1..2] encoding a small out-of-range total size (e.g. 0x0080..0x00FF) the driver silently reads uninitialized kernel stack at eeprom[128..255] into driver state (no exfiltration path observed β€” values feed only the 1-bit no_audio_mux decision at bktr_card.c:1019 and the verbose kprintf at 1024-1030 which uses in-bounds block_1 fields); with a large total size (e.g. 0xFFFF) the dereference lands past the ~4 KB kernel stack and hits the stack guard page, panicking the system.

Net impact is a kernel-panic DoS at boot or device open; no info-leak or write primitive was found.

Validate block offsets against the 128 bytes actually read, bail out (skip the Hauppauge parse) on out-of-range offsets or on readEEProm failure.

--- a/sys/dev/video/bktr/bktr_card.c
+++ b/sys/dev/video/bktr/bktr_card.c
@@ -998,9 +998,23 @@ probeCard( bktr_ptr_t bktr, int verbose, int unit )
        unsigned char tuner_code;
        unsigned char no_audio_mux;

-       readEEProm(bktr, 0, 128, (u_char *) &eeprom );
+       /* Only 128 bytes are loaded into eeprom[]; require the read to
+        * succeed and validate every block offset against that window
+        * before dereferencing block_2/block_3 pointers derived from
+        * (possibly hostile) EEPROM contents. */
+       if ( readEEProm(bktr, 0, 128, (u_char *) &eeprom ) < 0 )
+           break;

        /* LOCATE THE EEPROM DATA BLOCKS */
        block_1 = &eeprom[0];
        block_1_data_size = (block_1[2] << 8 | block_1[1]);
        block_1_total_size = block_1_data_size + 3; /* Header bytes */
+       /* block_2[2] must remain inside the 128-byte window */
+       if (block_1_total_size < 0 || block_1_total_size > 128 - 3)
+           break;

        block_2 = &eeprom[block_1_total_size];
        block_2_data_size = (block_2[2] << 8 | block_2[1]);
        block_2_total_size = block_2_data_size + 3; /* Header bytes */
+       /* block_3[3] must remain inside the 128-byte window */
+       if (block_2_total_size < 0 ||
+           block_1_total_size + block_2_total_size > 128 - 4)
+           break;

        block_3 = &eeprom[block_1_total_size + block_2_total_size];

        model    = (block_1[12] << 8  | block_1[11]);

Breaking out of the enclosing switch (card) case on a bad EEPROM simply falls through to the default tuner-selection logic at bktr_card.c:1189-1203, which is the intended behaviour for an unrecognised card.

The same if (readEEProm(...) < 0) ... guard should also be added at bktr_card.c:667 and 794 to prevent the parallel uninitialized-stack use in the subsystem-ID and Osprey/Hauppauge-signature detection paths (defense in depth).

  • DF-1215 (sibling, bktr_core.c): Critical METEORSVIDEO DMA target address.
  • DF-1216 (sibling, bktr_core.c): BPP mismatch OOB.
  • DF-1217 (sibling, bktr_core.c): I2C no-auth.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1569 Β· 4 files
FileTypeDescriptionSize
fix.diff suggested-fix git-apply-able fix for the cited bug 766 B view raw
VERDICT.md verdict source-confirmation analysis 1.0 KB ↓ raw
build.sh build-script N/A (source-only) 65 B view raw
run.sh run-script N/A (source-only) 89 B view raw
VERDICT.md verdict source-confirmation analysis
↓ download raw

DF-1569 VERDICT

Verdict: REPRODUCED (source-confirmed)

Impact: Low (driver-level NULL deref / OOB / leak / DoS β€” hardware-gated)

Mechanism: bktr_card.c:1001 readEEProm reads 128 bytes into 256-byte eeprom[] stack buffer; return value NEVER checked. 1005-1006 block_1_data_size=(block_1[2]<<8|block_1[1]) range 0..65535; block_1_total_size=d

Citation: sys/dev/video/bktr/bktr_card.c:1001-1019

Fix: Applied fix.diff β€” compiles in batch kernel build (rc=0, -Werror).

Verification method: Source-only line-by-line trace of cited path:line. Low-severity driver bug; PoC trigger requires specific hardware or root context. Confirmed the cited vulnerable pattern exists in source.

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

fix.diff compiled in batch kernel build rc=0 -Werror

fix.diff compiled in batch kernel build rc=0 -Werror
↓ fix.diffcombined build rc=0

Confirmed kernel references

β€”

Detail

Exploit chain

none (Low severity)

Evidence (decisive lines)

Source-confirmed: unchecked EEPROM block offsets deref block_2/3 OOB (bktr_card.c:1001-1019)

Verified recommended fix

Source-confirmed: unchecked EEPROM block offsets deref block_2/3 OOB (bktr_card.c:1001-1019)

Verdict

Source-confirmed: unchecked EEPROM block offsets deref block_2/3 OOB (bktr_card.c:1001-1019)