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

vmbus: NULL deref in vmbus_msghc_wakeup on host-injected spurious VERSION_RESP

Field Value
ID DF-1668
File sys/dev/virtual/hyperv/vmbus/vmbus.c
Lines 613, 621–624, 1014–1023
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-476 NULL Pointer Dereference
Confidence likely
Status new
CVE match dfly_specific (DFly VMBus port β€” incomplete TODO; KASSERT compiled out in production)
Created 2026-07-18

Summary

vmbus_chan_msgproc (vmbus.c:1014-1023) dispatches any host-supplied SynIC channel message with chm_type == VMBUS_CHANMSG_TYPE_VERSION_RESP straight into vmbus_msghc_wakeup, which assumes a guest-initiated INIT_CONTACT hypercall is currently in flight. There is no validation that one is. vmbus_msghc_wakeup reads mhc->mhc_active, KASSERTs it non-NULL, and unconditionally dereferences it.

In a production kernel KASSERT is a no-op (sys/systm.h:117), so the NULL dereference proceeds and the kernel takes a page fault on a low address (offsetof(struct vmbus_msghc, mh_resp0), which is non-zero but within the zero page), panicking the guest. In an INVARIANTS kernel the KASSERT itself panics. Either way: deterministic guest DoS from a single crafted SynIC message slot write + SINT trigger.

Root cause

The SynIC interrupt receive path trusts the host to send VERSION_RESP only in response to a guest INIT_CONTACT. vmbus_intr (vmbus.c:331-346) reads msg->msg_type from the host-shared message page (psc->message[VMBUS_SINT_MESSAGE]) and, for HYPERV_MSGTYPE_CHANNEL, calls vmbus_chan_msgproc (vmbus.c:341-342).

vmbus_chan_msgproc (vmbus.c:1019-1023) casts msg->msg_data to vmbus_chanmsg_hdr, reads chm_type, and on VMBUS_CHANMSG_TYPE_VERSION_RESP calls vmbus_msghc_wakeup(sc, msg) with no check on the current protocol state.

vmbus_msghc_wakeup (vmbus.c:613-628) does:

mh = mhc->mhc_active;
KASSERT(mh != NULL, ...);
memcpy(&mh->mh_resp0, msg, sizeof(mh->mh_resp0));
mh->mh_resp = &mh->mh_resp0;

Outside the brief window between vmbus_msghc_exec setting mhc_active (vmbus.c:583) and vmbus_msghc_wait_result clearing it (vmbus.c:606), mhc_active is NULL. At all other times β€” including idle steady state after attach, before any channel traffic, and between INIT_CONTACT probes β€” a host that writes {msg_type=HYPERV_MSGTYPE_CHANNEL, msg_data.chm_type=VMBUS_CHANMSG_TYPE_VERSION_RESP} into the guest's SynIC message slot and asserts the SINT will trigger this path.

The KASSERT at vmbus.c:622 is the only guard and it is compiled out without INVARIANTS.

Threat model

Attacker position: the Hyper-V host (or any entity that can write the guest's SynIC message page and assert the message SINT, e.g. a compromised host VSP). The standard VMBus trust model assumes a cooperative host, so under the strict threat model this is not a privilege boundary crossing β€” the host already has full guest-memory access.

The practical impact is therefore a deterministic guest kernel panic (denial of service) triggerable at any time, including steady state when no VMBus traffic is expected, by a single 4-byte message-slot field write plus one interrupt.

Reachability requires that the vmbus driver attached (Hyper-V guest with SynIC).

PoC

This is a malicious-host / host-compromise primitive, not a guest-locals or remote-network exploit; PoC is conceptual since it requires host-side control of the SynIC message page.

Sketch:

  1. From the host (or a compromised host VSP), locate the guest's SynIC message page for the target VP (the GFN programmed into MSR_HV_SIMP during vmbus_synic_setup at vmbus.c:826-829).
  2. Pick the slot at offset VMBUS_SINT_MESSAGE * sizeof(struct vmbus_message) = 2 * 256 = 512 bytes into the page.
  3. Write slot->msg_type = HYPERV_MSGTYPE_CHANNEL (1) and slot->msg_data[0..3] = VMBUS_CHANMSG_TYPE_VERSION_RESP (15, little-endian). Set msg_flags as desired.
  4. Assert the SINT configured at MSR_HV_SINT0 + VMBUS_SINT_MESSAGE with the programmed vector (psc->intr_vec from vmbus.c:846).
  5. On the guest, vmbus_intr runs, dispatches to vmbus_chan_msgproc, which calls vmbus_msghc_wakeup, which dereferences NULL mhc_active and the guest panics.

Expected result: guest kernel panic, either:

  • panic: no pending msg hypercall (INVARIANTS build), or
  • a NULL/low-address page-fault panic (production build)

Reproducible 100% of the time the host chooses to fire it.

Because the trigger is wholly outside the guest, no guest-side PoC binary is possible; the fix is the deliverable.

Defensive fix: do not trust the host's classification of the message; verify there is an active hypercall context before touching it. If there is none, silently drop the message (matching the existing TODO behaviour for unknown channel message types).

Applied at vmbus_chan_msgproc so the policy lives at the trust boundary:

--- a/sys/dev/virtual/hyperv/vmbus/vmbus.c
+++ b/sys/dev/virtual/hyperv/vmbus/vmbus.c
@@ -1014,6 +1014,15 @@ static void
 vmbus_chan_msgproc(struct vmbus_softc *sc, const struct vmbus_message *msg)
 {
    const struct vmbus_chanmsg_hdr *hdr;
+   struct vmbus_msghc_ctx *mhc;
+   int active = 0;

    hdr = (const struct vmbus_chanmsg_hdr *)msg->msg_data;

+   /*
+    * Don't trust the host: only deliver a VERSION_RESP if a
+    * guest-initiated INIT_CONTACT hypercall is actually pending.
+    * Without this check, a spurious VERSION_RESP from the host
+    * drives vmbus_msghc_wakeup into a NULL mhc_active deref.
+    */
+   mhc = sc->vmbus_msg_hc;
+   if (mhc != NULL) {
+       lwkt_gettoken(&mhc->mhc_active_token);
+       active = (mhc->mhc_active != NULL);
+       lwkt_reltoken(&mhc->mhc_active_token);
+   }
+
    /* TODO */
-   if (hdr->chm_type == VMBUS_CHANMSG_TYPE_VERSION_RESP)
+   if (hdr->chm_type == VMBUS_CHANMSG_TYPE_VERSION_RESP && active)
        vmbus_msghc_wakeup(sc, msg);
 }

Additionally, defense-in-depth inside vmbus_msghc_wakeup itself (so future callers and future channel-message types added under the TODO cannot reintroduce the panic): replace the bare KASSERT at vmbus.c:622 with a real check that drops the message instead of crashing:

--- a/sys/dev/virtual/hyperv/vmbus/vmbus.c
+++ b/sys/dev/virtual/hyperv/vmbus/vmbus.c
@@ -619,7 +628,12 @@ vmbus_msghc_wakeup(struct vmbus_softc *sc, const struct vmbus_message *msg)

    lwkt_gettoken(&mhc->mhc_active_token);

+   /*
+    * Be robust against a host that delivers VERSION_RESP when no
+    * hypercall is pending; KASSERT is a no-op in production builds.
+    */
    mh = mhc->mhc_active;
-   KASSERT(mh != NULL, ("no pending msg hypercall"));
+   if (mh == NULL) {
+       lwkt_reltoken(&mhc->mhc_active_token);
+       return;
+   }
    memcpy(&mh->mh_resp0, msg, sizeof(mh->mh_resp0));
    mh->mh_resp = &mh->mh_resp0;

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1668 Β· 4 files
FileTypeDescriptionSize
fix.diff suggested-fix Fix for vmbus msghc wakeup NULL deref 613 B view raw
VERDICT.md verdict Source-only verification verdict 820 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-1668: vmbus msghc wakeup NULL deref

Verdict

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

Mechanism

Host-supplied VERSION_RESP dispatched without checking active INIT_CONTACT; mhc_active NULL deref.

Source reference: sys/dev/virtual/hyperv/vmbus/vmbus.c:1014-1023.

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/virtual/hyperv/vmbus/vmbus.c:1020. Combined 41-fix kernel build rc=0 -Werror clean.

PoC changes

fix.diff authored; validated by combined kernel build.

Verified recommended fix

Guard msghc_wakeup with active check. Matches finding.

Verdict

REPRODUCED (source-confirmed). Host VERSION_RESP without active INIT -> mhc NULL deref. Cited path verified at sys/dev/virtual/hyperv/vmbus/vmbus.c:1020. HW/module-gated on QEMU guest.