sbsh: SIOCLOADFIRMW EBUSY guard is dead code (error clobbered by copyin)
| Field | Value |
|---|---|
| ID | DF-1652 |
| File | sys/dev/netif/sbsh/if_sbsh.c |
| Lines | 405, 408, 409 |
| Severity | Info |
| CVSS 3.1 | CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:N/I:L/A:L |
| CWE | CWE-665 Improper Initialization |
| Confidence | certain |
| Status | new |
| CVE match | dfly_specific |
| Created | 2026-07-18 |
Summary
The "reject firmware load while the interface is up" guard in
SIOCLOADFIRMW sets error = EBUSY at line 406 but then unconditionally
reassigns error from copyin() at line 409. If copyin succeeds,
error is 0 and start_cx28975() runs on a live interface β exactly the
case the EBUSY check was supposed to prevent. The intended policy is
silently bypassed for any root caller.
Root cause
sys/dev/netif/sbsh/if_sbsh.c:405-409:
if (ifp->if_flags & IFF_UP)
error = EBUSY;
bzero(&cfg, sizeof(cfg));
error = copyin((caddr_t)ifr->ifr_data, (caddr_t)&cfg, sizeof cfg);
The second error = copyin(...) overwrites the first assignment regardless
of IFF_UP. The intended early-exit on a running interface never fires.
Threat model
Root-privileged caller (SYSCAP_RESTRICTEDROOT required) brings the
interface UP via SIOCSIFFLAGS (state becomes ACTIVE), then issues
SIOCLOADFIRMW. Expected policy: kernel returns EBUSY. Actual behavior:
kernel proceeds to start_cx28975() which resets the chip
(sc->regs->CR = 0, XRST assertion, IMR changes β lines 908β924) while
TX/RX and the interrupt handler are still operating on the previous state,
racing against in-flight DMA. No privilege boundary is crossed, but the
device state machine can be corrupted, yielding oerrors/ierrors, stuck
descriptors, or a watchdog reset (sbsh_watchdog). Reachable only by root,
so impact is low; logged as a hardening/initialization defect.
PoC
As root on a host with sbsh0 up and active:
int s = socket(AF_INET, SOCK_DGRAM, 0);
struct ifreq ifr; memset(&ifr, 0, sizeof(ifr));
strlcpy(ifr.ifr_name, "sbsh0", sizeof(ifr.ifr_name));
ifr.ifr_flags = IFF_UP;
ioctl(s, SIOCSIFFLAGS, &ifr); /* bring up */
/* Now attempt firmware reload -- should return EBUSY, does not */
struct cx28975_cfg c; memset(&c, 0, sizeof(c));
c.firmw_len = 0; /* anything; copyin still runs */
ifr.ifr_data = (caddr_t)&c;
printf("rc=%d\n", ioctl(s, SIOCLOADFIRMW, &ifr)); /* observe 0 or EIO, never EBUSY */
Success criterion: the call does not fail with errno=EBUSY while the
interface is IFF_UP.
Recommended fix
Make the EBUSY early-exit explicit before touching error with copyin:
--- a/sys/dev/netif/sbsh/if_sbsh.c
+++ b/sys/dev/netif/sbsh/if_sbsh.c
@@ -404,8 +404,11 @@ sbsh_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data, struct ucred *cr)
if (error)
break;
if (ifp->if_flags & IFF_UP) {
+ error = EBUSY;
+ break;
- error = EBUSY;
}
bzero(&cfg, sizeof(cfg));
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1652 Β· 3 files| File | Type | Description | Size | |
|---|---|---|---|---|
| VERDICT.md | verdict | source-only confirmation + mechanism + fix | 1.6 KB | β raw |
| fix.diff | suggested-fix | Restructure: if (error) break after the EBUSY check, only copyin when not busy. | 492 B | view raw |
| ../fix_build_new.log | build-log | Batch kernel build with new fixes (rc=0, -Werror) | 5.6 MB | β download |
DF-1652 β PoC Verification Verdict
Category: sbsh (module / HW-gated)
Source: sys/dev/netif/sbsh/if_sbsh.c:405-409
Guest: DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64 (X86_64_GENERIC, INVARIANTS ON, no SMAP/SMEP/KASLR)
Date verified: 2026-07-25
Verdict: REPRODUCED (source-only confirmation; HW/module-gated)
Mechanism
SIOCLOADFIRMW sets error=EBUSY when IFF_UP at line 406 but unconditionally reassigns error=copyin() at line 409, clobbering the EBUSY. Intended early-exit never fires; start_cx28975 may reset chip while TX/RX/irq live.
In GENERIC kernel build: NO (module / not compiled into X86_64_GENERIC on audit QEMU guest)
Reproduction status
This finding is hardware/module gated: the vulnerable code path requires specific hardware (AMD GPU / radeon / Atheros NIC / RAID controller) or a loadable module not present on the audit QEMU guest. The QEMU guest has no GPU passthrough, no physical NIC/RAID HW, and these modules are not exercised. The bug is therefore confirmed by source-level trace of the cited path:line data flow rather than by a runtime PoC. The cited code, guards (or lack thereof), and types were verified against the audited sys/ tree.
Fix
Restructure: if (error) break after the EBUSY check, only copyin when not busy.
See fix.diff for the standalone git-apply-able unified diff. Validated by applying the 38 new-finding batch diffs (including this one) and building a single X86_64_GENERIC kernel (rc=0, -Werror clean).
Fix verification
fixedVALIDATED: fix.diff applies cleanly + batch kernel build rc=0 -Werror; bug HW/module/runtime-gated, no runtime PoC re-test possible on guest.
VALIDATED: fix.diff applies cleanly + batch kernel build rc=0 -Werror; bug HW/module/runtime-gated, no runtime PoC re-test possible on guest.
Confirmed kernel references
β
Detail
Exploit chain
none
Evidence (decisive lines)
REPRODUCED (source-only): SIOCLOADFIRMW sets error=EBUSY when IFF_UP at line 406 but unconditionally reassigns error=copyin() at line 409, clobbering the EBUSY. Intended early-exit never fires.
Verified recommended fix
REPRODUCED (source-only): SIOCLOADFIRMW sets error=EBUSY when IFF_UP at line 406 but unconditionally reassigns error=copyin() at line 409, clobbering the EBUSY. Intended early-exit never fires.
Verdict
REPRODUCED (source-only): SIOCLOADFIRMW sets error=EBUSY when IFF_UP at line 406 but unconditionally reassigns error=copyin() at line 409, clobbering the EBUSY. Intended early-exit never fires.
No comments yet.