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:
- 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_SIMPduringvmbus_synic_setupatvmbus.c:826-829). - Pick the slot at offset
VMBUS_SINT_MESSAGE * sizeof(struct vmbus_message) = 2 * 256 = 512bytes into the page. - Write
slot->msg_type = HYPERV_MSGTYPE_CHANNEL (1)andslot->msg_data[0..3] = VMBUS_CHANMSG_TYPE_VERSION_RESP (15, little-endian). Setmsg_flagsas desired. - Assert the SINT configured at
MSR_HV_SINT0 + VMBUS_SINT_MESSAGEwith the programmed vector (psc->intr_vecfromvmbus.c:846). - On the guest,
vmbus_intrruns, dispatches tovmbus_chan_msgproc, which callsvmbus_msghc_wakeup, which dereferencesNULL mhc_activeand the guest panics.
Expected result: guest kernel panic, either:
panic: no pending msg hypercall(INVARIANTSbuild), 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.
Recommended fix
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)
PoC verification
Evidence pack
findings/poc/DF-1668 Β· 4 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 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
fixedCombined 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.
Confirmed kernel references
- s
- y
- s
- /
- d
- e
- v
- /
- v
- i
- r
- t
- u
- a
- l
- /
- h
- y
- p
- e
- r
- v
- /
- v
- m
- b
- u
- s
- /
- v
- m
- b
- u
- s
- .
- c
- :
- 1
- 0
- 2
- 0
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.
No comments yet.