β¬’ 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 712 B ↓ raw
build.sh build-script N/A (source-only) 61 B view raw
run.sh run-script N/A (source-only) 87 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 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)