Ask QEMU whether it accepts the command line this package writes - #4
Merged
Conversation
Nothing did. The device set is decided in qemu/devices.mak and the device and
property names are typed out by hand in machine/machine.go — virtio-balloon-pci
with free-page-reporting and deflate-on-oom, virtio-mem-pci with requested-size,
vhost-vsock-pci, vmgenid, virtio-net-pci with romfile=, the ICH9-LPC globals, the
four -sandbox restrictions — and the two halves move independently. The package's
tests assert on the strings it produces, which proves it agrees with itself. A
device dropped from the allowlist, a property renamed at the next version bump
and a typo added here therefore all produce the same thing: a VM that does not
start, found days later by whoever booted one, in an error that reads like a
fault in whatever launched it.
`task verify:args` builds nine Specs — with and without a memory file, with and
without a memory ceiling, with and without a vsock, with disks, with a NIC, with
a named CPU model, and one with all of it — and starts each under the TCG binary
with -S, then requires an answer on QMP. "It parsed" would have been the weaker
claim: a process that replies to query-status has already created and realized
every device on its command line. Nine machines start, answer and are killed in
0.23 s together, which is less than one boot.
Deliberately broken to check it goes red and says where: free-page-reporting
misspelt gives `-device virtio-balloon-pci,free-page-reportng=on,...: Property
'virtio-balloon-pci.free-page-reportng' not found`, and virtio-mem-pci as
virtio-mem-pcie gives `'virtio-mem-pcie' is not a valid device model name`, each
under the full command line, one argument per line.
**Three arguments are rewritten, and no others.** CI has no /dev/kvm and the
binary a tenant's host runs refuses to start without it, so the question can only
be put to the TCG build — and `accel=kvm` becomes `accel=tcg`, `-cpu host`
becomes `max` (a KVM-only value by construction: "CPU model 'host' requires KVM
or HVF"; max is the other model that derives its features from the silicon, so
migratable=on stays under test), and a NIC's tap backend becomes a hub port. Each
rewrite is printed, so how far the claim reaches is visible in the log rather
than buried here.
What that leaves uncovered, stated rather than hidden:
- `tap,id=netN,fd=N,vhost=on`. The backend is a file descriptor the caller
opened and a test cannot open one without CAP_NET_ADMIN. The device that
references it is checked verbatim, romfile= and slot included, which is the
half a release can get wrong.
- A real named CPU model. enforce=on is precisely what stops one being checked
under emulation — Skylake-Server-v4 gives ten warnings and then "TCG doesn't
support requested features", exit 1, which is the flag doing its job. qemu64
is used instead, so what is checked is the package's own contribution: that
enforce is a property this binary has.
- vhost-vsock-pci on a machine that will not give up /dev/vhost-vsock. Both
workflows modprobe it *and chmod it*, which is not decoration: the first run
of this on a GitHub runner loaded the module, found the node, and failed with
"Could not open '/dev/vhost-vsock': Permission denied" — indistinguishable,
in a stat, from having it. The test opens the device rather than stat'ing it
for the same reason, and a host that refuses names the device as unchecked
instead of passing.
The kernel is a 197-byte ELF with a PVH entry note, written by the test. Not the
real one, deliberately: what is under test is the command line, and requiring
vmlinux would tie this to the kernel build — tens of minutes — so it could only
run in a lane that had one, which is the lane a machine/*.go change never
reaches. QEMU still enters it through pvh.bin out of the firmware directory, so
-L is exercised either way. Without the note it says "Error loading uncompressed
kernel without PVH ELF Note", which is how the stub was checked.
It runs in ci.yml, on every push, which is where machine/*.go is changed and
where qemu.yml — filtered to qemu/** — never fires. ci.yml still builds nothing:
`task qemu:fetch` unpacks the published runtime image for the version
qemu/Dockerfile pins into the same _output/ tree a build writes, in seconds, and
ends in the same qemu:verify. The one case that cannot work is a pull request
bumping the pin, since publishing happens from main — and that pull request
changes qemu/**, so qemu.yml builds the binary and runs the identical check
against it, which is the other place this was added.
`task test` is unchanged: with no binary in _output/ the test skips and says so.
`task verify:args` refuses instead, because a gate that passes for want of a
binary is worse than no gate.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019GHRRFvbnZi5q3KSTvyW5a
aledbf
force-pushed
the
machine/qemu-accepts-the-arguments
branch
from
September 8, 2026 04:15
35f43ac to
1d5f975
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The
machinepackage writes a QEMU command line by hand and nothing checked that the pinned binary accepts it. The device set is decided inqemu/devices.mak; the device and property names are typed out inmachine/machine.go. The two move independently, and a device dropped from the allowlist, a property renamed at a version bump, and a typo added here all surface the same way: a VM that does not start, days later, reading like a fault in whatever launched it.What it asserts
task verify:argsbuilds nine Specs — with and without a memory file, with and without a memory ceiling (which is what adds virtio-mem), with and without vsock, with disks, with a NIC, with a named CPU model, and one with all of it — starts each under the TCG binary with-S, and requires an answer on QMP. A process that replies toquery-statushas already created and realized every device on its command line, which is a stronger claim than "it parsed". Nine machines start, answer and are killed in 0.23 s together.The rewrites, and what is left uncovered
CI has no
/dev/kvmand the binary a tenant's host runs refuses to start without it, so only the TCG build can answer. Exactly three arguments are rewritten, each printed in the log:accel=kvm→accel=tcg-cpu host→max—hostis KVM-only by construction;maxis the other model that derives its features from the silicon, somigratable=onstays under testNot covered, and said so rather than hidden: the
tap,id=netN,fd=N,vhost=online itself (a TAP fd needs CAP_NET_ADMIN; thevirtio-net-pcidevice that references it is checked verbatim,romfile=and slot included); a real named microarchitecture (enforce=onis exactly what refuses one under TCG —Skylake-Server-v4gives "TCG doesn't support requested features", exit 1;qemu64is used soenforceitself is checked); andvhost-vsock-pcion a host with novhost_vsockmodule, which both workflows modprobe and which names itself as unchecked otherwise.The kernel is a 197-byte ELF with a PVH entry note written by the test, so the check does not depend on the kernel build. QEMU still enters it through
pvh.bin, so-Lis exercised.Proving it can fail
free-page-reportingmisspelt →-device virtio-balloon-pci,free-page-reportng=on,...: Property 'virtio-balloon-pci.free-page-reportng' not foundvirtio-mem-pciasvirtio-mem-pcie→'virtio-mem-pcie' is not a valid device model nameBoth under the full command line, one argument per line.
Where it runs
ci.yml, on every push — which is wheremachine/*.gochanges and whereqemu.yml, filtered toqemu/**, never fires. It still builds nothing:task qemu:fetchunpacks the published runtime image for the pinned version into the same_output/tree a build writes, in seconds, and ends in the sameqemu:verify. Also inqemu.yml, against the freshly built binary — which covers the one caseci.ymlcannot, a pull request bumping the pin to a version not yet published.task testis unchanged: with no binary the test skips and says so.task verify:argsrefuses instead.🤖 Generated with Claude Code
https://claude.ai/code/session_019GHRRFvbnZi5q3KSTvyW5a