NetBSD Problem Report #60887

From www@netbsd.org  Fri Oct 16 04:54:37 2026
Return-Path: <www@netbsd.org>
Received: from mail.netbsd.org (mail.netbsd.org [199.233.217.200])
	(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)
	 key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256
	 client-signature RSA-PSS (2048 bits) client-digest SHA256)
	(Client CN "mail.netbsd.org", Issuer "YR1" (not verified))
	by mollari.NetBSD.org (Postfix) with ESMTPS id 23BCD1A923B
	for <gnats-bugs@gnats.NetBSD.org>; Fri, 16 Oct 2026 04:54:37 +0000 (UTC)
Message-Id: <20261016045435.893E81A923C@mollari.NetBSD.org>
Date: Fri, 16 Oct 2026 04:54:35 +0000 (UTC)
From: david@crap.solutions
Reply-To: david@crap.solutions
To: gnats-bugs@NetBSD.org
Subject: nvmm(4): x86 VMX CR0 guest/host mask does not trap CR0.PE; guest mode switches leave VMCS state stale
X-Send-Pr-Version: www-1.0
X-From4GNATS: "david@crap.solutions via gnats" <gnats-admin@NetBSD.org>

>Number:         60887
>Category:       kern
>Synopsis:       nvmm(4): x86 VMX CR0 guest/host mask does not trap CR0.PE; guest mode switches leave VMCS state stale
>Confidential:   no
>Severity:       non-critical
>Priority:       low
>Responsible:    kern-bug-people
>State:          open
>Class:          sw-bug
>Submitter-Id:   net
>Arrival-Date:   Fri Oct 16 04:55:00 +0000 2026
>Originator:     David Bidner
>Release:        
>Organization:
-
>Environment:
>Description:
nvmm_x86_vmx.c sets the CR0 guest/host mask to CR0_STATIC_MASK (CR0_ET | CR0_NW | CR0_CD)
and reconstructs the previous guest CR0 from VMCS_CR0_SHADOW and VMCS_GUEST_CR0 using the
same mask. A CR0 write that changes only unmasked bits therefore does not cause a VM exit,
and the hardware applies CR0.PE directly while the VMCS guest state (and entry controls)
are not reconfigured by vmx_inkernel_handle_cr0().

A guest whose AP start-up enters protected mode by writing CR0 with PE set from real mode,
keeping the cache-disable bits at the values it reads back, hits exactly this: the AP faults
back into firmware instead of entering the guest kernel. GNU Mach does this (observed on
DragonFly's nvmm port as "Waiting for AP 1"; fixed there with the same change, where 32-bit
and 64-bit guests then boot with -smp 2). The same code, masks and shadow logic are present
in NetBSD-current.
>How-To-Repeat:
Run a guest whose firmware/AP start-up writes CR0 with only CR0.PE
changing relative to the shadowed value (e.g. GNU Mach under an nvmm build on the same
code); the AP does not enter the guest kernel. Alternatively audit vmx_inkernel_handle_cr0():
no VM exit occurs for PE-only writes, so the VMCS entry state update there is unreachable
for that transition.

>Fix:
Add CR0_PE to the trapped bits: define CR0_TRAP_MASK as (CR0_STATIC_MASK | CR0_PE), write
it into VMCS_CR0_MASK, and use it for the CR0 shadow computations in
vmx_inkernel_handle_cr0(), vmx_vcpu_setstate() and vmx_vcpu_getstate(). CR0.PG is
deliberately not trapped: that would break 32-bit PAE guests, whose PDPTEs this handler
does not load.

Found while fixing the same issue in DragonFly's nvmm port; the DragonFly change
was validated with 32-bit and 64-bit guests booting to login at -smp 2. The NetBSD side is
the same audit and the same change; not runtime-tested on NetBSD.

--- a/sys/dev/nvmm/x86/nvmm_x86_vmx.c
+++ b/sys/dev/nvmm/x86/nvmm_x86_vmx.c
@@ -730,6 +730,9 @@
 #define CR0_STATIC_MASK \
 	(CR0_ET | CR0_NW | CR0_CD)

+#define CR0_TRAP_MASK \
+	(CR0_STATIC_MASK | CR0_PE)
+
 #define CR4_VALID \
 	(CR4_VME |			\
 	 CR4_PVI |			\
@@ -1694,8 +1697,8 @@
 		vmx_vmwrite(VMCS_ENTRY_CTLS, ctls1);
 	}

-	oldcr0 = (vmx_vmread(VMCS_CR0_SHADOW) & CR0_STATIC_MASK) |
-	    (vmx_vmread(VMCS_GUEST_CR0) & ~CR0_STATIC_MASK);
+	oldcr0 = (vmx_vmread(VMCS_CR0_SHADOW) & CR0_TRAP_MASK) |
+	    (vmx_vmread(VMCS_GUEST_CR0) & ~CR0_TRAP_MASK);
 	if ((oldcr0 ^ fakecr0) & CR0_TLB_FLUSH) {
 		cpudata->gtlb_want_flush = true;
 	}
@@ -2794,7 +2797,7 @@
 		 * CR0_NW and CR0_CD must be 0 in the real register.
 		 */
 		vmx_vmwrite(VMCS_CR0_SHADOW,
-		    (state->crs[NVMM_X64_CR_CR0] & CR0_STATIC_MASK) |
+		    (state->crs[NVMM_X64_CR_CR0] & CR0_TRAP_MASK) |
 		    CR0_ET);
 		vmx_vmwrite(VMCS_GUEST_CR0,
 		    (state->crs[NVMM_X64_CR_CR0] & ~CR0_STATIC_MASK) |
@@ -2945,8 +2948,8 @@

 	if (flags & NVMM_X64_STATE_CRS) {
 		state->crs[NVMM_X64_CR_CR0] =
-		    (vmx_vmread(VMCS_CR0_SHADOW) & CR0_STATIC_MASK) |
-		    (vmx_vmread(VMCS_GUEST_CR0) & ~CR0_STATIC_MASK);
+		    (vmx_vmread(VMCS_CR0_SHADOW) & CR0_TRAP_MASK) |
+		    (vmx_vmread(VMCS_GUEST_CR0) & ~CR0_TRAP_MASK);
 		state->crs[NVMM_X64_CR_CR2] = cpudata->gcr2;
 		state->crs[NVMM_X64_CR_CR3] = vmx_vmread(VMCS_GUEST_CR3);
 		state->crs[NVMM_X64_CR_CR4] = vmx_vmread(VMCS_GUEST_CR4);
@@ -3140,7 +3143,7 @@
 	vmx_vmwrite(VMCS_EXIT_MSR_STORE_COUNT, VMX_MSRLIST_EXIT_NMSR);

 	/* Set the CR0 mask. Any change of these bits causes a VMEXIT. */
-	vmx_vmwrite(VMCS_CR0_MASK, CR0_STATIC_MASK);
+	vmx_vmwrite(VMCS_CR0_MASK, CR0_TRAP_MASK);

 	/* Force unsupported CR4 fields to zero. */
 	vmx_vmwrite(VMCS_CR4_MASK, CR4_INVALID);

NetBSD Home
NetBSD PR Database Search

(Contact us) $NetBSD: query-full-pr,v 1.51 2026/08/10 02:28:17 riastradh Exp $
$NetBSD: gnats_config.sh,v 1.10 2026/05/13 22:00:09 riastradh Exp $
Copyright © 1994-2026 The NetBSD Foundation, Inc. ALL RIGHTS RESERVED.