NetBSD Problem Report #60655
From zakinko@bambi.snowrabbit.org Thu Aug 27 01:55:28 2026
Return-Path: <zakinko@bambi.snowrabbit.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 "YR2" (not verified))
by mollari.NetBSD.org (Postfix) with ESMTPS id B66AE1A923B
for <gnats-bugs@gnats.NetBSD.org>; Thu, 27 Aug 2026 01:55:28 +0000 (UTC)
Message-Id: <20260827015524.C5FE9C92F0@bambi.snowrabbit.org>
Date: Thu, 27 Aug 2026 10:55:24 +0900 (JST)
From: Showta Ishizaki <zakinko@snowrabbit.org>
To: gnats-bugs@NetBSD.org
Cc: zakinko@snowrabbit.org
Subject: port-arm/: vmt(4) probes the VMware backdoor unconditionally on aarch64 and panics
X-Send-Pr-Version: 3.95
X-From4GNATS: "Showta Ishizaki via gnats" <gnats-admin@NetBSD.org>
>Number: 60655
>Category: port-arm
>Synopsis: vmt(4) probes the VMware backdoor unconditionally on aarch64 and panics the kernel on hypervisors that do not implement it
>Confidential: no
>Severity: serious
>Priority: medium
>Responsible: port-arm-maintainer
>State: analyzed
>Class: sw-bug
>Submitter-Id: net
>Arrival-Date: Thu Aug 27 02:00:00 +0000 2026
>Closed-Date:
>Last-Modified: Sat Sep 12 12:50:01 +0000 2026
>Originator: Showta Ishizaki
>Release: NetBSD 11.99.7 (also NetBSD 11.0)
>Organization:
>Environment:
System: NetBSD 11.99.7 (GENERIC64) evbarm/aarch64, built from -current
Architecture: aarch64
Machine: evbarm
Host: qemu-system-aarch64 -M virt -accel hvf -cpu host, Apple M4, macOS
Also reproduced with the released NetBSD 11.0 evbarm-aarch64 arm64.img.
>Description:
sys/arch/aarch64/aarch64/vmt.c attaches at cpufeaturebus and its match
routine calls vmt_probe() with no guard:
static int
vmt_match(device_t parent, cfdata_t match, void *aux)
{
/* vmt should not attach to more than a single CPU. */
if (vmt_attached)
return 0;
return vmt_probe();
}
vmt_probe() reaches BACKDOOR_OP_AARCH64 in sys/dev/vmt/vmtvar.h, whose
operative instruction is
mrs xzr, mdccsr_el0
On VMware this access is intercepted by the hypervisor and becomes the
backdoor call. Elsewhere it is an ordinary read of a real system
register, and a hypervisor that neither implements nor emulates it
delivers an undefined instruction exception instead. Apple's
Hypervisor.framework does exactly that, and since the access happens at
EL1, trap_el1h_sync() falls through to its default case and panics before
the root file system is mounted:
[ 1.0000000] cpu0 at acpi0: unknown CPU (ID = 0x610f0000), id 0x0
[ 1.0000000] cpu0: package 0, core 0, smt 0, numa 0
[ 1.0000000] panic: Trap: fatal Unknown Reason (Illegal Instruction):
pc=ffffc000003fa318 sp=ffffc000010262e0 esr=02000000
[ 1.0000000] fp ffffc00001026880 config_found_acquire() at ...
[ 1.0000000] fp ffffc00001026910 acpi_fdt_attach() at ...
[ 1.0000000] cpu0: End traceback...
Stopped in pid 0.0 (system) at netbsd:cpu_Debugger+0xc
pc resolves to vm_cmd+0x28.
The same unconditional probe on x86 is harmless because the backdoor there
is an inl from an I/O port, and reading an unimplemented port returns
0xffffffff rather than faulting. aarch64 offers no equivalent guarantee,
so the same shape of code is not safe here.
This is not specific to Apple's hypervisor. Any aarch64 hypervisor that
does not implement the VMware backdoor will panic NetBSD at boot the same
way.
>How-To-Repeat:
On an Apple silicon host with qemu installed:
ftp https://cdn.NetBSD.org/pub/NetBSD/NetBSD-11.0/evbarm-aarch64/binary/gzimg/arm64.img.gz
gunzip arm64.img.gz
qemu-img resize -f raw arm64.img 40G
cp /opt/homebrew/share/qemu/edk2-aarch64-code.fd .
qemu-img create -f raw varstore.img 64M
qemu-system-aarch64 -M virt -accel hvf -cpu host -smp 4 -m 4G \
-drive if=pflash,format=raw,readonly=on,file=edk2-aarch64-code.fd \
-drive if=pflash,format=raw,file=varstore.img \
-drive if=none,file=arm64.img,format=raw,id=hd0 \
-device virtio-blk-pci,drive=hd0 -nographic
The kernel panics as above. -M virt,acpi=off panics in the same place.
The same image with -accel tcg -cpu cortex-a72 boots to a login prompt.
That is the control: the image and qemu are fine, and the only difference
is the hypervisor.
>Fix:
Two pieces. NetBSD already has the machinery for "poke something that may
not be there": cpu_set_onfault() and the faultbuf that bus_space_peek()
uses, and trap_el1h_error() already consults it for an SError. It was
simply never wired up for an undefined instruction.
The first hunk does that wiring; the second has the probe use it. Built
from -current and verified under -accel hvf on an Apple M4: the kernel now
walks past vmt and reaches a login prompt.
--- sys/arch/aarch64/aarch64/trap.c
+++ sys/arch/aarch64/aarch64/trap.c
@@ -245,6 +245,24 @@
#endif
break;
+ case ESR_EC_UNKNOWN: {
+ /*
+ * An undefined instruction in the kernel is normally fatal,
+ * but a driver may be probing for an instruction that only
+ * some machines implement, in the same way bus_space_peek()
+ * probes for a device that may not be there. Let such a
+ * probe unwind through its faultbuf, as trap_el1h_error()
+ * already does for an SError.
+ */
+ struct faultbuf * const fb = cpu_disable_onfault();
+
+ if (fb != NULL) {
+ cpu_jump_onfault(tf, fb, EFAULT);
+ return;
+ }
+ goto fatal;
+ }
+
case ESR_EC_FP_ACCESS:
if ((curlwp->l_flag & (LW_SYSTEM|LW_SYSTEM_FPU)) ==
(LW_SYSTEM|LW_SYSTEM_FPU)) {
@@ -258,6 +276,7 @@
case ESR_EC_ILL_STATE:
case ESR_EC_BTE_A64:
default:
+ fatal:
panic("Trap: fatal %s: pc=%016" PRIx64 " sp=%016" PRIx64
" esr=%08x", eclass_trapname(eclass), tf->tf_pc, tf->tf_sp,
esr);
--- sys/arch/aarch64/aarch64/vmt.c
+++ sys/arch/aarch64/aarch64/vmt.c
@@ -33,6 +33,8 @@
#include <sys/device.h>
#include <sys/module.h>
+#include <aarch64/machdep.h>
+
#include <dev/vmt/vmtreg.h>
#include <dev/vmt/vmtvar.h>
@@ -48,11 +50,25 @@
static int
vmt_match(device_t parent, cfdata_t match, void *aux)
{
+ struct faultbuf fb;
+ int rv;
+
/* vmt should not attach to more than a single CPU. */
if (vmt_attached)
return 0;
- return vmt_probe();
+ /*
+ * The backdoor VMware listens on is an access to a real system
+ * register. A hypervisor that does not implement it delivers an
+ * undefined instruction exception instead, so run the probe under a
+ * faultbuf and read a fault as "not VMware".
+ */
+ if (cpu_set_onfault(&fb) != 0)
+ return 0;
+ rv = vmt_probe();
+ cpu_unset_onfault();
+
+ return rv;
}
>Release-Note:
>Audit-Trail:
State-Changed-From-To: open->feedback
State-Changed-By: skrll@NetBSD.org
State-Changed-When: Fri, 04 Sep 2026 06:57:08 +0000
State-Changed-Why:
Thanks for the report.
This doesn't fail for me. What version of qemu-system-aarch64 are you using?
From: zakinko@snowrabbit.org
To: gnats-bugs@NetBSD.org
Cc: port-arm-maintainer@NetBSD.org, netbsd-bugs@NetBSD.org,
gnats-admin@NetBSD.org, skrll@NetBSD.org, zakinko@snowrabbit.org
Subject: Re: port-arm/60655: vmt(4) probes the VMware backdoor unconditionally on aarch64 and panics the kernel on hypervisors that do not implement it
Date: Sat, 5 Sep 2026 00:40:00 +0900 (JST)
qemu-system-aarch64 11.1.0 from Homebrew, on macOS 26.6.2 (build 25G83),
on an Apple M4. The accelerator is what matters here, and I have now
measured both.
-accel hvf panics
-accel tcg boots to login
Same image, same GENERIC64 kernel (NetBSD 11.99.7; 11.0 behaves the same).
The only difference is the accelerator, so if you were using TCG that would
explain why it does not fail for you.
With hvf:
qemu-system-aarch64 \
-M virt -accel hvf -cpu host -smp 6 -m 8G \
-drive if=pflash,format=raw,readonly=on,file=edk2-aarch64-code.fd \
-drive if=pflash,format=raw,file=varstore.img \
-drive if=none,file=arm64.img,format=raw,id=hd0 \
-device virtio-blk-pci,drive=hd0 \
-display none -serial unix:console.sock,server,nowait
[ 1.0000000] cpu0 at acpi0: unknown CPU (ID = 0x610f0000), id 0x0
[ 1.0000000] panic: Trap: fatal Unknown Reason (Illegal Instruction)
With tcg, the same invocation but -accel tcg -cpu cortex-a72:
[ 1.0000000] cpu0 at acpi0: Arm Cortex-A72 r0p3 (v8-A), id 0x0
...
login:
The read of `mrs xzr, mdccsr_el0` succeeds under TCG because QEMU implements
that system register itself, so vmt_probe() returns and vmt(4) simply decides
it is not running under VMware. Under hvf the instruction reaches the real
CPU through Hypervisor.framework, nothing intercepts it, and it raises an
undefined instruction exception at EL1.
The CPU line differs too: hvf reports the host M4 as an unknown CPU, TCG
reports the modelled Cortex-A72.
I cannot test KVM here; this host is macOS, where the only accelerators are
hvf and tcg.
One caveat on the TCG run, so as not to overstate it: it panicked once with
"cpu0: softints stuck for 16 seconds" and rebooted, which I take to be TCG
being slow rather than anything to do with vmt(4). It reached login on the
following boot. There is no vmt-related panic anywhere in that log.
From: Nick Hudson <nick.hudson@gmx.co.uk>
To: zakinko@snowrabbit.org,
"gnats-bugs@netbsd.org" <gnats-bugs@NetBSD.org>
Cc: "port-arm-maintainer@netbsd.org" <port-arm-maintainer@NetBSD.org>,
"netbsd-bugs@netbsd.org" <netbsd-bugs@NetBSD.org>,
"gnats-admin@netbsd.org" <gnats-admin@NetBSD.org>
Subject: Re: port-arm/60655: vmt(4) probes the VMware backdoor unconditionally
on aarch64 and panics the kernel on hypervisors that do not implement it
Date: Fri, 4 Sep 2026 17:02:46 +0100
On 04/09/2026 17:40, zakinko@snowrabbit.org wrote:
> qemu-system-aarch64 11.1.0 from Homebrew, on macOS 26.6.2 (build =
25G83),
> on an Apple M4.
I tested qemu-system-aarch64 10.2 from homebrew. I see the error with =
11.1.
I think this is related to qemu 11.1 support for
ARM: =E2=80=98virt=E2=80=99 board support for specifying cache topology, =
=E2=80=98hvf=E2=80=99 accelerator now supports nested virtualization and =
vGIC
From: zakinko@snowrabbit.org
To: gnats-bugs@NetBSD.org
Cc: port-arm-maintainer@NetBSD.org, netbsd-bugs@NetBSD.org,
gnats-admin@NetBSD.org, skrll@NetBSD.org, zakinko@snowrabbit.org
Subject: Re: port-arm/60655: vmt(4) probes the VMware backdoor unconditionally on aarch64 and panics the kernel on hypervisors that do not implement it
Date: Sat, 5 Sep 2026 10:27:02 +0900 (JST)
You were right that the version is the variable, and it narrows further than
that. It is one commit, and it can be shown without rebuilding anything:
qemu 11.1.0, -M virt (= virt-11.1) panics
qemu 11.1.0, -M virt-11.0 boots
qemu 11.1.0, -M virt,kernel-irqchip=off boots
qemu 10.2.0, -M virt boots
Same host, same NetBSD 11.0 GENERIC64 arm64.img, same command line otherwise.
The commit is 37863fff59 ("hvf: arm: enable vGIC by default for virt-11.1 and
later"), which makes kernel-irqchip default to on for the new machine type.
With the vGIC in play the MDCCSR_EL0 read is trapped out to QEMU, and QEMU's
hvf path has no case for that register, so it injects an undefined
instruction. Its own trace point says so, at the pc from the panic:
hvf_unhandled_sysreg_read unhandled sysreg read at
pc=0xffffc000003fa318: 0x0020c002 (op0=2 op1=3 crn=0 crm=1 op2=0)
The TCG path does not do this: debug_helper.c defines MDCCSR_EL0 as
ARM_CP_CONST with resetvalue 0, and the comment there says it is RAZ
specifically to avoid spurious SIGILLs "as the support cannot be probed for".
I have sent a patch to qemu-devel making hvf do the same, under the subject
"target/arm/hvf: implement MDCCSR_EL0 as RAZ"; it has not appeared in the
archive yet. It is not fixed upstream either: master is 1168 commits past
v11.1.0 and none of them touch accel/hvf or target/arm/hvf, and 11.1.1 has
none.
That said, I do not think this is only QEMU's problem, and I would like your
opinion on the NetBSD side.
A hypervisor is free to trap MDCCSR_EL0 and refuse it, and QEMU's own comment
is the point: the guest cannot ask beforehand. The register is
architecturally mandatory, so testing for its presence answers "yes" and the
probe dies all the same. On x86 the same driver is safe by construction --
the backdoor there is an inl from an unused I/O port, which cannot fault -- so
the exposure is specific to aarch64, where the probe is an instruction a
hypervisor can decline.
The machinery to survive it is already in the tree. trap_el1h_error() does
exactly this for SErrors, with a comment saying it would otherwise panic
unconditionally but bus_space_peek(9) has to probe. trap_el1h_sync() has no
such escape, so ESR_EC_UNKNOWN falls to the default arm and panics.
Built from trunk, cross-compiled for evbarm-aarch64, -Werror -Wall clean.
With that kernel, a stock unpatched QEMU 11.1.0 on -M virt -accel hvf reaches
login, while the trace point above still fires once -- so the kernel is
surviving the injected undefined instruction, rather than the register having
become readable.
One thing I am unsure of, and would rather raise myself: this reuses the
onfault machinery, which copyin/copyout also use. A genuine kernel bug that
executed an undefined instruction while an onfault was set would now return
EFAULT instead of panicking. A dedicated flag would be narrower. I went this
way because trap_el1h_error() already took the broad approach for the same
kind of probe, but if you would rather have something narrower I will redo it.
--- sys/arch/aarch64/aarch64/trap.c.orig
+++ sys/arch/aarch64/aarch64/trap.c
@@ -244,6 +244,25 @@
panic("No debugger in kernel");
#endif
break;
+
+ case ESR_EC_UNKNOWN:
+ /*
+ * An undefined instruction in the kernel is normally fatal,
+ * but a driver may have to execute one on purpose: an
+ * instruction that some hypervisors answer and others refuse,
+ * with no way to ask beforehand. vmt(4) does this to look
+ * for the VMware backdoor. Let such a probe recover the way
+ * bus_space_peek(9) recovers from an SError, see
+ * trap_el1h_error() below.
+ */
+ if (curcpu()->ci_intr_depth == 0) {
+ struct faultbuf * const fb = cpu_disable_onfault();
+ if (fb != NULL) {
+ cpu_jump_onfault(tf, fb, EFAULT);
+ break;
+ }
+ }
+ goto fatal;
case ESR_EC_FP_ACCESS:
if ((curlwp->l_flag & (LW_SYSTEM|LW_SYSTEM_FPU)) ==
@@ -258,6 +277,7 @@
case ESR_EC_ILL_STATE:
case ESR_EC_BTE_A64:
default:
+ fatal:
panic("Trap: fatal %s: pc=%016" PRIx64 " sp=%016" PRIx64
" esr=%08x", eclass_trapname(eclass), tf->tf_pc, tf->tf_sp,
esr);
--- sys/arch/aarch64/aarch64/vmt.c.orig
+++ sys/arch/aarch64/aarch64/vmt.c
@@ -33,6 +33,8 @@
#include <sys/device.h>
#include <sys/module.h>
+#include <aarch64/machdep.h>
+
#include <dev/vmt/vmtreg.h>
#include <dev/vmt/vmtvar.h>
@@ -48,11 +50,27 @@
static int
vmt_match(device_t parent, cfdata_t match, void *aux)
{
+ struct faultbuf fb;
+ int rv;
+
/* vmt should not attach to more than a single CPU. */
if (vmt_attached)
return 0;
- return vmt_probe();
+ /*
+ * On aarch64 the backdoor is a read of MDCCSR_EL0 with a magic
+ * value in x7. The register is mandatory, but a hypervisor is
+ * free to trap it and answer with an undefined instruction, and
+ * the guest cannot ask which one it is on beforehand. QEMU 11.1
+ * with the Apple vGIC does exactly that, and the probe took the
+ * whole kernel down with it. Catch the trap and report no vmt.
+ */
+ if (cpu_set_onfault(&fb) != 0)
+ return 0;
+ rv = vmt_probe();
+ cpu_unset_onfault();
+
+ return rv;
}
static void
From: Nick Hudson <nick.hudson@gmx.co.uk>
To: gnats-bugs@netbsd.org,
port-arm-maintainer@netbsd.org,
gnats-admin@netbsd.org,
netbsd-bugs@netbsd.org,
Showta Ishizaki <zakinko@snowrabbit.org>
Cc:
Subject: Re: port-arm/60655: vmt(4) probes the VMware backdoor unconditionally
on aarch64 and panics the kernel on hypervisors that do not implement it
Date: Sat, 5 Sep 2026 13:49:45 +0100
On 05/09/2026 03:30, zakinko@snowrabbit.org via gnats wrote:
[snip]
> I have sent a patch to qemu-devel making hvf do the same, under the =
subject
> "target/arm/hvf: implement MDCCSR_EL0 as RAZ"; it has not appeared in =
the
> archive yet. It is not fixed upstream either: master is 1168 commits =
past
> v11.1.0 and none of them touch accel/hvf or target/arm/hvf, and 11.1.1 =
has
> none.
>=20
Definitely a qemu bug and I see Mohamed Mediouni agrees.
https://lists.gnu.org/archive/html/qemu-devel/2026-09/msg01794.html
>=20
> That said, I do not think this is only QEMU's problem, and I would =
like your
> opinion on the NetBSD side.
The patch is not correct.
We can do better by not probing when
arm64# acpidump -dt | grep HypervisorId
HypervisorId=3D0x00000000554d4551
arm64#
State-Changed-From-To: feedback->analyzed
State-Changed-By: skrll@NetBSD.org
State-Changed-When: Sat, 05 Sep 2026 13:57:49 +0000
State-Changed-Why:
Problem is understood
From: zakinko@snowrabbit.org
To: gnats-bugs@NetBSD.org
Cc: port-arm-maintainer@NetBSD.org, netbsd-bugs@NetBSD.org,
gnats-admin@NetBSD.org, skrll@NetBSD.org, zakinko@snowrabbit.org
Subject: Re: port-arm/60655: vmt(4) probes the VMware backdoor unconditionally on aarch64 and panics the kernel on hypervisors that do not implement it
Date: Sun, 6 Sep 2026 11:42:58 +0900 (JST)
Thanks for the pointer to HypervisorId -- that was the right place to look.
I have rewritten the patch to read the FADT instead: the old version let
the probe run and recovered from the trap, while this one never executes
the instruction. On stock QEMU 11.1.0 (-M virt -accel hvf) the guest
reaches login with no hvf_unhandled_sysreg_read firing, where the old patch
left one trap per boot.
I checked all three cases on real hypervisors, not QEMU alone. On VMware
Fusion 26 (Apple Silicon), with the same kernel:
HypervisorId = 0x0000657261774d56 = "VMware"
vmt0 at cpu0: VMware Tools driver
vmt0: UUID: 564de88b-...
so the comparison matches a real VMware guest and vmt(4) attaches and talks
to the backdoor as before. With the FADT string forced to "VMware" or left
at zero on QEMU, vmt_probe() still runs (and panics there, since that QEMU
refuses MDCCSR_EL0) -- so the check is not just disabling vmt(4), and a
hypervisor that leaves the field empty keeps the old behaviour.
Let me know if this isn't the shape you had in mind.
--- sys/arch/aarch64/aarch64/vmt.c.orig
+++ sys/arch/aarch64/aarch64/vmt.c
@@ -32,7 +32,16 @@
#include <sys/device.h>
#include <sys/module.h>
+#include <sys/systm.h>
+#ifdef _KERNEL_OPT
+#include "acpica.h"
+#endif
+
+#if NACPICA > 0
+#include <dev/acpi/acpivar.h>
+#endif
+
#include <dev/vmt/vmtreg.h>
#include <dev/vmt/vmtvar.h>
@@ -45,6 +54,39 @@
static bool vmt_attached = false;
+#if NACPICA > 0
+/*
+ * The ACPI 6.0 FADT carries a Hypervisor Vendor Identity: eight bytes of
+ * ASCII, NUL padded. Skip the probe when the hypervisor identifies
+ * itself as something other than VMware; there is nothing there to find.
+ *
+ * On aarch64 the backdoor is a read of MDCCSR_EL0 with a magic value in
+ * x7. The register is architecturally mandatory, but a hypervisor is
+ * free to trap it and answer with an undefined instruction rather than a
+ * value, and that trap is fatal in kernel mode. QEMU 11.1 does exactly
+ * this once the Apple vGIC is in use, so the machine panics during
+ * autoconfiguration. See port-arm/60655.
+ *
+ * Firmware that leaves the field zero tells us nothing, so probe as
+ * before. The bytes are compared in table order rather than through the
+ * UINT64, so that this reads the same on aarch64eb.
+ */
+static bool
+vmt_hypervisor_may_be_vmware(void)
+{
+ char name[sizeof(AcpiGbl_FADT.HypervisorId) + 1];
+
+ if (AcpiGbl_FADT.HypervisorId == 0)
+ return true;
+
+ memcpy(name, &AcpiGbl_FADT.HypervisorId,
+ sizeof(AcpiGbl_FADT.HypervisorId));
+ name[sizeof(name) - 1] = '\0';
+
+ return strncmp(name, "VMware", 6) == 0;
+}
+#endif /* NACPICA > 0 */
+
static int
vmt_match(device_t parent, cfdata_t match, void *aux)
{
@@ -52,6 +94,11 @@
if (vmt_attached)
return 0;
+#if NACPICA > 0
+ if (!vmt_hypervisor_may_be_vmware())
+ return 0;
+#endif
+
return vmt_probe();
}
From: Nick Hudson <nick.hudson@gmx.co.uk>
To: gnats-bugs@netbsd.org,
port-arm-maintainer@netbsd.org,
gnats-admin@netbsd.org,
netbsd-bugs@netbsd.org,
Showta Ishizaki <zakinko@snowrabbit.org>
Cc:
Subject: Re: port-arm/60655: vmt(4) probes the VMware backdoor unconditionally
on aarch64 and panics the kernel on hypervisors that do not implement it
Date: Sun, 6 Sep 2026 11:56:50 +0100
Thanks for doing this.
Think you need to check the FADT version is at least 6 too, though.
From: zakinko@snowrabbit.org
To: gnats-bugs@NetBSD.org
Cc: port-arm-maintainer@NetBSD.org, netbsd-bugs@NetBSD.org,
gnats-admin@NetBSD.org, nick.hudson@gmx.co.uk, zakinko@snowrabbit.org
Subject: Re: port-arm/60655: vmt(4) probes the VMware backdoor unconditionally on aarch64 and panics the kernel on hypervisors that do not implement it
Date: Mon, 7 Sep 2026 02:53:44 +0900 (JST)
Thanks for catching that -- I had missed it. I read the pointer as:
HypervisorId only exists from FADT revision 6, so the revision (and the
length) should be checked before the field is read. Is that the right
understanding?
Two things I went on to do. I am not confident about either, so I would
be grateful for your view:
- The FADT reading moved into a small helper in dev/acpi,
acpi_fadt_hypervisor_id(), so that the next user of the field need not
copy it.
- vmt_match() now probes only when the firmware names VMware, rather
than skipping only when it names something else. The difference shows
on a hypervisor that traps MDCCSR_EL0 but leaves the field zero: the
earlier form still panics there (I checked with a QEMU 11.1.0 patched
to write zeros). The ACPI 6.0 note for the field reads to me as
"probe only on a vendor you recognise", but I may be reading too much
into it -- if you would rather keep the old behaviour on a zero field,
it is a single condition to flip.
Rebuilt from trunk, -Werror -Wall clean. Boots on stock QEMU 11.1 (hvf;
"QEMU", vmt does not attach), on the zero-field QEMU, and on VMware
Fusion 26 ("VMware", vmt0 attaches and answers the host).
--- sys/dev/acpi/acpi_util.h.orig
+++ sys/dev/acpi/acpi_util.h
@@ -114,4 +114,6 @@
ACPI_STATUS acpi_claim_childdevs(device_t, struct acpi_devnode *,
const char *);
+bool acpi_fadt_hypervisor_id(char *, size_t);
+
#endif /* !_SYS_DEV_ACPI_ACPI_UTIL_H */
--- sys/dev/acpi/acpi_util.c.orig
+++ sys/dev/acpi/acpi_util.c
@@ -1422,3 +1422,42 @@
return AE_OK;
}
+
+/*
+ * Return the Hypervisor Vendor Identity from the FADT as a NUL-terminated
+ * string, or false if there is none to report.
+ *
+ * ACPI 6.0 added the field (5.2.9, FADT Format, offset 268): eight bytes
+ * that name the hypervisor vendor, "usually following the name of the
+ * hypervisor product", with no version information in it. QEMU writes
+ * "QEMU" and VMware writes "VMware", zero padded. Firmware places zero
+ * bytes in it when no hypervisor is present, so zero is reported as
+ * absence. The specification's own note is that a guest "can consult it
+ * and act on the result, based on whether it recognized the vendor",
+ * which is what a caller does with the string.
+ *
+ * The field only exists from FADT revision 6. ACPICA zeroes its copy of
+ * the FADT before filling it from the firmware's table, so on an older,
+ * shorter table the field reads as zero rather than as garbage. That is
+ * a property of the copy and not something to lean on, so both the
+ * revision and the length are checked before the field is read, as
+ * open-vm-tools does. The bytes are copied in table order rather than
+ * through the UINT64, so that this reads the same on big-endian machines.
+ */
+bool
+acpi_fadt_hypervisor_id(char *buf, size_t buflen)
+{
+ const size_t len = sizeof(AcpiGbl_FADT.HypervisorId);
+
+ KASSERT(buflen > len);
+
+ if (AcpiGbl_FADT.Header.Revision < 6 ||
+ AcpiGbl_FADT.Header.Length < ACPI_FADT_V6_SIZE)
+ return false;
+ if (AcpiGbl_FADT.HypervisorId == 0)
+ return false;
+
+ memcpy(buf, &AcpiGbl_FADT.HypervisorId, len);
+ buf[len] = '\0';
+ return true;
+}
--- sys/arch/aarch64/aarch64/vmt.c.orig
+++ sys/arch/aarch64/aarch64/vmt.c
@@ -32,7 +32,16 @@
#include <sys/device.h>
#include <sys/module.h>
+#include <sys/systm.h>
+#ifdef _KERNEL_OPT
+#include "acpica.h"
+#endif
+
+#if NACPICA > 0
+#include <dev/acpi/acpivar.h>
+#endif
+
#include <dev/vmt/vmtreg.h>
#include <dev/vmt/vmtvar.h>
@@ -48,10 +57,29 @@
static int
vmt_match(device_t parent, cfdata_t match, void *aux)
{
+#if NACPICA > 0
+ char hv[sizeof(AcpiGbl_FADT.HypervisorId) + 1];
+#endif
+
/* vmt should not attach to more than a single CPU. */
if (vmt_attached)
return 0;
+#if NACPICA > 0
+ /*
+ * The backdoor is a read of MDCCSR_EL0 with a magic value in x7. A
+ * hypervisor other than VMware is free to answer that read with an
+ * undefined instruction trap, which is fatal in kernel mode; QEMU
+ * does so once the Apple vGIC is in use. Probe only when the
+ * firmware names VMware; a hypervisor that names something else,
+ * or nothing, is not one whose API this driver knows. See
+ * port-arm/60655.
+ */
+ if (!acpi_fadt_hypervisor_id(hv, sizeof(hv)) ||
+ strncmp(hv, "VMware", 6) != 0)
+ return 0;
+#endif
+
return vmt_probe();
}
From: Nick Hudson <nick.hudson@gmx.co.uk>
To: zakinko@snowrabbit.org,
"gnats-bugs@netbsd.org" <gnats-bugs@NetBSD.org>
Cc: "port-arm-maintainer@netbsd.org" <port-arm-maintainer@NetBSD.org>,
"netbsd-bugs@netbsd.org" <netbsd-bugs@NetBSD.org>,
"gnats-admin@netbsd.org" <gnats-admin@NetBSD.org>
Subject: Re: port-arm/60655: vmt(4) probes the VMware backdoor unconditionally
on aarch64 and panics the kernel on hypervisors that do not implement it
Date: Thu, 10 Sep 2026 07:50:57 +0100
--Apple-Mail=_AEE1818F-833B-4FFD-BCBD-5418E5C5662C
Content-Transfer-Encoding: quoted-printable
Content-Type: text/plain;
charset=utf-8
On 06/09/2026 19:53, zakinko@snowrabbit.org =
<mailto:zakinko@snowrabbit.org> wrote:
> Thanks for catching that -- I had missed it. I read the pointer as:
> HypervisorId only exists from FADT revision 6, so the revision (and =
the
> length) should be checked before the field is read. Is that the right
> understanding?
>=20
> Two things I went on to do. I am not confident about either, so I =
would
> be grateful for your view:
>=20
> - The FADT reading moved into a small helper in dev/acpi,
> acpi_fadt_hypervisor_id(), so that the next user of the field need =
not
> copy it.
Not a big fan of this helper, but OK, I guess.
>=20
> - vmt_match() now probes only when the firmware names VMware, rather
> than skipping only when it names something else.
Sounds good.
> The difference shows
> on a hypervisor that traps MDCCSR_EL0 but leaves the field zero: the
> earlier form still panics there (I checked with a QEMU 11.1.0 =
patched
> to write zeros).
Any hypervisor that doesn=E2=80=99t provide a RAZ MDCCSR_EL0 is broken.
> The ACPI 6.0 note for the field reads to me as
> "probe only on a vendor you recognise", but I may be reading too =
much
> into it -- if you would rather keep the old behaviour on a zero =
field,
> it is a single condition to flip.
>=20
I think the code in the message is fine.
> =20
> +#if NACPICA > 0
> + /*
> + =
... A
> + * hypervisor other than VMware is free to answer that read with =
an
> + * undefined instruction trap, which is fatal in kernel mode; =
QEMU
> + * does so once the Apple vGIC is in use.
This is wrong.
QEMU was broken - it is now hopefully fixed with your diff.=
--Apple-Mail=_AEE1818F-833B-4FFD-BCBD-5418E5C5662C
Content-Transfer-Encoding: quoted-printable
Content-Type: text/html;
charset=utf-8
<html aria-label=3D"message body"><head><meta http-equiv=3D"content-type" =
content=3D"text/html; charset=3Dutf-8"></head><body =
style=3D"overflow-wrap: break-word; -webkit-nbsp-mode: space; =
line-break: after-white-space;">
=20
<meta http-equiv=3D"Content-Type" content=3D"text/html; =
charset=3DUTF-8">
=20
<div><p><br>
</p>
<div class=3D"moz-cite-prefix">On 06/09/2026 19:53,
<a class=3D"moz-txt-link-abbreviated" =
href=3D"mailto:zakinko@snowrabbit.org">zakinko@snowrabbit.org</a> =
wrote:<br>
</div>
<blockquote type=3D"cite" =
cite=3D"mid:20260906175344.36CA7C9311@bambi.snowrabbit.org">
<pre wrap=3D"" class=3D"moz-quote-pre">Thanks for catching that -- =
I had missed it. I read the pointer as:
HypervisorId only exists from FADT revision 6, so the revision (and the
length) should be checked before the field is read. Is that the right
understanding?
Two things I went on to do. I am not confident about either, so I would
be grateful for your view:
- The FADT reading moved into a small helper in dev/acpi,
acpi_fadt_hypervisor_id(), so that the next user of the field need not
copy it.</pre>
</blockquote><p>Not a big fan of this helper, but OK, I guess.</p>
<blockquote type=3D"cite" =
cite=3D"mid:20260906175344.36CA7C9311@bambi.snowrabbit.org">
<pre wrap=3D"" class=3D"moz-quote-pre">
- vmt_match() now probes only when the firmware names VMware, rather
than skipping only when it names something =
else.</pre></blockquote>Sounds good.<br><blockquote type=3D"cite" =
cite=3D"mid:20260906175344.36CA7C9311@bambi.snowrabbit.org"><pre wrap=3D""=
class=3D"moz-quote-pre"> The difference shows
on a hypervisor that traps MDCCSR_EL0 but leaves the field zero: the
earlier form still panics there (I checked with a QEMU 11.1.0 patched
to write zeros).</pre></blockquote><div><br></div>Any hypervisor that =
doesn=E2=80=99t provide a RAZ MDCCSR_EL0 is =
broken.</div><div><br><blockquote type=3D"cite" =
cite=3D"mid:20260906175344.36CA7C9311@bambi.snowrabbit.org"><pre wrap=3D""=
class=3D"moz-quote-pre"> The ACPI 6.0 note for the field reads to me =
as
"probe only on a vendor you recognise", but I may be reading too much
into it -- if you would rather keep the old behaviour on a zero field,
it is a single condition to flip.
</pre></blockquote>I think the code in the message is =
fine.<br><blockquote type=3D"cite" =
cite=3D"mid:20260906175344.36CA7C9311@bambi.snowrabbit.org"><pre wrap=3D""=
class=3D"moz-quote-pre">=20
+#if NACPICA > 0
+ /*
+ ... =
A
+ * hypervisor other than VMware is free to answer that read with =
an
+ * undefined instruction trap, which is fatal in kernel mode; =
QEMU
+ * does so once the Apple vGIC is in use.</pre></blockquote>This =
is wrong.</div><div><br></div><div>QEMU was broken - it is now hopefully =
fixed with your diff.</div>
</body></html>=
--Apple-Mail=_AEE1818F-833B-4FFD-BCBD-5418E5C5662C--
From: zakinko@snowrabbit.org
To: gnats-bugs@NetBSD.org
Cc: port-arm-maintainer@NetBSD.org, netbsd-bugs@NetBSD.org,
gnats-admin@NetBSD.org, nick.hudson@gmx.co.uk, zakinko@snowrabbit.org
Subject: Re: port-arm/60655: vmt(4) probes the VMware backdoor unconditionally on aarch64 and panics the kernel on hypervisors that do not implement it
Date: Fri, 11 Sep 2026 07:34:13 +0900 (JST)
Thanks for looking at it again.
I have taken out the sentence about a hypervisor being free to trap
the read. A hypervisor that does not give a RAZ MDCCSR_EL0 is simply
broken, and QEMU was; the comment in vmt_match() now says that and
notes that QEMU has fixed it (Peter Maydell applied the patch to
target-arm.next on 8 September with a cc:stable tag). Nothing else in
the diff changed.
I kept the helper since it seemed acceptable to you. If you would
rather have the FADT read inline in vmt_match(), please say so and I
will send that form instead.
Rebuilt GENERIC64 from the same trunk tree with the comment change,
-Werror -Wall clean, and booted it on stock QEMU 11.1.0 under hvf: it
reaches login, vmt does not attach, and QEMU reports no unhandled
sysreg read. The zero-field QEMU and VMware Fusion runs reported
before were with this code minus the comment, so I did not repeat
them.
--- sys/dev/acpi/acpi_util.h.orig
+++ sys/dev/acpi/acpi_util.h
@@ -114,4 +114,6 @@ ACPI_STATUS acpi_dsm_query(ACPI_HANDLE, uint8_t *, ACPI_INTEGER,
ACPI_STATUS acpi_claim_childdevs(device_t, struct acpi_devnode *,
const char *);
+bool acpi_fadt_hypervisor_id(char *, size_t);
+
#endif /* !_SYS_DEV_ACPI_ACPI_UTIL_H */
--- sys/dev/acpi/acpi_util.c.orig
+++ sys/dev/acpi/acpi_util.c
@@ -1422,3 +1422,42 @@ acpi_claim_childdevs(device_t dev, struct acpi_devnode *devnode,
return AE_OK;
}
+
+/*
+ * Return the Hypervisor Vendor Identity from the FADT as a NUL-terminated
+ * string, or false if there is none to report.
+ *
+ * ACPI 6.0 added the field (5.2.9, FADT Format, offset 268): eight bytes
+ * that name the hypervisor vendor, "usually following the name of the
+ * hypervisor product", with no version information in it. QEMU writes
+ * "QEMU" and VMware writes "VMware", zero padded. Firmware places zero
+ * bytes in it when no hypervisor is present, so zero is reported as
+ * absence. The specification's own note is that a guest "can consult it
+ * and act on the result, based on whether it recognized the vendor",
+ * which is what a caller does with the string.
+ *
+ * The field only exists from FADT revision 6. ACPICA zeroes its copy of
+ * the FADT before filling it from the firmware's table, so on an older,
+ * shorter table the field reads as zero rather than as garbage. That is
+ * a property of the copy and not something to lean on, so both the
+ * revision and the length are checked before the field is read, as
+ * open-vm-tools does. The bytes are copied in table order rather than
+ * through the UINT64, so that this reads the same on big-endian machines.
+ */
+bool
+acpi_fadt_hypervisor_id(char *buf, size_t buflen)
+{
+ const size_t len = sizeof(AcpiGbl_FADT.HypervisorId);
+
+ KASSERT(buflen > len);
+
+ if (AcpiGbl_FADT.Header.Revision < 6 ||
+ AcpiGbl_FADT.Header.Length < ACPI_FADT_V6_SIZE)
+ return false;
+ if (AcpiGbl_FADT.HypervisorId == 0)
+ return false;
+
+ memcpy(buf, &AcpiGbl_FADT.HypervisorId, len);
+ buf[len] = '\0';
+ return true;
+}
--- sys/arch/aarch64/aarch64/vmt.c.orig
+++ sys/arch/aarch64/aarch64/vmt.c
@@ -32,6 +32,15 @@
#include <sys/device.h>
#include <sys/module.h>
+#include <sys/systm.h>
+
+#ifdef _KERNEL_OPT
+#include "acpica.h"
+#endif
+
+#if NACPICA > 0
+#include <dev/acpi/acpivar.h>
+#endif
#include <dev/vmt/vmtreg.h>
#include <dev/vmt/vmtvar.h>
@@ -48,10 +57,29 @@ static bool vmt_attached = false;
static int
vmt_match(device_t parent, cfdata_t match, void *aux)
{
+#if NACPICA > 0
+ char hv[sizeof(AcpiGbl_FADT.HypervisorId) + 1];
+#endif
+
/* vmt should not attach to more than a single CPU. */
if (vmt_attached)
return 0;
+#if NACPICA > 0
+ /*
+ * The backdoor is a read of MDCCSR_EL0 with a magic value in x7.
+ * Probe only when the firmware names VMware; a hypervisor that
+ * names something else, or nothing, is not one whose API this
+ * driver knows, and there is nothing to find. (QEMU 11.1 under
+ * hvf also trapped the read as an undefined instruction, which is
+ * fatal in kernel mode. That was a QEMU bug, since fixed.) See
+ * port-arm/60655.
+ */
+ if (!acpi_fadt_hypervisor_id(hv, sizeof(hv)) ||
+ strncmp(hv, "VMware", 6) != 0)
+ return 0;
+#endif
+
return vmt_probe();
}
From: "Nick Hudson" <skrll@netbsd.org>
To: gnats-bugs@gnats.NetBSD.org
Cc:
Subject: PR/60655 CVS commit: src/sys
Date: Sat, 12 Sep 2026 12:47:59 +0000
Module Name: src
Committed By: skrll
Date: Sat Sep 12 12:47:59 UTC 2026
Modified Files:
src/sys/arch/aarch64/aarch64: vmt.c
src/sys/dev/acpi: acpi_util.c acpi_util.h
Log Message:
PR/60655: vmt(4) probes the VMware backdoor unconditionally on aarch64
On ACPI systems only probe for vmt(4) when Hypervisor_Id in FADT reports
VMWare.
This helps avoid buggy hypervisors that don't implement a RAZ mdccsr_el0
Patch from Showta Ishizaki and slightly updated by me.
To generate a diff of this commit:
cvs rdiff -u -r1.2 -r1.3 src/sys/arch/aarch64/aarch64/vmt.c
cvs rdiff -u -r1.37 -r1.38 src/sys/dev/acpi/acpi_util.c
cvs rdiff -u -r1.16 -r1.17 src/sys/dev/acpi/acpi_util.h
Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.
>Unformatted:
(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.