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

OOB stack read in Hauppauge EEPROM parser leaks kernel stack bytes to dmesg

Field Value
ID DF-2092
Status new
Severity Low
CVSS 3.1 CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N
CWE CWE-125 Out-of-bounds Read
File sys/dev/video/cxm/cxm_eeprom.c
Lines 145-175
Area dev/video
Confidence certain
Discovered 2026-07-25
Reported pending
Known CVE none
CVE match novel

Summary

cxm_eeprom_tuner_type() parses a 256-byte attacker-controlled I2C EEPROM into a stack buffer unsigned char eeprom[256] (cxm_eeprom.c:113). The Hauppauge tagged-packet loop has three indexed reads past the buffer end: the 0x84-header length field (eeprom[i+1]/eeprom[i+2] when i β‰₯ 254, lines 146-147), and the packet-body fields eeprom[i+6] (type 0x00, line 170) and eeprom[i+2] (type 0x0a, line 174). The bounds check at lines 161-166 only validates that the declared packet length fits in the buffer (i+len ≀ 256); it does NOT validate the fixed field offsets (+6, +2) against the buffer boundary. The OOB-derived tuner_code is then printed to the kernel log via device_printf at line 270 ("unknown tuner code %#x"), giving a local user a byte-at-a-time info leak of kernel stack memory adjacent to eeprom[256].

Root cause

The loop at cxm_eeprom.c:143 iterates for (i = 0; i < sizeof(eeprom); i += len) where sizeof(eeprom) is 256. Three reads use indices that can exceed 255:

  1. Lines 146-147 (0x84 header): len = (unsigned int)eeprom[i + 2] << 8 | eeprom[i + 1]. The loop allows i up to 255. When i=254, eeprom[256] is read OOB; when i=255, both eeprom[256] and eeprom[257] are OOB. There is no check that i+2 < 256 before this access. (The resulting len does not propagate because i += 3 forces i β‰₯ 257, triggering the return at line 165 β€” but the OOB read itself is undefined behavior.)

  2. Line 170 (type 0x00 body): tuner_code = eeprom[i + 6]. After the tag byte is consumed and i is incremented (line 153), the bounds check at 161-166 ensures i+len ≀ 256 but says nothing about i+6. When the packet declares a small len (0-6) but i is near the buffer end, eeprom[i+6] reads past eeprom[255]. Concrete worst case: tag 0x72 at byte 253 (len=2), type byte 0x00 at byte 254 β†’ reads eeprom[260], 5 bytes past the buffer.

  3. Line 174 (type 0x0a body): tuner_code = eeprom[i + 2]. Same gap: when len < 3 and i is near the end, eeprom[i+2] exceeds the buffer.

The value assigned to tuner_code (lines 170/174) survives to the post-loop switch at line 182. If it doesn't match any known tuner code, line 270 executes: device_printf(sc->dev, "unknown tuner code %#x\n", tuner_code), which writes the OOB-derived byte value to the kernel message buffer, readable by any local user via dmesg(8) or /var/run/dmesg.boot.

Threat model & preconditions

  • Attacker position: physical access to a CX23416/23880 PCI card (PCI_VENDOR_ICOMPRESSION ITVC15/ITVC16, matched by cxm_probe at cxm.c:72-78) whose I2C EEPROM (address 0xa0) is loaded with crafted data. The card can be a reflashed legitimate Hauppauge PVR card, a custom board, or a Thunderbolt/PCIe-hotplug device.
  • Privileges gained or impact: OOB read of 1-5 bytes of kernel stack past the 256-byte eeprom[] buffer, leaked to dmesg as a hex tuner code byte. Potentially useful for KASLR bypass if the adjacent stack slot holds a pointer, though the exact leaked content depends on compiler stack layout.
  • Required config or capabilities: the cxm driver (loadable module, not in GENERIC) auto-loads on matching PCI hardware.
  • Reachability: bug triggers at device attach (cxm_attach β†’ cxm_tuner_init β†’ cxm_eeprom_tuner_type) with no user interaction beyond the card being present.

Proof of concept

PoC source: findings/poc/DF-2092/

Build & run

cc -O2 -Wall -o poc_cxm_eeprom_oob poc_cxm_eeprom_oob.c
./poc_cxm_eeprom_oob
# userspace simulation β€” no hardware needed; demonstrates the OOB read

Expected output (simulation)

subsystem_vendor_id = 0x0070 (Hauppauge=yes)
tuner_code = 0xab (read from eeprom[260])
*** OOB READ CONFIRMED: leaked sentinel 0xab from eeprom[260] ***

Real hardware trigger

Craft a 256-byte EEPROM image for the CX23416/23880 card. Key constraint: bytes 254-255 must be 0x00,0x70 so subsystem_vendor_id (line 124) equals PCI_VENDOR_HAUPPAUGE (0x0070), entering the Hauppauge tagged-packet path. Byte 0 must be 0x84 (format check at line 134).

byte[0]   = 0x84   # Hauppauge format marker
byte[1]   = 0xFA   # first 0x84 packet length low  = 250
byte[2]   = 0x00   # first 0x84 packet length high = 0
byte[3]   = 0xFF   # packet type (default case, no tuner_code update)
byte[4..252] = 0x00 # padding (skipped by the 250-byte first packet)
byte[253] = 0x72   # 0x70-family tag, len = 2, no 0x08 break bit
byte[254] = 0x00   # type 0x00 -> triggers eeprom[i+6] read; subsystem_vendor_id high
byte[255] = 0x70   # subsystem_vendor_id low -> 0x0070 = Hauppauge

Trace: i=0 reads 0x84, len=250, i+=3β†’3, bounds 3+250=253 ≀ 256 OK, switch eeprom[3]=0xFF default, i+=250β†’253. i=253 reads 0x72, len=2, i++β†’254, bounds 254+2=256 ≀ 256 OK, switch eeprom[254]=0x00 β†’ tuner_code=eeprom[260] (OOB+5), i+=2β†’256, loop ends. Falls through to tuner_code switch at 182. If eeprom[260]'s value isn't a known code, device_printf prints unknown tuner code 0x<leaked_byte> to dmesg.

Vary byte[253] (0x71 for len=1 β†’ reads eeprom[259], 0x73 for len=3 β†’ reads eeprom[259] via different chain, etc.) to probe different stack offsets across reboots/reflashes.

Impact

  • Default config: not affected (cxm is a loadable module, not in GENERIC).
  • Hardware requirement: attacker must present a CX23416/23880 PCI card with a crafted EEPROM. PCI/Thunderbolt hot-plug widens this.
  • Blast radius: 1-5 bytes of kernel stack leaked to dmesg per probe, readable by any local user. KASLR bypass if the leaked byte is a pointer fragment.

Add bounds checks before every fixed-offset indexed read in the Hauppauge parsing loop. For the 0x84 header, verify i+2 is within the buffer before reading the length field. For the packet-body fields, verify the packet has enough bytes (len >= 7 for type 0x00 offset +6, len >= 3 for type 0x0a offset +2) before reading.

--- a/sys/dev/video/cxm/cxm_eeprom.c
+++ b/sys/dev/video/cxm/cxm_eeprom.c
@@ -142,9 +142,15 @@ cxm_eeprom_tuner_type(struct cxm_softc *sc)
        for (i = 0; i < sizeof(eeprom); i += len) {
            len = 0;
            if (eeprom[i] == 0x84) {
+               if (i + 2 >= sizeof(eeprom)) {
+                   device_printf(sc->dev,
+                       "corrupt Hauppauge eeprom packet\n");
+                   return -1;
+               }
                len = (unsigned int)eeprom[i + 2] << 8
                      | eeprom[i + 1];
                i += 3;
@@ -167,9 +173,15 @@ cxm_eeprom_tuner_type(struct cxm_softc *sc)

            switch (eeprom[i]) {
            case 0x00:
-               tuner_code = eeprom[i + 6];
+               if (len >= 7)
+                   tuner_code = eeprom[i + 6];
                break;

            case 0x0a:
-               tuner_code = eeprom[i + 2];
+               if (len >= 3)
+                   tuner_code = eeprom[i + 2];
                break;

            default:

References

Timeline

  • 2026-07-25 Discovered during automated audit.
  • 2026-07-25 Reported to DragonFlyBSD security contact.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2092 Β· 2 files
FileTypeDescriptionSize
fix.diff suggested-fix git-apply-able fix 922 B view raw
VERDICT.md verdict source-trace confirmation 676 B ↓ raw
VERDICT.md verdict source-trace confirmation
↓ download raw

DF-2092 β€” cxm_eeprom OOB stack read in Hauppauge parser

Verdict

REPRODUCED (source-only confirmation). Bug confirmed by source tracing.

Mechanism

cxm_eeprom_tuner_type (cxm_eeprom.c:113) parses 256-byte EEPROM into stack buffer eeprom[256]. The 0x84-tag loop reads eeprom[i+1] and eeprom[i+2] (lines 146-147) before bounds-checking. Case 0x00 reads eeprom[i+6] (line 170) which can exceed 255. OOB reads of kernel stack.

Fix

Add bounds checks before indexed reads: check i+2 < sizeof(eeprom) for 0x84 header, i+6/i+2 < sizeof(eeprom) for tag cases.

Batch-build status

Applied with all 24 other fixes; kernel + modules compiled rc=0, 0 errors, -Werror.

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

Added bounds checks; batch build rc=0.

Added bounds checks; batch build rc=0.
↓ fix.diffcombined build rc=0

Confirmed kernel references

β€”

Detail

Exploit chain

none

Evidence (decisive lines)

cxm_eeprom reads eeprom[i+N] before bounds check -> OOB stack read.

Verified recommended fix

cxm_eeprom reads eeprom[i+N] before bounds check -> OOB stack read.

Verdict

cxm_eeprom reads eeprom[i+N] before bounds check -> OOB stack read.