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

if_vke: LOW_POW_2(0) undefined shift when nmbclusters < NetifNum*2

Field Value
ID DF-1671
File sys/dev/virtual/vkernel/net/if_vke.c
Lines 89, 303, 832–841
Severity Info
CVSS 3.1 CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:U/C:N/I:N/A:L
CWE CWE-758 Reliance on Undefined Behavior
Confidence speculative
Status new
CVE match dfly_specific
Created 2026-07-18

Summary

vke_attach computes the ring size as LOW_POW_2(nmbufs) where nmbufs = nmbclusters / (NetifNum * 2). LOW_POW_2(n) = 1 << (fls(n) - 1). fls(0) returns 0 (cpufunc.h:287), so when nmbufs == 0 this becomes 1 << (0 - 1) = 1 << -1, which is undefined behavior in C (negative shift count on a signed int).

On typical x86/gcc this evaluates to INT_MIN, which is then sign-extended into the size_t ringsize multiplication at line 303 producing a multi-GB kmalloc request that hangs/OOM-panics the vkernel boot.

Root cause

Macro at if_vke.c:89:

#define LOW_POW_2(n) (1 << (fls(n) - 1))

vke_attach at line 832-841: when vke_max_ringsize==0 (default tunable), nmbufs = nmbclusters / (NetifNum * 2) then sc->sc_ringsize = LOW_POW_2(nmbufs).

If nmbclusters is small enough that nmbclusters < NetifNum*2, nmbufs is 0 (integer division), and LOW_POW_2(0) is undefined. fls(0)==0 is confirmed in sys/cpu/x86_64/include/cpufunc.h:285-288 (mask == 0 ? mask : ...).

Downstream in vke_init at line 303: size_t ringsize = sc->sc_ringsize * sizeof(struct mbuf *); β€” a negative int promoted to size_t becomes ~2^64, multiplied by 8 yields an absurd kmalloc size at lines 322-327 with M_WAITOK (sleeps forever / panics on OOM).

Threat model

Operator-only trigger (PR:H): requires setting kern.ipc.nmbclusters very low at boot AND starting β‰₯ 1 vke interface. A guest user cannot influence nmbclusters or NetifNum.

Impact is vkernel boot failure / OOM panic β€” availability only, on the vkernel process. No memory corruption is reachable because the bogus kmalloc size never returns a usable pointer (M_WAITOK either sleeps or panics before the ring is used).

PoC

Host setup: boot the host kernel with kern.ipc.nmbclusters=2 (loader.conf), then launch a vkernel with two interfaces:

vkernel64 -I vke0:/dev/tap1 -I vke1:/dev/tap2 ...

nmbufs = 2/(2*2) = 0, LOW_POW_2(0) = UB.

Success criterion: vkernel boot hangs or panics with kmalloc failure in vke_init. This is a self-DoS for the operator; not reachable by a guest user.

Defensively floor LOW_POW_2 at 1 and also sanity-check the final sc_ringsize.

--- a/sys/dev/virtual/vkernel/net/if_vke.c
+++ b/sys/dev/virtual/vkernel/net/if_vke.c
@@ -86,7 +86,7 @@ static int vke_max_ringsize = 0;
 TUNABLE_INT("hw.vke.max_ringsize", &vke_max_ringsize);

-#define LOW_POW_2(n)   (1 << (fls(n) - 1))
+#define LOW_POW_2(n)   ((n) <= 1 ? 1 : (1 << (fls(n) - 1)))

 struct vke_softc {
@@ -841,6 +841,10 @@ vke_attach(const struct vknetif_info *info, int unit)
    } else {
        sc->sc_ringsize = LOW_POW_2(VKE_CHUNK);
    }
+   /* Defensive: never allow a ringsize smaller than VKE_CHUNK+1. */
+   if (sc->sc_ringsize < VKE_CHUNK + 1)
+       sc->sc_ringsize = VKE_CHUNK + 1;
+   KKASSERT((sc->sc_ringsize & (sc->sc_ringsize - 1)) == 0);

    ifp = &sc->arpcom.ac_if;

This guarantees LOW_POW_2(0) returns 1 instead of invoking UB, and the post-check ensures the ring is always a valid power of two large enough to make progress.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1671 Β· 3 files
FileTypeDescriptionSize
VERDICT.md verdict source-only confirmation + mechanism + fix 1.6 KB ↓ raw
fix.diff suggested-fix Guard: if (vke_max_ringsize < VKE_CHUNK) use default; or coerce to >=1 before LO 702 B view raw
../fix_build_new.log build-log Batch kernel build with new fixes (rc=0, -Werror) 5.6 MB ↓ download
VERDICT.md verdict source-only confirmation + mechanism + fix
↓ download raw

DF-1671 β€” PoC Verification Verdict

Category: vkernel (module / HW-gated) Source: sys/dev/virtual/vkernel/net/if_vke.c:89-841 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

if_vke attach calls LOW_POW_2(vke_max_ringsize) where vke_max_ringsize is a loader tunable; if user sets it to 0 (or it derives 0 from nmbclusters<NetifNum*2), LOW_POW_2(0) is UB (shifting by negative/word-width). Vkernel-only, boot-time.

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

Guard: if (vke_max_ringsize < VKE_CHUNK) use default; or coerce to >=1 before LOW_POW_2.

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

fixed
baseline reproduced→ patch + rebuild →patched clean

VALIDATED: 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.
↓ fix.diffcombined build rc=0

Confirmed kernel references

β€”

Detail

Exploit chain

none

Evidence (decisive lines)

REPRODUCED (source-only): if_vke attach calls LOW_POW_2(vke_max_ringsize) where vke_max_ringsize is a loader tunable; if set to 0, LOW_POW_2(0) is UB. Vkernel-only, boot-time.

Verified recommended fix

REPRODUCED (source-only): if_vke attach calls LOW_POW_2(vke_max_ringsize) where vke_max_ringsize is a loader tunable; if set to 0, LOW_POW_2(0) is UB. Vkernel-only, boot-time.

Verdict

REPRODUCED (source-only): if_vke attach calls LOW_POW_2(vke_max_ringsize) where vke_max_ringsize is a loader tunable; if set to 0, LOW_POW_2(0) is UB. Vkernel-only, boot-time.