| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/09/11 19:19 | flow | patch-triage |
0mArgs:null Results: {
"EnableConfigs": null,
"FocusSymbols": [],
"KMSANReasoning": "",
"NeedsKMSAN": false,
"Reasoning": "The patch series only adds a KVM selftest in tools/testing/selftests/kvm/ and modifies its Makefile. It does not modify any kernel source code, compiled kernel image (vmlinux), or kernel modules.",
"WorthFuzzing": false
} |
| 1/1 | 2026/09/11 19:19 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit a0a259c5d3a3dd5d80e0c743f5486769914ad1cb\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Fri Sep 11 19:19:25 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm\nindex 96bab7002d39e..178101e40e401 100644\n--- a/tools/testing/selftests/kvm/Makefile.kvm\n+++ b/tools/testing/selftests/kvm/Makefile.kvm\n@@ -135,6 +135,7 @@ TEST_GEN_PROGS_x86 += x86/vmx_exception_with_invalid_guest_state\n TEST_GEN_PROGS_x86 += x86/vmx_msrs_test\n TEST_GEN_PROGS_x86 += x86/vmx_invalid_nested_guest_state\n TEST_GEN_PROGS_x86 += x86/vmx_nested_la57_state_test\n+TEST_GEN_PROGS_x86 += x86/vmx_shadow_vmcs_teardown_test\n TEST_GEN_PROGS_x86 += x86/apic_bus_clock_test\n TEST_GEN_PROGS_x86 += x86/xapic_ipi_test\n TEST_GEN_PROGS_x86 += x86/xapic_state_test\ndiff --git a/tools/testing/selftests/kvm/x86/vmx_shadow_vmcs_teardown_test.c b/tools/testing/selftests/kvm/x86/vmx_shadow_vmcs_teardown_test.c\nnew file mode 100644\nindex 0000000000000..e0863ef20d3ed\n--- /dev/null\n+++ b/tools/testing/selftests/kvm/x86/vmx_shadow_vmcs_teardown_test.c\n@@ -0,0 +1,317 @@\n+// SPDX-License-Identifier: GPL-2.0-only\n+/*\n+ * vmx_shadow_vmcs_teardown_test\n+ *\n+ * Verify that destroying a nested VMM with active shadow VMCS12 does not\n+ * flush the cached shadow VMCS into an unrelated process memory space\n+ * during process exit in lazy TLB mode.\n+ */\n+\n+#include \u003cerrno.h\u003e\n+#include \u003cfcntl.h\u003e\n+#include \u003csched.h\u003e\n+#include \u003cstdbool.h\u003e\n+#include \u003cstdint.h\u003e\n+#include \u003cstring.h\u003e\n+#include \u003csys/mman.h\u003e\n+#include \u003csys/wait.h\u003e\n+#include \u003clinux/mman.h\u003e\n+#include \u003cunistd.h\u003e\n+\n+#include \"test_util.h\"\n+#include \"kvm_util.h\"\n+#include \"processor.h\"\n+#include \"vmx.h\"\n+#include \"kselftest.h\"\n+\n+#define PORT_L0_EXIT\t\t0x2000\n+#define SHADOW_VMCS_GPA\t\t0x80000000ULL\n+#define SHADOW_VMCS_GVA\t\t0x80000000ULL\n+#define CANARY_BYTE\t\t0x5a\n+#define L2_GUEST_STACK_SIZE\t64\n+\n+struct test_result {\n+\tbool corrupted;\n+\tsize_t corrupt_offset;\n+\tuint8_t corrupt_val;\n+};\n+\n+static void l2_guest_code(void)\n+{\n+\t/* Exit to L0 */\n+\tasm volatile(\"inb %%dx, %%al\"\n+\t\t : : [port] \"d\" (PORT_L0_EXIT) : \"rax\");\n+}\n+\n+static void l1_guest_code(struct vmx_pages *vmx_pages)\n+{\n+\tunsigned long l2_guest_stack[L2_GUEST_STACK_SIZE];\n+\n+\tGUEST_ASSERT(prepare_for_vmx_operation(vmx_pages));\n+\tGUEST_ASSERT(load_vmcs(vmx_pages));\n+\n+\t/* Prepare the VMCS for L2 execution. */\n+\tprepare_vmcs(vmx_pages, l2_guest_code,\n+\t\t \u0026l2_guest_stack[L2_GUEST_STACK_SIZE]);\n+\n+\t/* Enable VMCS shadowing and set the shadow VMCS link pointer. */\n+\tvmwrite(SECONDARY_VM_EXEC_CONTROL,\n+\t\tvmreadz(SECONDARY_VM_EXEC_CONTROL) | SECONDARY_EXEC_SHADOW_VMCS);\n+\tvmwrite(VMCS_LINK_POINTER, vmx_pages-\u003eshadow_vmcs_gpa);\n+\n+\tvmlaunch();\n+\tGUEST_FAIL(\"L1 guest must not regain control\");\n+}\n+\n+static bool kvm_cpu_has_shadow_vmcs(void)\n+{\n+\tuint64_t ctrl;\n+\n+\tctrl = kvm_get_feature_msr(MSR_IA32_VMX_TRUE_PROCBASED_CTLS) \u003e\u003e 32;\n+\tif (!(ctrl \u0026 CPU_BASED_ACTIVATE_SECONDARY_CONTROLS))\n+\t\treturn false;\n+\n+\tctrl = kvm_get_feature_msr(MSR_IA32_VMX_PROCBASED_CTLS2) \u003e\u003e 32;\n+\treturn ctrl \u0026 SECONDARY_EXEC_SHADOW_VMCS;\n+}\n+\n+static void run_vmm(int pcpu, void *target_hva, int c2_to_c1_fd)\n+{\n+\tvm_vaddr_t vmx_pages_gva;\n+\tstruct kvm_vcpu *vcpu;\n+\tstruct kvm_vm *vm;\n+\tstruct vmx_pages *vmx;\n+\tvoid *shadow_hva;\n+\n+\tpin_self_to_cpu(pcpu);\n+\n+\tvm = vm_create_with_one_vcpu(\u0026vcpu, l1_guest_code);\n+\n+\t/* Allocate VMX pages and shared descriptors. */\n+\tvcpu_alloc_vmx(vm, \u0026vmx_pages_gva);\n+\tvmx = addr_gva2hva(vm, vmx_pages_gva);\n+\n+\t/*\n+\t * Map the shadow VMCS page at the exact target_hva allocated by the\n+\t * victim so it collides with the victim process's mapping.\n+\t */\n+\tshadow_hva = mmap(target_hva, PAGE_SIZE, PROT_READ | PROT_WRITE,\n+\t\t\t MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED_NOREPLACE, -1, 0);\n+\tTEST_ASSERT(shadow_hva == target_hva, \"mmap target_hva failed in VMM\");\n+\tmemset(shadow_hva, 0, PAGE_SIZE);\n+\n+\t/* Add a caller-managed memslot for the shadow VMCS backing page. */\n+\tvm_userspace_mem_region_add_caller_managed(vm, shadow_hva,\n+\t\t\t\t\t\t SHADOW_VMCS_GPA, 10, 1, 0);\n+\tvirt_pg_map(vm, SHADOW_VMCS_GVA, SHADOW_VMCS_GPA);\n+\n+\t/* Override the shadow VMCS pointers in vmx_pages. */\n+\tvmx-\u003eshadow_vmcs = (void *)SHADOW_VMCS_GVA;\n+\tvmx-\u003eshadow_vmcs_hva = shadow_hva;\n+\tvmx-\u003eshadow_vmcs_gpa = SHADOW_VMCS_GPA;\n+\n+\tvcpu_args_set(vcpu, 1, vmx_pages_gva);\n+\n+\tfor (;;) {\n+\t\tstruct kvm_run *run = vcpu-\u003erun;\n+\t\tstruct ucall uc;\n+\n+\t\tvcpu_run(vcpu);\n+\t\tTEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO);\n+\n+\t\tif (run-\u003eio.port == PORT_L0_EXIT)\n+\t\t\tbreak;\n+\n+\t\tswitch (get_ucall(vcpu, \u0026uc)) {\n+\t\tcase UCALL_ABORT:\n+\t\t\tREPORT_GUEST_ASSERT(uc);\n+\t\t\t/* NOT REACHED */\n+\t\tdefault:\n+\t\t\tTEST_FAIL(\"Unknown ucall %lu\", uc.cmd);\n+\t\t}\n+\t}\n+\n+\t/*\n+\t * Signal the victim process that the VMM is about to terminate\n+\t * while L2 is active with VMCS shadowing enabled.\n+\t */\n+\tTEST_ASSERT(write(c2_to_c1_fd, \"X\", 1) == 1, \"write to victim failed\");\n+\n+\t/*\n+\t * Exit immediately without closing VM or vCPU file descriptors.\n+\t * The kernel closes them in exit_files() after exit_mm() sets\n+\t * current-\u003emm = NULL.\n+\t */\n+\t_exit(0);\n+}\n+\n+static void run_victim(int pcpu, int ready_fd, int c2_to_c1_fd,\n+\t\t int p_to_c1_fd, int result_fd)\n+{\n+\tstruct test_result result = { .corrupted = false };\n+\tvoid *victim_hva;\n+\tchar sync_byte;\n+\tsize_t i;\n+\tint flags;\n+\n+\tpin_self_to_cpu(pcpu);\n+\n+\tvictim_hva = mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE,\n+\t\t\t MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);\n+\tTEST_ASSERT(victim_hva != MAP_FAILED, \"mmap failed in victim\");\n+\tmemset(victim_hva, CANARY_BYTE, PAGE_SIZE);\n+\n+\t/* Send mapped virtual address to parent so VMM can collide with it. */\n+\tTEST_ASSERT(write(ready_fd, \u0026victim_hva, sizeof(victim_hva)) == sizeof(victim_hva),\n+\t\t \"write ready failed\");\n+\n+\t/* Wait for VMM to launch L2 and reach exit to userspace. */\n+\tTEST_ASSERT(read(c2_to_c1_fd, \u0026sync_byte, 1) == 1, \"read sync failed\");\n+\n+\t/* Set parent-to-victim pipe to non-blocking. */\n+\tflags = fcntl(p_to_c1_fd, F_GETFL, 0);\n+\tTEST_ASSERT(flags \u003e= 0, \"fcntl F_GETFL failed\");\n+\tTEST_ASSERT(fcntl(p_to_c1_fd, F_SETFL, flags | O_NONBLOCK) == 0,\n+\t\t \"fcntl F_SETFL failed\");\n+\n+\t/*\n+\t * Loop yielding the CPU while the VMM terminates. When the VMM\n+\t * sleeps in synchronize_srcu() during teardown, the scheduler\n+\t * runs this victim process on the CPU. When the VMM wakes up,\n+\t * the scheduler context switches from this victim to the VMM\n+\t * with current-\u003emm == NULL, leaving the victim's CR3 active.\n+\t */\n+\tfor (;;) {\n+\t\tssize_t ret = read(p_to_c1_fd, \u0026sync_byte, 1);\n+\n+\t\tif (ret == 1)\n+\t\t\tbreak;\n+\t\tif (ret \u003c 0 \u0026\u0026 (errno == EAGAIN || errno == EWOULDBLOCK)) {\n+\t\t\tsched_yield();\n+\t\t\tcontinue;\n+\t\t}\n+\t\tTEST_FAIL(\"read from parent pipe failed: ret=%zd, errno=%d\",\n+\t\t\t ret, errno);\n+\t}\n+\n+\t/* Verify that the entire page remained untouched. */\n+\tfor (i = 0; i \u003c PAGE_SIZE; i++) {\n+\t\tif (((uint8_t *)victim_hva)[i] != CANARY_BYTE) {\n+\t\t\tresult.corrupted = true;\n+\t\t\tresult.corrupt_offset = i;\n+\t\t\tresult.corrupt_val = ((uint8_t *)victim_hva)[i];\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\n+\tTEST_ASSERT(write(result_fd, \u0026result, sizeof(result)) == sizeof(result),\n+\t\t \"write result failed\");\n+\t_exit(0);\n+}\n+\n+static bool run_iteration(int pcpu)\n+{\n+\tint pipe_ready[2];\n+\tint pipe_c2_to_c1[2];\n+\tint pipe_p_to_c1[2];\n+\tint pipe_result[2];\n+\tpid_t pid_victim;\n+\tpid_t pid_vmm;\n+\tstruct test_result result = {};\n+\tvoid *target_hva = NULL;\n+\tint status;\n+\n+\tTEST_ASSERT(pipe(pipe_ready) == 0, \"pipe failed\");\n+\tTEST_ASSERT(pipe(pipe_c2_to_c1) == 0, \"pipe failed\");\n+\tTEST_ASSERT(pipe(pipe_p_to_c1) == 0, \"pipe failed\");\n+\tTEST_ASSERT(pipe(pipe_result) == 0, \"pipe failed\");\n+\n+\tpid_victim = fork();\n+\tTEST_ASSERT(pid_victim \u003e= 0, \"fork victim failed\");\n+\tif (pid_victim == 0) {\n+\t\tclose(pipe_ready[0]);\n+\t\tclose(pipe_c2_to_c1[1]);\n+\t\tclose(pipe_p_to_c1[1]);\n+\t\tclose(pipe_result[0]);\n+\t\trun_victim(pcpu, pipe_ready[1], pipe_c2_to_c1[0],\n+\t\t\t pipe_p_to_c1[0], pipe_result[1]);\n+\t}\n+\n+\tclose(pipe_ready[1]);\n+\tclose(pipe_result[1]);\n+\tclose(pipe_p_to_c1[0]);\n+\n+\t/* Wait for victim to initialize its mapping and retrieve its address. */\n+\tTEST_ASSERT(read(pipe_ready[0], \u0026target_hva, sizeof(target_hva)) == sizeof(target_hva),\n+\t\t \"wait ready failed\");\n+\tclose(pipe_ready[0]);\n+\n+\tpid_vmm = fork();\n+\tTEST_ASSERT(pid_vmm \u003e= 0, \"fork VMM failed\");\n+\tif (pid_vmm == 0) {\n+\t\tclose(pipe_c2_to_c1[0]);\n+\t\tclose(pipe_p_to_c1[1]);\n+\t\tclose(pipe_result[0]);\n+\t\trun_vmm(pcpu, target_hva, pipe_c2_to_c1[1]);\n+\t}\n+\n+\tclose(pipe_c2_to_c1[0]);\n+\tclose(pipe_c2_to_c1[1]);\n+\n+\t/* Wait for VMM to completely terminate. */\n+\twaitpid(pid_vmm, \u0026status, 0);\n+\tTEST_ASSERT(WIFEXITED(status) \u0026\u0026 WEXITSTATUS(status) == 0,\n+\t\t \"VMM child failed with status %d\", status);\n+\n+\t/* Signal victim to exit yield loop. */\n+\tTEST_ASSERT(write(pipe_p_to_c1[1], \"D\", 1) == 1, \"signal victim failed\");\n+\tclose(pipe_p_to_c1[1]);\n+\n+\t/* Read result from victim. */\n+\tTEST_ASSERT(read(pipe_result[0], \u0026result, sizeof(result)) == sizeof(result),\n+\t\t \"read result failed\");\n+\tclose(pipe_result[0]);\n+\n+\twaitpid(pid_victim, \u0026status, 0);\n+\tTEST_ASSERT(WIFEXITED(status) \u0026\u0026 WEXITSTATUS(status) == 0,\n+\t\t \"Victim child failed with status %d\", status);\n+\n+\tif (result.corrupted)\n+\t\tpr_info(\"Memory check: corruption at offset 0x%zx: expected 0x%02x, actual 0x%02x\\n\",\n+\t\t\tresult.corrupt_offset, CANARY_BYTE, result.corrupt_val);\n+\telse\n+\t\tpr_info(\"Memory check: entire page intact (canary = 0x%02x)\\n\",\n+\t\t\tCANARY_BYTE);\n+\n+\treturn result.corrupted;\n+}\n+\n+int main(int argc, char *argv[])\n+{\n+\tbool corrupted = false;\n+\tint pcpu;\n+\tint i;\n+\tconst int iterations = 5;\n+\n+\tTEST_REQUIRE(kvm_cpu_has(X86_FEATURE_VMX));\n+\tTEST_REQUIRE(kvm_cpu_has_shadow_vmcs());\n+\n+\tpcpu = pin_self_to_any_cpu();\n+\n+\tpr_info(\"Testing shadow VMCS teardown on pCPU %d (%d iterations)...\\n\",\n+\t\tpcpu, iterations);\n+\n+\tfor (i = 0; i \u003c iterations; i++) {\n+\t\tif (run_iteration(pcpu)) {\n+\t\t\tcorrupted = true;\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\n+\tTEST_ASSERT(!corrupted,\n+\t\t \"Memory corruption detected! Active MM was corrupted during vCPU teardown: canary byte 0x%02x overwritten\\n\",\n+\t\t CANARY_BYTE);\n+\n+\tpr_info(\"PASS: No memory corruption detected during teardown.\\n\");\n+\treturn 0;\n+}\n"
} |
| 2/1 | 2026/09/11 19:19 | agent | patch-evaluator |
0mModel:core Args: null Results: {
"EnableConfigs": null,
"FocusSymbols": [],
"Reasoning": "The patch series only adds a KVM selftest in tools/testing/selftests/kvm/ and modifies its Makefile. It does not modify any kernel source code, compiled kernel image (vmlinux), or kernel modules.",
"WorthFuzzing": false
}Instruction: You are an expert Linux kernel maintainer and security engineer.
Your job is to review a provided patch series and evaluate whether it warrants fuzzing with syzkaller.
IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on internal assumptions. You must actively use your code access
tools to inspect the actual source code, callers, and surrounding context.
================================================================================
1. CORE TRIAGE PHILOSOPHY
================================================================================
The goal of patch fuzzing is to discover crashes, regressions, exposed latent bugs,
and newly triggered assertions introduced by the patch series.
- REACHABILITY IS THE PRIMARY GATE:
Fuzzing can only discover bugs in code that can actually execute in standard virtualized
environments (GCE or QEMU, utilizing software-emulated devices like USB gadgets, netdev, tun/tap).
If the modified code is structurally unreachable (see Section 2), it MUST NOT be fuzzed,
regardless of whether it adds assertions or complex logic.
- DO NOT BLINDLY TRUST "NO FUNCTIONAL CHANGE" (NFCI) OR "REFACTORING" CLAIMS:
Patch authors routinely label changes as "cleanups", "refactorings", or state
"No functional change intended". Do NOT take these claims at face value.
Code refactorings that rearrange logic, introduce helper functions, or alter state management
in core subsystems frequently introduce subtle semantic shifts or uncover latent kernel bugs.
If reachable executable code is modified or refactored, it MUST be fuzzed.
- NEW OR MODIFIED ASSERTIONS IN REACHABLE CODE MUST BE FUZZED:
When a patch introduces or modifies runtime checks or assertions (e.g., WARN_ON*, VM_WARN_ON*,
BUG_ON*, lockdep_assert*) in reachable code paths, it enforces new or stricter invariants.
Even if the author believes the invariant always holds, fuzzing is essential to verify whether
an unusual sequence of operations can violate it.
================================================================================
2. WHEN TO RETURN WorthFuzzing=false (NEGATIVE CRITERIA)
================================================================================
Return WorthFuzzing=false ONLY IF all modified code falls strictly into one or more of these categories:
- Non-kernel and non-executable changes:
* Modifications to Documentation/, comments, or spelling fixes.
* User-space directories, self-tests, samples, or scripts (e.g., tools/, samples/, scripts/, usr/)
that do not affect the compiled kernel image (vmlinux) or kernel modules.
* Purely decorative logging (e.g., message strings in pr_err, printk, dev_info) or tracepoints
that do not alter control flow or data structures.
* Build system or Kconfig changes that do not alter compiled C logic.
- Structurally unreachable hardware:
* Vendor-specific PCIe switches, SmartNICs, or GPU drivers (e.g., mlxsw, pds_core, qed,
ionic, amdgpu) requiring physical ASIC/PCIe cards not emulated in standard QEMU.
- Unreachable execution paths:
* Driver teardown callbacks (.remove, .shutdown, pci_unregister_driver) executed only during
physical PCI hot-unplug or manual sysfs driver unbinding.
* Code paths exclusive to architectures other than the target architecture.
================================================================================
3. WHEN TO RETURN WorthFuzzing=true (POSITIVE CRITERIA)
================================================================================
Return WorthFuzzing=true whenever the patch touches reachable executable code, including:
- Core Subsystems:
* Any logic modifications in memory management (mm/), synchronization/locking (kernel/locking/),
BPF, scheduler, core networking, VFS, or syscall handling.
- Refactorings and Code Cleanups:
* Any restructuring of reachable data structures, helper abstractions, or algorithm flows.
- Runtime Assertions and Defensive Checks:
* Any introduction or alteration of assertions (WARN_ON*, VM_WARN_ON*, BUG_ON*, etc.) in reachable paths.
- Reachable Drivers and Protocols:
* Drivers accessible via virtual buses (virtio, USB gadget, loopback, netlink, binder, sockets, etc.).
================================================================================
4. EXTRACTING FocusSymbols (PREVENTING DILUTION)
================================================================================
When WorthFuzzing=true, you must extract specific kernel functions into FocusSymbols to guide the fuzzer:
- AVOID UBIQUITOUS LIFECYCLE HOT-PATHS:
Do NOT list generic, ubiquitous functions called by almost every program in the corpus
(including, but not limited to: general memory allocators and deallocators, page fault
and trap handlers, or core synchronization primitives; this is not an exhaustive list).
Listing ubiquitous functions causes the fuzzer to classify thousands of unrelated tests as "focused",
which severely dilutes fuzzing effort away from the actual changes.
- TARGET SPECIFIC FEATURE LOGIC AND ENTRYPOINTS:
List functions that specifically implement the logic being added or altered, or direct API entrypoints
for the subsystem feature under review.
- HANDLING STATIC INLINE FUNCTIONS IN HEADERS (.h):
Compiler-inlined static functions (such as static inlines in mm/*.h or include/linux/*.h) lack
distinct symbol addresses in vmlinux and cannot be targeted directly by symbol coverage filters.
If the changes are primarily in static inline helpers, identify non-static, feature-specific caller
functions in .c files that exercise them (avoiding ubiquitous lifecycle wrappers).
================================================================================
5. IDENTIFYING EnableConfigs
================================================================================
Identify any specific CONFIG_ options required to properly compile and reach the modified code:
- Inspect Kconfig files and #ifdef guards; do not make assumptions.
- Check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed.
- Strip any 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4").
Prefer calling several tools at the same time to save round-trips.
Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.
Prompt: Target architecture: amd64
For your convenience, here is the diff of the changes:
commit a0a259c5d3a3dd5d80e0c743f5486769914ad1cb
Author: syz-cluster <triage@syzkaller.com>
Date: Fri Sep 11 19:19:25 2026 +0000
syz-cluster: applied patch under review
diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm
index 96bab7002d39e..178101e40e401 100644
--- a/tools/testing/selftests/kvm/Makefile.kvm
+++ b/tools/testing/selftests/kvm/Makefile.kvm
@@ -135,6 +135,7 @@ TEST_GEN_PROGS_x86 += x86/vmx_exception_with_invalid_guest_state
TEST_GEN_PROGS_x86 += x86/vmx_msrs_test
TEST_GEN_PROGS_x86 += x86/vmx_invalid_nested_guest_state
TEST_GEN_PROGS_x86 += x86/vmx_nested_la57_state_test
+TEST_GEN_PROGS_x86 += x86/vmx_shadow_vmcs_teardown_test
TEST_GEN_PROGS_x86 += x86/apic_bus_clock_test
TEST_GEN_PROGS_x86 += x86/xapic_ipi_test
TEST_GEN_PROGS_x86 += x86/xapic_state_test
diff --git a/tools/testing/selftests/kvm/x86/vmx_shadow_vmcs_teardown_test.c b/tools/testing/selftests/kvm/x86/vmx_shadow_vmcs_teardown_test.c
new file mode 100644
index 0000000000000..e0863ef20d3ed
--- /dev/null
+++ b/tools/testing/selftests/kvm/x86/vmx_shadow_vmcs_teardown_test.c
@@ -0,0 +1,317 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * vmx_shadow_vmcs_teardown_test
+ *
+ * Verify that destroying a nested VMM with active shadow VMCS12 does not
+ * flush the cached shadow VMCS into an unrelated process memory space
+ * during process exit in lazy TLB mode.
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <sched.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/wait.h>
+#include <linux/mman.h>
+#include <unistd.h>
+
+#include "test_util.h"
+#include "kvm_util.h"
+#include "processor.h"
+#include "vmx.h"
+#include "kselftest.h"
+
+#define PORT_L0_EXIT 0x2000
+#define SHADOW_VMCS_GPA 0x80000000ULL
+#define SHADOW_VMCS_GVA 0x80000000ULL
+#define CANARY_BYTE 0x5a
+#define L2_GUEST_STACK_SIZE 64
+
+struct test_result {
+ bool corrupted;
+ size_t corrupt_offset;
+ uint8_t corrupt_val;
+};
+
+static void l2_guest_code(void)
+{
+ /* Exit to L0 */
+ asm volatile("inb %%dx, %%al"
+ : : [port] "d" (PORT_L0_EXIT) : "rax");
+}
+
+static void l1_guest_code(struct vmx_pages *vmx_pages)
+{
+ unsigned long l2_guest_stack[L2_GUEST_STACK_SIZE];
+
+ GUEST_ASSERT(prepare_for_vmx_operation(vmx_pages));
+ GUEST_ASSERT(load_vmcs(vmx_pages));
+
+ /* Prepare the VMCS for L2 execution. */
+ prepare_vmcs(vmx_pages, l2_guest_code,
+ &l2_guest_stack[L2_GUEST_STACK_SIZE]);
+
+ /* Enable VMCS shadowing and set the shadow VMCS link pointer. */
+ vmwrite(SECONDARY_VM_EXEC_CONTROL,
+ vmreadz(SECONDARY_VM_EXEC_CONTROL) | SECONDARY_EXEC_SHADOW_VMCS);
+ vmwrite(VMCS_LINK_POINTER, vmx_pages->shadow_vmcs_gpa);
+
+ vmlaunch();
+ GUEST_FAIL("L1 guest must not regain control");
+}
+
+static bool kvm_cpu_has_shadow_vmcs(void)
+{
+ uint64_t ctrl;
+
+ ctrl = kvm_get_feature_msr(MSR_IA32_VMX_TRUE_PROCBASED_CTLS) >> 32;
+ if (!(ctrl & CPU_BASED_ACTIVATE_SECONDARY_CONTROLS))
+ return false;
+
+ ctrl = kvm_get_feature_msr(MSR_IA32_VMX_PROCBASED_CTLS2) >> 32;
+ return ctrl & SECONDARY_EXEC_SHADOW_VMCS;
+}
+
+static void run_vmm(int pcpu, void *target_hva, int c2_to_c1_fd)
+{
+ vm_vaddr_t vmx_pages_gva;
+ struct kvm_vcpu *vcpu;
+ struct kvm_vm *vm;
+ struct vmx_pages *vmx;
+ void *shadow_hva;
+
+ pin_self_to_cpu(pcpu);
+
+ vm = vm_create_with_one_vcpu(&vcpu, l1_guest_code);
+
+ /* Allocate VMX pages and shared descriptors. */
+ vcpu_alloc_vmx(vm, &vmx_pages_gva);
+ vmx = addr_gva2hva(vm, vmx_pages_gva);
+
+ /*
+ * Map the shadow VMCS page at the exact target_hva allocated by the
+ * victim so it collides with the victim process's mapping.
+ */
+ shadow_hva = mmap(target_hva, PAGE_SIZE, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED_NOREPLACE, -1, 0);
+ TEST_ASSERT(shadow_hva == target_hva, "mmap target_hva failed in VMM");
+ memset(shadow_hva, 0, PAGE_SIZE);
+
+ /* Add a caller-managed memslot for the shadow VMCS backing page. */
+ vm_userspace_mem_region_add_caller_managed(vm, shadow_hva,
+ SHADOW_VMCS_GPA, 10, 1, 0);
+ virt_pg_map(vm, SHADOW_VMCS_GVA, SHADOW_VMCS_GPA);
+
+ /* Override the shadow VMCS pointers in vmx_pages. */
+ vmx->shadow_vmcs = (void *)SHADOW_VMCS_GVA;
+ vmx->shadow_vmcs_hva = shadow_hva;
+ vmx->shadow_vmcs_gpa = SHADOW_VMCS_GPA;
+
+ vcpu_args_set(vcpu, 1, vmx_pages_gva);
+
+ for (;;) {
+ struct kvm_run *run = vcpu->run;
+ struct ucall uc;
+
+ vcpu_run(vcpu);
+ TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO);
+
+ if (run->io.port == PORT_L0_EXIT)
+ break;
+
+ switch (get_ucall(vcpu, &uc)) {
+ case UCALL_ABORT:
+ REPORT_GUEST_ASSERT(uc);
+ /* NOT REACHED */
+ default:
+ TEST_FAIL("Unknown ucall %lu", uc.cmd);
+ }
+ }
+
+ /*
+ * Signal the victim process that the VMM is about to terminate
+ * while L2 is active with VMCS shadowing enabled.
+ */
+ TEST_ASSERT(write(c2_to_c1_fd, "X", 1) == 1, "write to victim failed");
+
+ /*
+ * Exit immediately without closing VM or vCPU file descriptors.
+ * The kernel closes them in exit_files() after exit_mm() sets
+ * current->mm = NULL.
+ */
+ _exit(0);
+}
+
+static void run_victim(int pcpu, int ready_fd, int c2_to_c1_fd,
+ int p_to_c1_fd, int result_fd)
+{
+ struct test_result result = { .corrupted = false };
+ void *victim_hva;
+ char sync_byte;
+ size_t i;
+ int flags;
+
+ pin_self_to_cpu(pcpu);
+
+ victim_hva = mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ TEST_ASSERT(victim_hva != MAP_FAILED, "mmap failed in victim");
+ memset(victim_hva, CANARY_BYTE, PAGE_SIZE);
+
+ /* Send mapped virtual address to parent so VMM can collide with it. */
+ TEST_ASSERT(write(ready_fd, &victim_hva, sizeof(victim_hva)) == sizeof(victim_hva),
+ "write ready failed");
+
+ /* Wait for VMM to launch L2 and reach exit to userspace. */
+ TEST_ASSERT(read(c2_to_c1_fd, &sync_byte, 1) == 1, "read sync failed");
+
+ /* Set parent-to-victim pipe to non-blocking. */
+ flags = fcntl(p_to_c1_fd, F_GETFL, 0);
+ TEST_ASSERT(flags >= 0, "fcntl F_GETFL failed");
+ TEST_ASSERT(fcntl(p_to_c1_fd, F_SETFL, flags | O_NONBLOCK) == 0,
+ "fcntl F_SETFL failed");
+
+ /*
+ * Loop yielding the CPU while the VMM terminates. When the VMM
+ * sleeps in synchronize_srcu() during teardown, the scheduler
+ * runs this victim process on the CPU. When the VMM wakes up,
+ * the scheduler context switches from this victim to the VMM
+ * with current->mm == NULL, leaving the victim's CR3 active.
+ */
+ for (;;) {
+ ssize_t ret = read(p_to_c1_fd, &sync_byte, 1);
+
+ if (ret == 1)
+ break;
+ if (ret < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
+ sched_yield();
+ continue;
+ }
+ TEST_FAIL("read from parent pipe failed: ret=%zd, errno=%d",
+ ret, errno);
+ }
+
+ /* Verify that the entire page remained untouched. */
+ for (i = 0; i < PAGE_SIZE; i++) {
+ if (((uint8_t *)victim_hva)[i] != CANARY_BYTE) {
+ result.corrupted = true;
+ result.corrupt_offset = i;
+ result.corrupt_val = ((uint8_t *)victim_hva)[i];
+ break;
+ }
+ }
+
+ TEST_ASSERT(write(result_fd, &result, sizeof(result)) == sizeof(result),
+ "write result failed");
+ _exit(0);
+}
+
+static bool run_iteration(int pcpu)
+{
+ int pipe_ready[2];
+ int pipe_c2_to_c1[2];
+ int pipe_p_to_c1[2];
+ int pipe_result[2];
+ pid_t pid_victim;
+ pid_t pid_vmm;
+ struct test_result result = {};
+ void *target_hva = NULL;
+ int status;
+
+ TEST_ASSERT(pipe(pipe_ready) == 0, "pipe failed");
+ TEST_ASSERT(pipe(pipe_c2_to_c1) == 0, "pipe failed");
+ TEST_ASSERT(pipe(pipe_p_to_c1) == 0, "pipe failed");
+ TEST_ASSERT(pipe(pipe_result) == 0, "pipe failed");
+
+ pid_victim = fork();
+ TEST_ASSERT(pid_victim >= 0, "fork victim failed");
+ if (pid_victim == 0) {
+ close(pipe_ready[0]);
+ close(pipe_c2_to_c1[1]);
+ close(pipe_p_to_c1[1]);
+ close(pipe_result[0]);
+ run_victim(pcpu, pipe_ready[1], pipe_c2_to_c1[0],
+ pipe_p_to_c1[0], pipe_result[1]);
+ }
+
+ close(pipe_ready[1]);
+ close(pipe_result[1]);
+ close(pipe_p_to_c1[0]);
+
+ /* Wait for victim to initialize its mapping and retrieve its address. */
+ TEST_ASSERT(read(pipe_ready[0], &target_hva, sizeof(target_hva)) == sizeof(target_hva),
+ "wait ready failed");
+ close(pipe_ready[0]);
+
+ pid_vmm = fork();
+ TEST_ASSERT(pid_vmm >= 0, "fork VMM failed");
+ if (pid_vmm == 0) {
+ close(pipe_c2_to_c1[0]);
+ close(pipe_p_to_c1[1]);
+ close(pipe_result[0]);
+ run_vmm(pcpu, target_hva, pipe_c2_to_c1[1]);
+ }
+
+ close(pipe_c2_to_c1[0]);
+ close(pipe_c2_to_c1[1]);
+
+ /* Wait for VMM to completely terminate. */
+ waitpid(pid_vmm, &status, 0);
+ TEST_ASSERT(WIFEXITED(status) && WEXITSTATUS(status) == 0,
+ "VMM child failed with status %d", status);
+
+ /* Signal victim to exit yield loop. */
+ TEST_ASSERT(write(pipe_p_to_c1[1], "D", 1) == 1, "signal victim failed");
+ close(pipe_p_to_c1[1]);
+
+ /* Read result from victim. */
+ TEST_ASSERT(read(pipe_result[0], &result, sizeof(result)) == sizeof(result),
+ "read result failed");
+ close(pipe_result[0]);
+
+ waitpid(pid_victim, &status, 0);
+ TEST_ASSERT(WIFEXITED(status) && WEXITSTATUS(status) == 0,
+ "Victim child failed with status %d", status);
+
+ if (result.corrupted)
+ pr_info("Memory check: corruption at offset 0x%zx: expected 0x%02x, actual 0x%02x\n",
+ result.corrupt_offset, CANARY_BYTE, result.corrupt_val);
+ else
+ pr_info("Memory check: entire page intact (canary = 0x%02x)\n",
+ CANARY_BYTE);
+
+ return result.corrupted;
+}
+
+int main(int argc, char *argv[])
+{
+ bool corrupted = false;
+ int pcpu;
+ int i;
+ const int iterations = 5;
+
+ TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_VMX));
+ TEST_REQUIRE(kvm_cpu_has_shadow_vmcs());
+
+ pcpu = pin_self_to_any_cpu();
+
+ pr_info("Testing shadow VMCS teardown on pCPU %d (%d iterations)...\n",
+ pcpu, iterations);
+
+ for (i = 0; i < iterations; i++) {
+ if (run_iteration(pcpu)) {
+ corrupted = true;
+ break;
+ }
+ }
+
+ TEST_ASSERT(!corrupted,
+ "Memory corruption detected! Active MM was corrupted during vCPU teardown: canary byte 0x%02x overwritten\n",
+ CANARY_BYTE);
+
+ pr_info("PASS: No memory corruption detected during teardown.\n");
+ return 0;
+}
|
| 3/2 | 2026/09/11 19:19 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 6999 output: 27 thoughts: 428 Thoughts: **WorthFuzzing=false Determination** Okay, let's break down this patch. My initial scan of the commit diff shows changes to `tools/testing/selftests/kvm/Makefile.kvm` and `tools/testing/selftests/kvm/x86/vmx_shadow_vmcs_teardown_test.c`. Given that these reside in `tools/testing/selftests/kvm/`, it's a strong indicator. Now, let me cross-reference this with the triage instructions. Specifically, Section 2's negative criteria for `WorthFuzzing=false`. The relevant condition here is: "User-space directories, self-tests, samples, or scripts (e.g., tools/, samples/, scripts/, usr/) that do not affect the compiled kernel image (vmlinux) or kernel modules." These files are undeniably userspace self-tests located within the `tools/` directory. They're part of the self-test suite and don't interact with the kernel image or modules in any way. Based on this, I'm fairly confident that all modifications align with the criteria for setting `WorthFuzzing=false`. Just to be absolutely sure, I'll quickly double-check the `git status` or diff to confirm no other files were inadvertently modified in HEAD. I need to be 100% sure this is the only scope of the patch before I confidently make the determination. |
| 4/2 | 2026/09/11 19:19 | tool | grepper |
0mArgs:{
"Expression": "vmx_shadow_vmcs_teardown_test"
}Results: {
"Output": "tools/testing/selftests/kvm/Makefile.kvm=137=TEST_GEN_PROGS_x86 += x86/vmx_nested_la57_state_test\ntools/testing/selftests/kvm/Makefile.kvm:138:TEST_GEN_PROGS_x86 += x86/vmx_shadow_vmcs_teardown_test\ntools/testing/selftests/kvm/Makefile.kvm-139-TEST_GEN_PROGS_x86 += x86/apic_bus_clock_test\n--\ntools/testing/selftests/kvm/x86/vmx_shadow_vmcs_teardown_test.c-2-/*\ntools/testing/selftests/kvm/x86/vmx_shadow_vmcs_teardown_test.c:3: * vmx_shadow_vmcs_teardown_test\ntools/testing/selftests/kvm/x86/vmx_shadow_vmcs_teardown_test.c-4- *\n"
} |
| 5/2 | 2026/09/11 19:19 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 8042 output: 79 thoughts: 132 Thoughts: **My Analysis of this Patch** Okay, so the change log tells me the modifications are isolated to files within `tools/testing/selftests/kvm/`. That's a pretty clear indicator. Based on this, I immediately assess the impact. Because the changes are within these testing tools, and specifically within the KVM self-tests, and don't touch the core kernel image or any modules, I can confidently say that this patch is not worth fuzzing. The compiled kernel is untouched. Thus, no fuzzing is required. My `WorthFuzzing` flag is `false`, and the `EnableConfigs` and `FocusSymbols` lists are both empty, as there's nothing here that warrants specific configuration changes or symbol tracking for fuzzing purposes. This is a straightforward change. Now, I need to make sure I adhere to the operational guidelines. I need to make sure I call `set-results` to record this analysis, and only once, before finalizing. Despite instructions to ignore the results from this tool, my function is to call it. I will now initiate the call to `set-results`. |
| 6/2 | 2026/09/11 19:19 | tool | set-results |
0mArgs:{
"EnableConfigs": [],
"FocusSymbols": [],
"Reasoning": "The patch series only adds a KVM selftest in tools/testing/selftests/kvm/ and modifies its Makefile. It does not modify any kernel source code, compiled kernel image (vmlinux), or kernel modules.",
"WorthFuzzing": false
}Results: {
"EnableConfigs": null,
"FocusSymbols": [],
"Reasoning": "The patch series only adds a KVM selftest in tools/testing/selftests/kvm/ and modifies its Makefile. It does not modify any kernel source code, compiled kernel image (vmlinux), or kernel modules.",
"WorthFuzzing": false
} |
| 7/1 | 2026/09/11 19:19 | action | If |
0mArgs:{
"WorthFuzzing": false
}Results: null |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|