Unprivileged users get raw I2C-bus, EEPROM-write, and GPIO control over the capture card
Summary
BT848_I2CWR (:2168), BT848_WEEPROM (:2078), BT848_GPIO_SET_EN/DATA (:2108-2118) accept arbitrary hardware-control args with NO priv check. Any local user with /dev/bktrN O_RDONLY (mode 0444): (a) drive I2C bus writing to tuner PLL/audio DSP/EEPROM at arbitrary device addresses, (b) write persistent EEPROM data surviving reboot (firmware-level tampering/persistent DoS), (c) toggle GPIO pins muxing analog paths, (d) reprogram tuner to out-of-band RF frequencies. No caps_priv_check/priv_check/suser anywhere in file. Fix: add caps_priv_check_td(td, SYSCAP_RESTRICTEDROOT) to each hw-control ioctl case.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1217 Β· 9 files| File | Type | Description | Size | |
|---|---|---|---|---|
| VERDICT.md | verdict | full narrative + path:line trace | 4.9 KB | β raw |
| README.md | readme | reproduce / preconditions / PoC snippet | 1.1 KB | β raw |
| fix.diff | suggested-fix | caps_priv_check_td gate on hw-control ioctls | 1.3 KB | view raw |
| build.sh | build-log | standalone bktr module build | 296 B | view raw |
| run.sh | run-log | no runtime trigger; static trace | 154 B | view raw |
| module_build.log | build-log | bktr module build output (fix compiles) | 1.6 KB | view raw |
| env.txt | environment | guest uname, PCI devices, GENERIC config | 587 B | view raw |
| ../fix_build_combined.log | build-log | Combined 41-finding kernel build (rc=0, -Werror clean) | 5.6 MB | β download |
| ../fix_build_summary.txt | build-summary | Summary of the combined 41-finding kernel build | 826 B | view raw |
DF-1217 β bktr unprivileged I2C / EEPROM / GPIO access
Reproduce
Not runnable on the audit guest β no Brooktree capture card is attached. The
verification is a static source trace + a compiling fix.diff.
To validate the fix compiles as a standalone module:
cd /usr/src/sys/dev/video/bktr
make obj
make
ls /usr/obj/usr/src/sys/dev/video/bktr/bktr/bktr.ko
Bug location
sys/dev/video/bktr/bktr_core.c:1202 (video_ioctl) dispatches hardware-
control ioctls (BT848_WEEPROM, BT848_I2CWR, BT848_GPIO_SET_EN,
BT848_GPIO_SET_DATA, BT848_MSP_WRITE, BT848_MSP_RESET) with no
privilege check. Device is created mode 0444 at bktr_os.c:325 so any
local user can open and issue them.
Trigger preconditions (NOT met on this guest)
- A Bt848/Bt878 capture card and
bktr.koloaded so/dev/bktrNexists.
On a system WITH the card, the PoC is:
int fd = open("/dev/bktr0", O_RDONLY);
u_long i2c = (1UL<<24) | (addr<<16) | (port<<8) | data; /* write=1 */
ioctl(fd, BT848_I2CWR, &i2c); /* UNFIXED: 0; FIXED: -1 errno=EPERM */
Files
VERDICT.md,fix.diff,env.txt,module_build.log.
DF-1217 β bktr unprivileged I2C / EEPROM / GPIO access
Verdict
INCONCLUSIVE (not_testable on this guest) β bug confirmed in source; the
capture-card hardware is absent from the audit guest, so /dev/bktrN is never
created and the ioctls cannot be issued. Fix authored, applied, and the bktr
module compiled clean as a standalone module build.
Finding summary
video_ioctl() in sys/dev/video/bktr/bktr_core.c dispatches several
hardware-control ioctls with no privilege check at any layer:
- BT848_WEEPROM (:2078) β writes persistent on-card EEPROM data.
- BT848_I2CWR (:2168) β drives the I2C bus at arbitrary device addresses
(tuner PLL, audio DSP, EEPROM), both read and write.
- BT848_GPIO_SET_EN (:2108) / BT848_GPIO_SET_DATA (:2116) β toggle GPIO
output pins, muxing analog signal paths.
- BT848_MSP_WRITE / BT848_MSP_RESET (:2195/:2204) β direct MSP34xx audio
DSP control.
The device node is created with mode 0444 (make_dev(... 0444, "bktr%d", unit)
at bktr_os.c:325β327), and bktr_ioctl() (bktr_os.c:615) calls video_ioctl()
directly with no caps_priv_check_td / priv_check / suser gate. So any
local user who can open /dev/bktr0 read-only (world-readable) can:
- write persistent EEPROM data that survives reboot (firmware-level tampering /
persistent DoS of the card),
- drive the I2C bus at arbitrary device addresses (write to tuner PLL, audio
DSP, EEPROM),
- toggle GPIO pins (mux analog paths),
- reprogram the tuner to out-of-band RF frequencies.
This is a privilege-boundary / hardware-misuse gap, not a memory-corruption bug.
No chain to uid=0 is implied; the impact is unauthorized hardware control
(which on capture-card-equipped kiosk / PVR / embedded systems is a real
concern β persistent EEPROM writes are particularly nasty).
Source confirmation (audited tree)
- bktr_os.c:147 β
cdevswdeclares.d_open = bktr_open,.d_ioctl = bktr_ioctl. - bktr_os.c:325β327 β
make_dev(&bktr_ops, unit, 0, 0, 0444, "bktr%d", unit);β world-readable. - bktr_os.c:615β649 β
bktr_ioctl()no priv check; dispatches tovideo_ioctl()for VIDEO_DEV. - bktr_core.c:1202 β
video_ioctl()entry β no priv check at entry. - bktr_core.c:2078β2084 β
BT848_WEEPROMβwriteEEProm()β no priv check. - bktr_core.c:2108β2118 β
BT848_GPIO_SET_EN/_SET_DATAβOUTL()MMIO β no priv check. - bktr_core.c:2168β2181 β
BT848_I2CWRβi2cWrite()/i2cRead()β no priv check. - bktr_core.c:2195β2206 β
BT848_MSP_WRITE/_RESETβmsp_dpl_write()/msp_dpl_reset()β no priv check. - Compare to
sys/dev/misc/kbd/kbd.c:672βif (caps_priv_check_self(SYSCAP_RESTRICTEDROOT)) return (EPERM);β the canonical pattern this driver is missing.
Why not runtime-reproduced on this guest
The QEMU/KVM guest has no Brooktree Bt848/Bt878 video-capture card (no PCI
device matching; only 7 PCI devices total, all virtio/QEMU-standard). So the
bktr driver never attaches, no /dev/bktrN node is created, and the ioctls
cannot be issued. The bktr.ko module is present in /boot/kernel/ but is
not loaded (the driver probe returns ENXIO without matching hardware).
This is a privilege-check gap rather than a memory bug; even on hardware that had the card, the demonstration would be "ioctl succeeds as unprivileged user" vs the fixed "ioctl returns EPERM as unprivileged user". The static trace above is sufficient to confirm the bug.
Fix (fix.diff)
Add a privilege gate at the top of video_ioctl() for the hardware-control
write ioctls, using the standard caps_priv_check_td(td, SYSCAP_RESTRICTEDROOT)
pattern (same pattern as kbd.c:672):
#include <sys/caps.h> /* added near other sys/ includes */
int
video_ioctl(...) {
switch (cmd) {
case BT848_WEEPROM:
case BT848_I2CWR:
case BT848_GPIO_SET_EN:
case BT848_GPIO_SET_DATA:
case BT848_MSP_WRITE:
case BT848_MSP_RESET:
if (caps_priv_check_td(td, SYSCAP_RESTRICTEDROOT))
return (EPERM);
break;
default:
break;
}
...
Reads (BT848_REEPROM, BT848_GPIO_GET_*) are left open β they leak only
card-identity / GPIO state, not persistent-modification capability.
Fix validation
git apply --check -p1β clean.- Applied to in-guest
/usr/src/sys/dev/video/bktr/bktr_core.c; standalone module build (makein/usr/src/sys/dev/video/bktr) β compiled clean,bktr.koproduced, build rc=0. fix_status: not_testableβ no capture-card hardware on this guest; cannot do a runtime before/after EPERM demonstration.
Run / reproduce
Not runnable on this guest. On a system with a Bt878 capture card and the
bktr.ko module loaded, the PoC is simply:
int fd = open("/dev/bktr0", O_RDONLY); /* succeeds as any user (mode 0444) */
u_long i2c = (1UL<<24) | (addr<<16) | (port<<8) | data; /* write=1 */
ioctl(fd, BT848_I2CWR, &i2c); /* UNFIXED: succeeds; FIXED: EPERM */
Fix verification
not_testablecompile validated
kernel/module build rc=0
Confirmed kernel references
β
Detail
Exploit chain
none
Evidence (decisive lines)
β
Verdict
Source-confirmed. bktr video_ioctl no priv check on WEEPROM/I2CWR/GPIO_SET. bktr module, no Bt878 HW.
No comments yet.