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.
Recommended fix
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)
PoC verification
Evidence pack
findings/poc/DF-1671 Β· 3 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 |
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
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): 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.
No comments yet.