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:
-
Lines 146-147 (
0x84header):len = (unsigned int)eeprom[i + 2] << 8 | eeprom[i + 1]. The loop allowsiup to 255. Wheni=254,eeprom[256]is read OOB; wheni=255, botheeprom[256]andeeprom[257]are OOB. There is no check thati+2 < 256before this access. (The resultinglendoes not propagate becausei += 3forcesi β₯ 257, triggering the return at line 165 β but the OOB read itself is undefined behavior.) -
Line 170 (type
0x00body):tuner_code = eeprom[i + 6]. After the tag byte is consumed andiis incremented (line 153), the bounds check at 161-166 ensuresi+len β€ 256but says nothing abouti+6. When the packet declares a smalllen(0-6) butiis near the buffer end,eeprom[i+6]reads pasteeprom[255]. Concrete worst case: tag0x72at byte 253 (len=2), type byte0x00at byte 254 β readseeprom[260], 5 bytes past the buffer. -
Line 174 (type
0x0abody):tuner_code = eeprom[i + 2]. Same gap: whenlen < 3andiis 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_probeatcxm.c:72-78) whose I2C EEPROM (address0xa0) 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 todmesgas 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
cxmdriver (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
dmesgper probe, readable by any local user. KASLR bypass if the leaked byte is a pointer fragment.
Recommended fix
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
sys/dev/video/cxm/cxm.c:72-78β PCI device match table forPCI_VENDOR_ICOMPRESSIONITVC15/ITVC16.sys/dev/video/cxm/cxm_tuner.c:547-574β callercxm_tuner_init β cxm_eeprom_tuner_type.
Timeline
- 2026-07-25 Discovered during automated audit.
- 2026-07-25 Reported to DragonFlyBSD security contact.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-2092 Β· 2 files| File | Type | Description | Size | |
|---|---|---|---|---|
| fix.diff | suggested-fix | git-apply-able fix | 922 B | view raw |
| VERDICT.md | verdict | source-trace confirmation | 676 B | β 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
fixedAdded bounds checks; batch build rc=0.
Added bounds checks; batch 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.
No comments yet.