You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Audit of every out-of-tree kernel patch against Linux 7.0.14, triggered by the 2026-08-08 host hang on fcvm-metal-arm. Full report lives at /home/ubuntu/patch-audit-7.0.14.md on the ARM box.
Headline: the crash is a known upstream bug with an existing fix that 7.0.14 does not have. Four of our nine patches can be dropped. The two NV2 barriers have never been A/B'd and one of them looks misplaced.
1. The nested.c crash — root cause found, fix identified
What happened
On 2026-08-08, fcvm-metal-arm (then on 6.18.3-fcvm-8ee6c35df0e1) hung hard under Firecracker load. EC2 instance status check went impaired at 12:19 with 0% CPU; SSH accepted TCP then never completed. The serial console held:
if (host_data_test_flag(L1_VNCR_MAPPED)) {
BUG_ON(vcpu->arch.vncr_tlb->cpu!=smp_processor_id()); /* <-- this one */BUG_ON(is_hyp_ctxt(vcpu));
The invariant and how it is violated
The VNCR pseudo-TLB holds the translation of L1's VNCR_EL2 page, mapped into a per-physical-CPU fixmap slot while L1 runs. Its state is split across four places: the per-CPU L1_VNCR_MAPPED flag, vncr_tlb->cpu, the fixmap PTE, and kvm->arch.vncr_map_count. The assertion says: if this CPU's flag is set, the pseudo-TLB must name this same CPU.
The violating sequence, verified in git:
KVM maps a VNCR translation on CPU N, sets vt->cpu = N, sets L1_VNCR_MAPPED, bumps vncr_map_count.
A VNCR_EL2-triggered fault calls kvm_translate_vncr().
That calls invalidate_vncr(vt), which marks the pseudo-TLB invalid and clears the host fixmap PTE — but does not clear L1_VNCR_MAPPED or decrement the count.
On success it sets vt->cpu = -1 and defers the remap. The per-CPU flag is now stale.
Any schedule-out before repair reaches kvm_sched_out() → kvm_vcpu_put_hw_mmu(), which sees the stale flag, compares -1 against the current CPU, and trips the first BUG_ON.
CPU migration is not required — an ordinary schedule-out on the same CPU is sufficient. The thread dies with IRQs disabled inside the scheduler path, which wedges that CPU and then the box. (The "exited with irqs disabled" note reflects the scheduler context, not guest behaviour.)
The upstream fix — and our exposure
5949004d7032767e8fde1e8c986a33f241b2a192 — KVM: arm64: nv: Fully update VNCR fixmap state in kvm_translate_vncr() — Oliver Upton, 2026-06-02, committed by Marc Zyngier 2026-06-03, Fixes: 069a05e535496 ("KVM: arm64: nv: Handle VNCR_EL2-triggered faults").
Its commit message describes our exact failure:
While the fixmap invalidation does clear the mapping from host stage-1, it does not clear the L1_VNCR_MAPPED flag. Depending on the state of the VNCR TLB at vcpu_put(), this could potentially precipitate a BUG_ON() if vt->cpu is reset.
It factors full teardown into this_cpu_reset_vncr_fixmap() and calls it from both kvm_vcpu_put_hw_mmu() and kvm_translate_vncr().
Independently verified against the stable tree:
The fix is ABSENT from v7.0.14 (git merge-base --is-ancestor against the fetched v7.0.14 tag). It is in no tag through v7.1-rc7; it merged for 7.2 via 751d041a13bd (kvmarm-7.2).
The vulnerable code is present in the 7.0.14 source at arch/arm64/kvm/nested.c:806.
We are running 7.0.14-fcvm-cd6cd2b4b52e right now, so we are still exposed.
Two things this corrects about the initial reading: the assertion still existing at v7.1-rc7 did not mean "unfixed" (the fix postdates rc7), and the fix deliberately keeps the assertion — the invariant is correct; it removes the state transition that violated it.
Related fixes in the same kvmarm-7.2 NV-MMU series, worth reviewing together: 4b54e2374d1b (hold mmu_lock while initializing vncr_tlb), 4be6cbeb93d2 (NULL VNCR pseudo-TLB deref), 6bef47288ce1 (stage-1 block-mapping validity), d8839941df7d / 699a2cc7f608 (restart walks after races), 9f76b039a72d (release PFN when VNCR translation races an MMU notifier).
Reportability
Do not file this as a new upstream bug — it is already known and fixed. Backport 5949004d7032 instead. A stock-7.0.14 reproduction is not needed to explain our failure, but is appropriate if we request a stable backport, since our trace came from a patched kernel and stable maintainers deserve a clean story.
2. Patch verdicts
kernel/patches-x86/ contains only symlinks to the two shared FUSE patches, so rows 1–2 cover both arches; the rest are ARM64-only.
Patch
Purpose
Upstream 7.0.14 status
Verdict
0001-fuse-add-remap_file_range (shared)
FICLONE/FICLONERANGE over FUSE via private opcode 54
No equivalent; UAPI enum ends at 53
KEEP, verify sizing
0002-fuse-fix-utimensat-... (shared)
UTIME_NOW wire semantics + extra permission path
FATTR_MTIME_NOW bug still present; permission rule now generic
KEEP, trimmed
mmfr4-override.vm.patch
arm64.nv2 alias forcing MMFR4.NV_frac=2
KVM understands NV_frac, but no boot override exists
KEEP
nv2-vsock-cache-sync.patch
dsb(sy) on every NV-capable vCPU exit
No equivalent through kvmarm-7.2
INVESTIGATE (retain pending A/B)
nv2-vsock-rx-barrier.patch
dsb(sy) in ARM64 virtio-vsock RX worker
No equivalent through kvmarm-7.2
INVESTIGATE (strong drop candidate)
wfx-stopped-exit.patch
WFx by a stopped vCPU → userspace whole-VM shutdown
kvm_prepare_system_event() already does this properly
DROP
psci-debug-emulate-nested.patch
pr_debug() around nested trap forwarding
Debug only
DROP
psci-debug-handle-exit.patch
pr_debug() around SMC forwarding
Debug only
DROP
psci-debug-psci.patch
Three unconditional [KVM PSCI DEBUG]pr_info()
Debug only
DROP
Detail on the non-obvious ones
wfx-stopped-exit — drop. Upstream kvm_prepare_system_event() already stops all vCPUs and initialises the complete shutdown event. Ours conflates "a stopped vCPU executed WFI" with "the whole VM is shutting down", and can pre-empt nested WFx forwarding. Local commit e1c2ea9bf696 established that poweroff -f (not halt -f) is what invokes PSCI SYSTEM_OFF, which is the behaviour this was reaching for.
psci-debug-* — drop all three. Logging only, no correctness change. psci-debug-psci.patch uses unconditional pr_info(), and those [KVM PSCI DEBUG] lines are visible in the host journal today — including in the crash console capture. They should not be in a kernel run day to day; if kept for NV debugging they belong behind a debug config, not in the default patch set.
The two NV2 DSB barriers — the real open question. Neither has an upstream equivalent through kvmarm-7.2, and the substantial post-6.18 VNCR/TLB work does not subsume them, so they cannot simply be dropped as "fixed upstream". However:
nv2-vsock-rx-barrier appears to be on the wrong side of the data path. The Linux virtio-vsock RX worker consumes VMM→guest buffers; the corruption we were chasing is L2-guest-TX data read by L1 Firecracker. That path already uses virtio_rmb()/DMA synchronisation. This is the strongest candidate for removal.
nv2-vsock-cache-sync's supporting evidence (local 9a37c89109f0) is a 100 MiB pass recorded without a same-build unpatched control. It has never been A/B'd.
Both fire on every exit / every RX worker invocation, so they carry ongoing cost for evidence we do not actually have.
mmfr4-override.vm — keep. 7.0.14 and v7.1-rc7 teach KVM about NV_frac (88aea41b9bc5, 642c23ea8b45) but provide no MMFR4 boot override, and the virtual-EL2 ID-register visibility problem is not moot. Note it is VM-only: the host_kernelbuild_inputs glob excludes *.vm.patch, so it is not in the host kernel.
0002-fuse-fix-utimensat — keep, trimmed. The FATTR_MTIME_NOW hunk is still needed (7.0.14 still suppresses it under writeback, fs/fuse/dir.c:2027-2032). The extra ATTR_FORCE/permission hunk is redundant since upstream f2b20f6ee842 made ATTR_TOUCH checking generic.
3. Separate concern: the mmio.c force-flush proof of concept
Not in the patch directories, but present as a local branch commit (f4f42c8c52f9, rebased 9930100e6114): KVM: arm64: Force cache flush for NV2 guests (bypass FWB).
On every NV-capable nested MMIO write it walks vcpu->arch.hw_mmu->pgt across 0–64 MiB, clean-invalidating every mapped granule, bracketed by DSBs, before kvm_io_bus_write(). It does not hold kvm->mmu_lock or mark the walk shared, though normal stage-2 flush/walk mutation does. A block mapping can also push a leaf granule past the nominal 64 MiB range.
Independently of the crash, this can race page-table mutation and is very broad. It labels itself a proof of concept and should be quarantined — and must not be included in any stock reproduction or upstream report.
It was not in the crash kernel: 6.18.3-fcvm-8ee6c35df0e1's /boot/System.map (built with CONFIG_KALLSYMS_ALL=y, CONFIG_LTO_NONE=y) contains neither nv2_flush_walker nor kvm_nv2_flush_guest_dcache, while containing ordinary KVM MMIO symbols. The walker is passed as a callback so it could not vanish through inlining.
4. Caveats and one correction
A claim in the audit that did not survive checking. The report asserted that the 6.18.3→7.0.14 rebase (Repin deployable kernels 6.18.3 -> 6.18.44 (7.x wedges nested guests) #751) dropped the >4 GiB FICLONE fix from local commit 559437b95b11. Checked directly by diffing only the added kernel lines of the pre-rebase and regenerated patch files: identical, no regression. The STATX_SIZE refresh and derive-length-from-request logic are both intact. The commit header/date/subject did change, which is the likely source of the false positive. Sizing behaviour is still worth an explicit 10 GiB clone test, but nothing was lost in the rebase.
Negative search results are weak evidence. "No public report matching our trace" is not proof none exists; the load-bearing evidence here is the upstream commit itself.
The audit was research-only — no tests were run, so the DSB verdicts are analysis, not measurement. That is exactly why the factorial matrix below matters.
Provenance: produced by a deep Codex run over the fcvm tree, a full upstream git tree with tags through v7.1-rc7, and the fetched stable v7.0.14 tag; key claims (the fix commit's existence, content, and absence from 7.0.14; the >4 GiB claim above) were re-verified by hand.
5. Next steps, ordered by value
Backport 5949004d7032 to the 7.0.14 host kernel and stress it. Applies with a four-line offset. Repeat the Firecracker/nested load that produced the BUG; capture console, KVM tracepoints, vCPU migrations. Consider testing the full kvmarm-7.2 NV-MMU series as a separate variant. This is the one item that protects against a repeat of the 25-minute hang.
Run a 2×4 DSB factorial matrix isolating the exit DSB, the Linux RX DSB, and the Firecracker event-handler DSB, with GiB-scale bidirectional raw-vsock and FUSE-over-FUSE integrity stress, repeated, with exit/throughput cost measured. Expect to remove the RX DSB — but let the evidence decide.
Drop wfx-stopped-exit and the three psci-debug-* patches. Verify poweroff -f, CPU_OFF/hotplug, WFI idle, nested WFx forwarding, and absence of log spam.
Explicitly test >4 GiB FICLONE (10 GiB clone with immediate size/data verification) so the sizing behaviour is proven rather than assumed, and ensure the test cannot silently skip on an unsupported kernel.
Narrow mmfr4-override.vm: A/B an unpatched VM kernel on the real NV2 stack, recording raw ID registers, feature detection, KVM_CAP_ARM_EL2, and L2/L3 launch.
Trim 0002-fuse-fix-utimensat to the FATTR_MTIME_NOW hunk and restore a writeback-enabled test across the owner/non-owner × NOW/explicit matrix.
Quarantine the mmio.c force-flush PoC; audit its locking and memory-attribute assumptions separately.
Only then consider upstream/stable submissions — nominate 5949004d for affected stable trees with transparent stock evidence, and treat the private FUSE opcode and any demonstrated NV2 coherency fix as separate submissions.
Context: the 6.18.3→7.0.14 rebase is #751. The host now runs 7.0.14-fcvm-cd6cd2b4b52e. Host-side diagnostics were also added on the AWS side so a recurrence reboots in ~2 minutes with the console captured, instead of hanging silently.
Audit of every out-of-tree kernel patch against Linux 7.0.14, triggered by the 2026-08-08 host hang on
fcvm-metal-arm. Full report lives at/home/ubuntu/patch-audit-7.0.14.mdon the ARM box.Headline: the crash is a known upstream bug with an existing fix that 7.0.14 does not have. Four of our nine patches can be dropped. The two NV2 barriers have never been A/B'd and one of them looks misplaced.
1. The
nested.ccrash — root cause found, fix identifiedWhat happened
On 2026-08-08,
fcvm-metal-arm(then on6.18.3-fcvm-8ee6c35df0e1) hung hard under Firecracker load. EC2 instance status check wentimpairedat 12:19 with 0% CPU; SSH accepted TCP then never completed. The serial console held:The failing assertion:
The invariant and how it is violated
The VNCR pseudo-TLB holds the translation of L1's
VNCR_EL2page, mapped into a per-physical-CPU fixmap slot while L1 runs. Its state is split across four places: the per-CPUL1_VNCR_MAPPEDflag,vncr_tlb->cpu, the fixmap PTE, andkvm->arch.vncr_map_count. The assertion says: if this CPU's flag is set, the pseudo-TLB must name this same CPU.The violating sequence, verified in git:
vt->cpu = N, setsL1_VNCR_MAPPED, bumpsvncr_map_count.kvm_translate_vncr().invalidate_vncr(vt), which marks the pseudo-TLB invalid and clears the host fixmap PTE — but does not clearL1_VNCR_MAPPEDor decrement the count.vt->cpu = -1and defers the remap. The per-CPU flag is now stale.kvm_sched_out()→kvm_vcpu_put_hw_mmu(), which sees the stale flag, compares-1against the current CPU, and trips the firstBUG_ON.CPU migration is not required — an ordinary schedule-out on the same CPU is sufficient. The thread dies with IRQs disabled inside the scheduler path, which wedges that CPU and then the box. (The "exited with irqs disabled" note reflects the scheduler context, not guest behaviour.)
The upstream fix — and our exposure
5949004d7032767e8fde1e8c986a33f241b2a192— KVM: arm64: nv: Fully update VNCR fixmap state inkvm_translate_vncr()— Oliver Upton, 2026-06-02, committed by Marc Zyngier 2026-06-03,Fixes: 069a05e535496 ("KVM: arm64: nv: Handle VNCR_EL2-triggered faults").Its commit message describes our exact failure:
It factors full teardown into
this_cpu_reset_vncr_fixmap()and calls it from bothkvm_vcpu_put_hw_mmu()andkvm_translate_vncr().Independently verified against the stable tree:
git merge-base --is-ancestoragainst the fetchedv7.0.14tag). It is in no tag through v7.1-rc7; it merged for 7.2 via751d041a13bd(kvmarm-7.2).arch/arm64/kvm/nested.c:806.7.0.14-fcvm-cd6cd2b4b52eright now, so we are still exposed.Two things this corrects about the initial reading: the assertion still existing at v7.1-rc7 did not mean "unfixed" (the fix postdates rc7), and the fix deliberately keeps the assertion — the invariant is correct; it removes the state transition that violated it.
Related fixes in the same kvmarm-7.2 NV-MMU series, worth reviewing together:
4b54e2374d1b(holdmmu_lockwhile initializingvncr_tlb),4be6cbeb93d2(NULL VNCR pseudo-TLB deref),6bef47288ce1(stage-1 block-mapping validity),d8839941df7d/699a2cc7f608(restart walks after races),9f76b039a72d(release PFN when VNCR translation races an MMU notifier).Reportability
Do not file this as a new upstream bug — it is already known and fixed. Backport
5949004d7032instead. A stock-7.0.14 reproduction is not needed to explain our failure, but is appropriate if we request a stable backport, since our trace came from a patched kernel and stable maintainers deserve a clean story.2. Patch verdicts
kernel/patches-x86/contains only symlinks to the two shared FUSE patches, so rows 1–2 cover both arches; the rest are ARM64-only.0001-fuse-add-remap_file_range(shared)0002-fuse-fix-utimensat-...(shared)FATTR_MTIME_NOWbug still present; permission rule now genericmmfr4-override.vm.patcharm64.nv2alias forcing MMFR4.NV_frac=2NV_frac, but no boot override existsnv2-vsock-cache-sync.patchdsb(sy)on every NV-capable vCPU exitnv2-vsock-rx-barrier.patchdsb(sy)in ARM64 virtio-vsock RX workerwfx-stopped-exit.patchkvm_prepare_system_event()already does this properlypsci-debug-emulate-nested.patchpr_debug()around nested trap forwardingpsci-debug-handle-exit.patchpr_debug()around SMC forwardingpsci-debug-psci.patch[KVM PSCI DEBUG]pr_info()Detail on the non-obvious ones
wfx-stopped-exit— drop. Upstreamkvm_prepare_system_event()already stops all vCPUs and initialises the complete shutdown event. Ours conflates "a stopped vCPU executed WFI" with "the whole VM is shutting down", and can pre-empt nested WFx forwarding. Local commite1c2ea9bf696established thatpoweroff -f(nothalt -f) is what invokes PSCI SYSTEM_OFF, which is the behaviour this was reaching for.psci-debug-*— drop all three. Logging only, no correctness change.psci-debug-psci.patchuses unconditionalpr_info(), and those[KVM PSCI DEBUG]lines are visible in the host journal today — including in the crash console capture. They should not be in a kernel run day to day; if kept for NV debugging they belong behind a debug config, not in the default patch set.The two NV2 DSB barriers — the real open question. Neither has an upstream equivalent through kvmarm-7.2, and the substantial post-6.18 VNCR/TLB work does not subsume them, so they cannot simply be dropped as "fixed upstream". However:
nv2-vsock-rx-barrierappears to be on the wrong side of the data path. The Linux virtio-vsock RX worker consumes VMM→guest buffers; the corruption we were chasing is L2-guest-TX data read by L1 Firecracker. That path already usesvirtio_rmb()/DMA synchronisation. This is the strongest candidate for removal.nv2-vsock-cache-sync's supporting evidence (local9a37c89109f0) is a 100 MiB pass recorded without a same-build unpatched control. It has never been A/B'd.Both fire on every exit / every RX worker invocation, so they carry ongoing cost for evidence we do not actually have.
mmfr4-override.vm— keep. 7.0.14 and v7.1-rc7 teach KVM aboutNV_frac(88aea41b9bc5,642c23ea8b45) but provide no MMFR4 boot override, and the virtual-EL2 ID-register visibility problem is not moot. Note it is VM-only: thehost_kernelbuild_inputsglob excludes*.vm.patch, so it is not in the host kernel.0002-fuse-fix-utimensat— keep, trimmed. TheFATTR_MTIME_NOWhunk is still needed (7.0.14 still suppresses it under writeback,fs/fuse/dir.c:2027-2032). The extraATTR_FORCE/permission hunk is redundant since upstreamf2b20f6ee842madeATTR_TOUCHchecking generic.3. Separate concern: the
mmio.cforce-flush proof of conceptNot in the patch directories, but present as a local branch commit (
f4f42c8c52f9, rebased9930100e6114): KVM: arm64: Force cache flush for NV2 guests (bypass FWB).On every NV-capable nested MMIO write it walks
vcpu->arch.hw_mmu->pgtacross 0–64 MiB, clean-invalidating every mapped granule, bracketed by DSBs, beforekvm_io_bus_write(). It does not holdkvm->mmu_lockor mark the walk shared, though normal stage-2 flush/walk mutation does. A block mapping can also push a leaf granule past the nominal 64 MiB range.Independently of the crash, this can race page-table mutation and is very broad. It labels itself a proof of concept and should be quarantined — and must not be included in any stock reproduction or upstream report.
It was not in the crash kernel:
6.18.3-fcvm-8ee6c35df0e1's/boot/System.map(built withCONFIG_KALLSYMS_ALL=y,CONFIG_LTO_NONE=y) contains neithernv2_flush_walkernorkvm_nv2_flush_guest_dcache, while containing ordinary KVM MMIO symbols. The walker is passed as a callback so it could not vanish through inlining.4. Caveats and one correction
559437b95b11. Checked directly by diffing only the added kernel lines of the pre-rebase and regenerated patch files: identical, no regression. TheSTATX_SIZErefresh and derive-length-from-request logic are both intact. The commit header/date/subject did change, which is the likely source of the false positive. Sizing behaviour is still worth an explicit 10 GiB clone test, but nothing was lost in the rebase.v7.0.14tag; key claims (the fix commit's existence, content, and absence from 7.0.14; the >4 GiB claim above) were re-verified by hand.5. Next steps, ordered by value
5949004d7032to the 7.0.14 host kernel and stress it. Applies with a four-line offset. Repeat the Firecracker/nested load that produced the BUG; capture console, KVM tracepoints, vCPU migrations. Consider testing the full kvmarm-7.2 NV-MMU series as a separate variant. This is the one item that protects against a repeat of the 25-minute hang.wfx-stopped-exitand the threepsci-debug-*patches. Verifypoweroff -f, CPU_OFF/hotplug, WFI idle, nested WFx forwarding, and absence of log spam.mmfr4-override.vm: A/B an unpatched VM kernel on the real NV2 stack, recording raw ID registers, feature detection,KVM_CAP_ARM_EL2, and L2/L3 launch.0002-fuse-fix-utimensatto theFATTR_MTIME_NOWhunk and restore a writeback-enabled test across the owner/non-owner × NOW/explicit matrix.mmio.cforce-flush PoC; audit its locking and memory-attribute assumptions separately.5949004dfor affected stable trees with transparent stock evidence, and treat the private FUSE opcode and any demonstrated NV2 coherency fix as separate submissions.Context: the 6.18.3→7.0.14 rebase is #751. The host now runs
7.0.14-fcvm-cd6cd2b4b52e. Host-side diagnostics were also added on the AWS side so a recurrence reboots in ~2 minutes with the console captured, instead of hanging silently.