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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1217 Β· 9 files
FileTypeDescriptionSize
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
README.md readme reproduce / preconditions / PoC snippet
↓ download 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.ko loaded so /dev/bktrN exists.

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.
VERDICT.md verdict full narrative + path:line trace
↓ download raw

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 β€” cdevsw declares .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 to video_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 (make in /usr/src/sys/dev/video/bktr) β€” compiled clean, bktr.ko produced, 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_testable

compile 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.