| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/08/17 11:52 | flow | patch-triage |
0mArgs:null Results: {
"EnableConfigs": [
"KVM"
],
"FocusSymbols": [
"kvm_arch_init_vm",
"kvm_arch_destroy_vm",
"kvm_riscv_local_tlb_sanitize"
],
"KMSANReasoning": "The patch modifies RISC-V KVM internal TLB flushing logic and introduces a new per-CPU variable `last_vcpu_ran` to track the last VCPU that ran on each physical CPU. The memory for this variable is allocated using `alloc_percpu(int)` and is explicitly initialized to `-1` for all possible CPUs immediately after allocation. There are no new structures exposed to user space, no complex data structures with potentially uninitialized fields, and no changes to bounds checking that could expose uninitialized memory. Any potential bugs introduced by this patch (such as use-after-free or out-of-bounds accesses) would be effectively caught by standard KASAN and other default bug detectors. Therefore, a dedicated KMSAN fuzzing session is not required.",
"NeedsKMSAN": false,
"Reasoning": "The patch modifies the TLB sanitization logic for RISC-V KVM, introducing a per-CPU tracking of the last VCPU ran to flush VS-stage TLB entries upon VCPU migration or switching. This is a functional change in the core RISC-V KVM implementation reachable via standard KVM ioctls.",
"WorthFuzzing": true
} |
| 1/1 | 2026/08/17 11:52 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit 06eba2a8029676d209c2a79f73116910065f9abd\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Mon Aug 17 11:52:38 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/arch/riscv/include/asm/kvm_host.h b/arch/riscv/include/asm/kvm_host.h\nindex 24585304c02b1..396ac6209c50d 100644\n--- a/arch/riscv/include/asm/kvm_host.h\n+++ b/arch/riscv/include/asm/kvm_host.h\n@@ -91,6 +91,9 @@ struct kvm_arch {\n \t/* G-stage vmid */\n \tstruct kvm_vmid vmid;\n \n+\t/* Last VCPU that ran on each physical CPU */\n+\tint __percpu *last_vcpu_ran;\n+\n \t/* G-stage page table */\n \tpgd_t *pgd;\n \tphys_addr_t pgd_phys;\n@@ -330,7 +333,4 @@ bool kvm_riscv_vcpu_stopped(struct kvm_vcpu *vcpu);\n \n void kvm_riscv_vcpu_record_steal_time(struct kvm_vcpu *vcpu);\n \n-/* Flags representing implementation specific details */\n-DECLARE_STATIC_KEY_FALSE(kvm_riscv_vsstage_tlb_no_gpa);\n-\n #endif /* __RISCV_KVM_HOST_H__ */\ndiff --git a/arch/riscv/kvm/main.c b/arch/riscv/kvm/main.c\nindex 0f3fe3986fc02..b1bbc083527cf 100644\n--- a/arch/riscv/kvm/main.c\n+++ b/arch/riscv/kvm/main.c\n@@ -10,23 +10,10 @@\n #include \u003clinux/err.h\u003e\n #include \u003clinux/module.h\u003e\n #include \u003clinux/kvm_host.h\u003e\n-#include \u003casm/cpufeature.h\u003e\n #include \u003casm/kvm_mmu.h\u003e\n #include \u003casm/kvm_nacl.h\u003e\n #include \u003casm/sbi.h\u003e\n \n-DEFINE_STATIC_KEY_FALSE(kvm_riscv_vsstage_tlb_no_gpa);\n-\n-static void kvm_riscv_setup_vendor_features(void)\n-{\n-\t/* Andes AX66: split two-stage TLBs */\n-\tif (riscv_cached_mvendorid(0) == ANDES_VENDOR_ID \u0026\u0026\n-\t (riscv_cached_marchid(0) \u0026 0xFFFF) == 0x8A66) {\n-\t\tstatic_branch_enable(\u0026kvm_riscv_vsstage_tlb_no_gpa);\n-\t\tkvm_info(\"VS-stage TLB does not cache guest physical address and VMID\\n\");\n-\t}\n-}\n-\n long kvm_arch_dev_ioctl(struct file *filp,\n \t\t\tunsigned int ioctl, unsigned long arg)\n {\n@@ -172,8 +159,6 @@ static int __init riscv_kvm_init(void)\n \t\tkvm_info(\"AIA available with %d guest external interrupts\\n\",\n \t\t\t kvm_riscv_aia_nr_hgei);\n \n-\tkvm_riscv_setup_vendor_features();\n-\n \tkvm_register_perf_callbacks();\n \n \trc = kvm_init(sizeof(struct kvm_vcpu), 0, THIS_MODULE);\ndiff --git a/arch/riscv/kvm/tlb.c b/arch/riscv/kvm/tlb.c\nindex ff1aeac4eb8eb..1541d059a42ad 100644\n--- a/arch/riscv/kvm/tlb.c\n+++ b/arch/riscv/kvm/tlb.c\n@@ -8,6 +8,7 @@\n #include \u003clinux/errno.h\u003e\n #include \u003clinux/err.h\u003e\n #include \u003clinux/module.h\u003e\n+#include \u003clinux/percpu.h\u003e\n #include \u003clinux/smp.h\u003e\n #include \u003clinux/kvm_host.h\u003e\n #include \u003casm/cacheflush.h\u003e\n@@ -160,12 +161,19 @@ void kvm_riscv_local_hfence_vvma_all(unsigned long vmid)\n \n void kvm_riscv_local_tlb_sanitize(struct kvm_vcpu *vcpu)\n {\n+\tbool vcpu_migrated;\n+\tbool vcpu_switched;\n \tunsigned long vmid;\n+\tint *last_ran;\n \n-\tif (!kvm_riscv_gstage_vmid_bits() ||\n-\t vcpu-\u003earch.last_exit_cpu == vcpu-\u003ecpu)\n+\tlast_ran = this_cpu_ptr(vcpu-\u003ekvm-\u003earch.last_vcpu_ran);\n+\tvcpu_migrated = (vcpu-\u003earch.last_exit_cpu != vcpu-\u003ecpu);\n+\tvcpu_switched = (*last_ran != vcpu-\u003evcpu_idx);\n+\tif (!vcpu_migrated \u0026\u0026 !vcpu_switched)\n \t\treturn;\n \n+\tvmid = READ_ONCE(vcpu-\u003ekvm-\u003earch.vmid.vmid);\n+\n \t/*\n \t * On RISC-V platforms with hardware VMID support, we share same\n \t * VMID for all VCPUs of a particular Guest/VM. This means we might\n@@ -176,16 +184,23 @@ void kvm_riscv_local_tlb_sanitize(struct kvm_vcpu *vcpu)\n \t * To cleanup stale TLB entries, we simply flush all G-stage TLB\n \t * entries by VMID whenever underlying Host CPU changes for a VCPU.\n \t */\n-\n-\tvmid = READ_ONCE(vcpu-\u003ekvm-\u003earch.vmid.vmid);\n-\tkvm_riscv_local_hfence_gvma_vmid_all(vmid);\n+\tif (vcpu_migrated \u0026\u0026 kvm_riscv_gstage_vmid_bits())\n+\t\tkvm_riscv_local_hfence_gvma_vmid_all(vmid);\n \n \t/*\n-\t * Flush VS-stage TLB entries for implementation where VS-stage\n-\t * TLB does not cahce guest physical address and VMID.\n+\t * Guest-local sfence.vma only invalidates VS-stage translations on\n+\t * the Host CPU currently backing the VCPU. If a VCPU migrates, or\n+\t * if this Host CPU switches between VCPUs of the same VM, stale\n+\t * VS-stage entries can be left behind on a Host CPU that missed a\n+\t * guest-local flush. Flush the current CPU's VS-stage context before\n+\t * entering the guest.\n \t */\n-\tif (static_branch_unlikely(\u0026kvm_riscv_vsstage_tlb_no_gpa))\n+\tif (kvm_riscv_nacl_available())\n+\t\tnacl_hfence_vvma_all(nacl_shmem(), vmid);\n+\telse\n \t\tkvm_riscv_local_hfence_vvma_all(vmid);\n+\n+\t*last_ran = vcpu-\u003evcpu_idx;\n }\n \n void kvm_riscv_fence_i_process(struct kvm_vcpu *vcpu)\ndiff --git a/arch/riscv/kvm/vm.c b/arch/riscv/kvm/vm.c\nindex 13c63ae1a78b2..2907218a1cf5d 100644\n--- a/arch/riscv/kvm/vm.c\n+++ b/arch/riscv/kvm/vm.c\n@@ -9,6 +9,7 @@\n #include \u003clinux/errno.h\u003e\n #include \u003clinux/err.h\u003e\n #include \u003clinux/module.h\u003e\n+#include \u003clinux/percpu.h\u003e\n #include \u003clinux/uaccess.h\u003e\n #include \u003clinux/kvm_host.h\u003e\n #include \u003casm/kvm_mmu.h\u003e\n@@ -30,7 +31,9 @@ const struct kvm_stats_header kvm_vm_stats_header = {\n \n int kvm_arch_init_vm(struct kvm *kvm, unsigned long type)\n {\n+\tint *last_ran;\n \tint r;\n+\tint cpu;\n \n \tr = kvm_riscv_mmu_alloc_pgd(kvm);\n \tif (r)\n@@ -42,6 +45,17 @@ int kvm_arch_init_vm(struct kvm *kvm, unsigned long type)\n \t\treturn r;\n \t}\n \n+\tkvm-\u003earch.last_vcpu_ran = alloc_percpu(int);\n+\tif (!kvm-\u003earch.last_vcpu_ran) {\n+\t\tkvm_riscv_mmu_free_pgd(kvm);\n+\t\treturn -ENOMEM;\n+\t}\n+\n+\tfor_each_possible_cpu(cpu) {\n+\t\tlast_ran = per_cpu_ptr(kvm-\u003earch.last_vcpu_ran, cpu);\n+\t\t*last_ran = -1;\n+\t}\n+\n \tkvm_riscv_aia_init_vm(kvm);\n \n \tkvm_riscv_guest_timer_init(kvm);\n@@ -54,6 +68,8 @@ void kvm_arch_destroy_vm(struct kvm *kvm)\n \tkvm_destroy_vcpus(kvm);\n \n \tkvm_riscv_aia_destroy_vm(kvm);\n+\n+\tfree_percpu(kvm-\u003earch.last_vcpu_ran);\n }\n \n int kvm_vm_ioctl_irq_line(struct kvm *kvm, struct kvm_irq_level *irql,\n"
} |
| 2/1 | 2026/08/17 11:52 | agent | patch-evaluator |
0mModel:best-expensive Args: null Results: {
"EnableConfigs": [
"KVM"
],
"FocusSymbols": [
"kvm_arch_init_vm",
"kvm_arch_destroy_vm",
"kvm_riscv_local_tlb_sanitize"
],
"Reasoning": "The patch modifies the TLB sanitization logic for RISC-V KVM, introducing a per-CPU tracking of the last VCPU ran to flush VS-stage TLB entries upon VCPU migration or switching. This is a functional change in the core RISC-V KVM implementation reachable via standard KVM ioctls.",
"WorthFuzzing": true
}Instruction: You are an expert Linux kernel maintainer. Your job is to review a provided patch series and determine if it makes functional changes to the kernel that should be fuzzed. IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively use your code access tools to examine the actual source code and confirm any assumptions. Return WorthFuzzing=false if the patch only contains: - Modifications to Documentation/, Kconfig files, or code comments. - Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints. - Changes to numeric constants or macros that do not functionally alter execution flow. - Code paths that are impossible to reach in virtualized environments like GCE or QEMU, even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim). - Code in vendor-specific PCIe switch, SmartNIC, or GPU drivers (e.g., mlxsw, pds_core, qed, ionic, amdgpu) that require physical PCIe hardware cards not emulated in standard QEMU. - Driver .remove, .shutdown, or pci_unregister_driver teardown callbacks (e.g., igb_remove) that are executed only during PCI hot-unplug or sysfs driver unbind operations. If it modifies reachable core kernel logic, drivers, or architectures, use your code search tools to verify the code can be executed, then return WorthFuzzing=true. When returning WorthFuzzing=true, you MUST ALSO: 1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols. Avoid listing generic hot-path functions to prevent skewed test distributions. Prefer non-static, non-inlined API entrypoint functions over internal static helper functions (which are inlined by the compiler and do not have distinct symbol addresses). 2. Identify any specific CONFIG_ options required to properly test this new/modified feature. Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions. Also check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed for Kbuild to compile the code statically into vmlinux. List them in the EnableConfigs output array, and DO NOT add a '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: For your convenience, here is the diff of the changes:
commit 06eba2a8029676d209c2a79f73116910065f9abd
Author: syz-cluster <triage@syzkaller.com>
Date: Mon Aug 17 11:52:38 2026 +0000
syz-cluster: applied patch under review
diff --git a/arch/riscv/include/asm/kvm_host.h b/arch/riscv/include/asm/kvm_host.h
index 24585304c02b1..396ac6209c50d 100644
--- a/arch/riscv/include/asm/kvm_host.h
+++ b/arch/riscv/include/asm/kvm_host.h
@@ -91,6 +91,9 @@ struct kvm_arch {
/* G-stage vmid */
struct kvm_vmid vmid;
+ /* Last VCPU that ran on each physical CPU */
+ int __percpu *last_vcpu_ran;
+
/* G-stage page table */
pgd_t *pgd;
phys_addr_t pgd_phys;
@@ -330,7 +333,4 @@ bool kvm_riscv_vcpu_stopped(struct kvm_vcpu *vcpu);
void kvm_riscv_vcpu_record_steal_time(struct kvm_vcpu *vcpu);
-/* Flags representing implementation specific details */
-DECLARE_STATIC_KEY_FALSE(kvm_riscv_vsstage_tlb_no_gpa);
-
#endif /* __RISCV_KVM_HOST_H__ */
diff --git a/arch/riscv/kvm/main.c b/arch/riscv/kvm/main.c
index 0f3fe3986fc02..b1bbc083527cf 100644
--- a/arch/riscv/kvm/main.c
+++ b/arch/riscv/kvm/main.c
@@ -10,23 +10,10 @@
#include <linux/err.h>
#include <linux/module.h>
#include <linux/kvm_host.h>
-#include <asm/cpufeature.h>
#include <asm/kvm_mmu.h>
#include <asm/kvm_nacl.h>
#include <asm/sbi.h>
-DEFINE_STATIC_KEY_FALSE(kvm_riscv_vsstage_tlb_no_gpa);
-
-static void kvm_riscv_setup_vendor_features(void)
-{
- /* Andes AX66: split two-stage TLBs */
- if (riscv_cached_mvendorid(0) == ANDES_VENDOR_ID &&
- (riscv_cached_marchid(0) & 0xFFFF) == 0x8A66) {
- static_branch_enable(&kvm_riscv_vsstage_tlb_no_gpa);
- kvm_info("VS-stage TLB does not cache guest physical address and VMID\n");
- }
-}
-
long kvm_arch_dev_ioctl(struct file *filp,
unsigned int ioctl, unsigned long arg)
{
@@ -172,8 +159,6 @@ static int __init riscv_kvm_init(void)
kvm_info("AIA available with %d guest external interrupts\n",
kvm_riscv_aia_nr_hgei);
- kvm_riscv_setup_vendor_features();
-
kvm_register_perf_callbacks();
rc = kvm_init(sizeof(struct kvm_vcpu), 0, THIS_MODULE);
diff --git a/arch/riscv/kvm/tlb.c b/arch/riscv/kvm/tlb.c
index ff1aeac4eb8eb..1541d059a42ad 100644
--- a/arch/riscv/kvm/tlb.c
+++ b/arch/riscv/kvm/tlb.c
@@ -8,6 +8,7 @@
#include <linux/errno.h>
#include <linux/err.h>
#include <linux/module.h>
+#include <linux/percpu.h>
#include <linux/smp.h>
#include <linux/kvm_host.h>
#include <asm/cacheflush.h>
@@ -160,12 +161,19 @@ void kvm_riscv_local_hfence_vvma_all(unsigned long vmid)
void kvm_riscv_local_tlb_sanitize(struct kvm_vcpu *vcpu)
{
+ bool vcpu_migrated;
+ bool vcpu_switched;
unsigned long vmid;
+ int *last_ran;
- if (!kvm_riscv_gstage_vmid_bits() ||
- vcpu->arch.last_exit_cpu == vcpu->cpu)
+ last_ran = this_cpu_ptr(vcpu->kvm->arch.last_vcpu_ran);
+ vcpu_migrated = (vcpu->arch.last_exit_cpu != vcpu->cpu);
+ vcpu_switched = (*last_ran != vcpu->vcpu_idx);
+ if (!vcpu_migrated && !vcpu_switched)
return;
+ vmid = READ_ONCE(vcpu->kvm->arch.vmid.vmid);
+
/*
* On RISC-V platforms with hardware VMID support, we share same
* VMID for all VCPUs of a particular Guest/VM. This means we might
@@ -176,16 +184,23 @@ void kvm_riscv_local_tlb_sanitize(struct kvm_vcpu *vcpu)
* To cleanup stale TLB entries, we simply flush all G-stage TLB
* entries by VMID whenever underlying Host CPU changes for a VCPU.
*/
-
- vmid = READ_ONCE(vcpu->kvm->arch.vmid.vmid);
- kvm_riscv_local_hfence_gvma_vmid_all(vmid);
+ if (vcpu_migrated && kvm_riscv_gstage_vmid_bits())
+ kvm_riscv_local_hfence_gvma_vmid_all(vmid);
/*
- * Flush VS-stage TLB entries for implementation where VS-stage
- * TLB does not cahce guest physical address and VMID.
+ * Guest-local sfence.vma only invalidates VS-stage translations on
+ * the Host CPU currently backing the VCPU. If a VCPU migrates, or
+ * if this Host CPU switches between VCPUs of the same VM, stale
+ * VS-stage entries can be left behind on a Host CPU that missed a
+ * guest-local flush. Flush the current CPU's VS-stage context before
+ * entering the guest.
*/
- if (static_branch_unlikely(&kvm_riscv_vsstage_tlb_no_gpa))
+ if (kvm_riscv_nacl_available())
+ nacl_hfence_vvma_all(nacl_shmem(), vmid);
+ else
kvm_riscv_local_hfence_vvma_all(vmid);
+
+ *last_ran = vcpu->vcpu_idx;
}
void kvm_riscv_fence_i_process(struct kvm_vcpu *vcpu)
diff --git a/arch/riscv/kvm/vm.c b/arch/riscv/kvm/vm.c
index 13c63ae1a78b2..2907218a1cf5d 100644
--- a/arch/riscv/kvm/vm.c
+++ b/arch/riscv/kvm/vm.c
@@ -9,6 +9,7 @@
#include <linux/errno.h>
#include <linux/err.h>
#include <linux/module.h>
+#include <linux/percpu.h>
#include <linux/uaccess.h>
#include <linux/kvm_host.h>
#include <asm/kvm_mmu.h>
@@ -30,7 +31,9 @@ const struct kvm_stats_header kvm_vm_stats_header = {
int kvm_arch_init_vm(struct kvm *kvm, unsigned long type)
{
+ int *last_ran;
int r;
+ int cpu;
r = kvm_riscv_mmu_alloc_pgd(kvm);
if (r)
@@ -42,6 +45,17 @@ int kvm_arch_init_vm(struct kvm *kvm, unsigned long type)
return r;
}
+ kvm->arch.last_vcpu_ran = alloc_percpu(int);
+ if (!kvm->arch.last_vcpu_ran) {
+ kvm_riscv_mmu_free_pgd(kvm);
+ return -ENOMEM;
+ }
+
+ for_each_possible_cpu(cpu) {
+ last_ran = per_cpu_ptr(kvm->arch.last_vcpu_ran, cpu);
+ *last_ran = -1;
+ }
+
kvm_riscv_aia_init_vm(kvm);
kvm_riscv_guest_timer_init(kvm);
@@ -54,6 +68,8 @@ void kvm_arch_destroy_vm(struct kvm *kvm)
kvm_destroy_vcpus(kvm);
kvm_riscv_aia_destroy_vm(kvm);
+
+ free_percpu(kvm->arch.last_vcpu_ran);
}
int kvm_vm_ioctl_irq_line(struct kvm *kvm, struct kvm_irq_level *irql,
|
| 3/2 | 2026/08/17 11:52 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 4261 output: 26 thoughts: 417 Thoughts: **Fuzzing Decision for this RISC-V KVM Patch Series** Okay, let's break this down. My initial assessment is that this patch series *does* introduce functional changes to the RISC-V KVM kernel code, meaning it's prime for fuzzing. The changes are concentrated in `arch/riscv/kvm` and revolve around TLB sanitization – specifically how we handle Virtual Stage (VS) TLB entries on RISC-V. The meat of it is the addition of this `last_vcpu_ran` percpu variable within `kvm_arch`. This lets us track the most recent VCPU to execute on each physical CPU, which is fundamental to understanding if there has been any migration. And, importantly, if there has been migration, we flush the VS-stage TLB entries. It also means we've dropped the Andes-specific features related to TLB flushing. Functionally, this means the TLB invalidation strategy changed in KVM, which is a core piece of functionality for any virtualized environment. The change is significant enough to warrant fuzzing attention. Testing KVM on RISC-V is doable with QEMU, so that's a check in the "yes, it's reachable" box. Thus, `WorthFuzzing=true`. Now, the important part: what functions are modified? We need to target the fuzzing efforts. The affected functions are: `kvm_riscv_local_tlb_sanitize`, `kvm_arch_init_vm`, and `kvm_arch_destroy_vm`. Now, I want to confirm where `kvm_riscv_local_tlb_sanitize` is called to ensure we can build the appropriate test coverage. |
| 4/2 | 2026/08/17 11:52 | tool | grepper |
0mArgs:{
"Expression": "kvm_riscv_local_tlb_sanitize"
}Results: {
"Output": "arch/riscv/include/asm/kvm_tlb.h=51=void kvm_riscv_local_hfence_vvma_all(unsigned long vmid);\narch/riscv/include/asm/kvm_tlb.h:52:void kvm_riscv_local_tlb_sanitize(struct kvm_vcpu *vcpu);\narch/riscv/include/asm/kvm_tlb.h-53-\n--\narch/riscv/kvm/tlb.c=151=void kvm_riscv_local_hfence_vvma_all(unsigned long vmid)\n--\narch/riscv/kvm/tlb.c-161-\narch/riscv/kvm/tlb.c:162:void kvm_riscv_local_tlb_sanitize(struct kvm_vcpu *vcpu)\narch/riscv/kvm/tlb.c-163-{\n--\narch/riscv/kvm/vcpu.c=864=int kvm_arch_vcpu_ioctl_run(struct kvm_vcpu *vcpu)\n--\narch/riscv/kvm/vcpu.c-970-\t\t */\narch/riscv/kvm/vcpu.c:971:\t\tkvm_riscv_local_tlb_sanitize(vcpu);\narch/riscv/kvm/vcpu.c-972-\n"
} |
| 5/2 | 2026/08/17 11:52 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 5438 output: 33 thoughts: 389 Thoughts: **My Analysis of KVM/RISC-V Entry Points** Okay, so I need to identify suitable entry points within the RISC-V KVM code, specifically avoiding hot-path functions to keep the testing distribution fair. The prompt's emphasis on non-static, non-inlined API entrypoints is key here. My initial thought was `kvm_arch_vcpu_ioctl_run`, because that's the core of VCPU execution – the `KVM_RUN` ioctl handler. And then I remembered, "avoid hot paths!" That's the primary loop for execution, which will skew any distribution. `kvm_arch_init_vm` and `kvm_arch_destroy_vm` are definitely safe, but are they the only options? The prompt did say to prefer non-static, non-inlined API entrypoints functions over internal static helper functions. I looked at `kvm_riscv_local_tlb_sanitize` is called from `kvm_arch_vcpu_ioctl_run`. That should be good! It's not static and not inlined. Time to look in Kconfig and check for RISC-V KVM-related entry points and to make sure I haven't missed anything. |
| 6/2 | 2026/08/17 11:52 | tool | grepper |
0mArgs:{
"Expression": "config KVM",
"PathPrefix": "arch/riscv/kvm/Kconfig"
}Results: {
"Output": "arch/riscv/kvm/Kconfig=18=if VIRTUALIZATION\narch/riscv/kvm/Kconfig-19-\narch/riscv/kvm/Kconfig:20:config KVM\narch/riscv/kvm/Kconfig-21-\ttristate \"Kernel-based Virtual Machine (KVM) support\"\n"
} |
| 7/2 | 2026/08/17 11:52 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 6227 output: 36 thoughts: 17 Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience: **Diving into the KVM on RISC-V Kconfig** Okay, time to get my hands dirty and really understand the configuration options for KVM on RISC-V. I need to get a clear picture of the architectural landscape and how the various features and functionalities are exposed through the Kconfig system. I'm thinking I'll need to look at the interactions between the generic KVM options and the RISC-V specific ones. I'm particularly interested in things like the virtual machine extensions (VME), any hardware virtualization support specific to RISC-V implementations I am familiar with, and the implications of different privilege levels. Let's see how they've handled the various privilege levels too. I want to see how the code handles things like nested virtualization or memory management in general, and whether there are any conditional dependencies I should be aware of. I'll need to correlate the Kconfig options with the underlying source code to develop a solid mental model. Let's start digging! |
| 8/2 | 2026/08/17 11:53 | tool | read-file |
0mArgs:{
"File": "arch/riscv/kvm/Kconfig",
"FirstLine": 1,
"LineCount": 50
}Results: {
"Contents": " 1:\t# SPDX-License-Identifier: GPL-2.0\n 2:\t#\n 3:\t# KVM configuration\n 4:\t#\n 5:\t\n 6:\tsource \"virt/kvm/Kconfig\"\n 7:\t\n 8:\tmenuconfig VIRTUALIZATION\n 9:\t\tbool \"Virtualization\"\n 10:\t\thelp\n 11:\t\t Say Y here to get to see options for using your Linux host to run\n 12:\t\t other operating systems inside virtual machines (guests).\n 13:\t\t This option alone does not add any kernel code.\n 14:\t\n 15:\t\t If you say N, all options in this submenu will be skipped and\n 16:\t\t disabled.\n 17:\t\n 18:\tif VIRTUALIZATION\n 19:\t\n 20:\tconfig KVM\n 21:\t\ttristate \"Kernel-based Virtual Machine (KVM) support\"\n 22:\t\tdepends on RISCV_SBI \u0026\u0026 MMU\n 23:\t\tselect HAVE_KVM_IRQCHIP\n 24:\t\tselect HAVE_KVM_IRQ_ROUTING\n 25:\t\tselect HAVE_KVM_MSI\n 26:\t\tselect HAVE_KVM_READONLY_MEM\n 27:\t\tselect HAVE_KVM_DIRTY_RING_ACQ_REL\n 28:\t\tselect KVM_COMMON\n 29:\t\tselect KVM_GENERIC_DIRTYLOG_READ_PROTECT\n 30:\t\tselect KVM_GENERIC_HARDWARE_ENABLING\n 31:\t\tselect KVM_MMIO\n 32:\t\tselect VIRT_XFER_TO_GUEST_WORK\n 33:\t\tselect SCHED_INFO\n 34:\t\tselect GUEST_PERF_EVENTS if PERF_EVENTS\n 35:\t\thelp\n 36:\t\t Support hosting virtualized guest machines.\n 37:\t\n 38:\t\t If unsure, say N.\n 39:\t\n 40:\tendif # VIRTUALIZATION\n"
} |
| 9/2 | 2026/08/17 11:53 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 7022 output: 139 thoughts: 371 Thoughts: **My Assessment of the RISC-V KVM Configuration and Relevant Symbols** Okay, so I'm looking at how to best configure and analyze this RISC-V KVM code. It seems pretty straightforward. The primary configuration item I need to enable is `KVM` itself. That makes sense; it's the core. `KVM` hinges on `VIRTUALIZATION`, `RISCV_SBI`, and `MMU`, so those are likely prerequisites, though I don't need to explicitly list them since enabling `KVM` should take care of that. Now, let's identify the key functions to focus on. Based on the description, and my knowledge of KVM, I'm thinking I should start with the standard KVM API calls like `kvm_arch_init_vm` and `kvm_arch_destroy_vm`. These are the foundational setup and teardown points for a VM. The description mentions `kvm_riscv_local_tlb_sanitize` being specific to RISC-V KVM, and it's the target of the patch so that should definitely be on the list. So, I'll definitely list: `kvm_arch_init_vm`, `kvm_arch_destroy_vm`, and `kvm_riscv_local_tlb_sanitize`. Let me quickly double-check that `kvm_riscv_local_tlb_sanitize` is actually exported. Ah, yes, it's global (non-static) in `arch/riscv/kvm/tlb.c`, so it's readily accessible. `kvm_arch_init_vm` and `kvm_arch_destroy_vm` are in `arch/riscv/kvm/vm.c`. With this in mind, I will set these configurations to make sure the fuzzing is as thorough as possible: * **WorthFuzzing**: `true` * **FocusSymbols**: `["kvm_arch_init_vm", "kvm_arch_destroy_vm", "kvm_riscv_local_tlb_sanitize"]` * **EnableConfigs**: `["KVM"]` The reasoning behind this is that this patch is introducing a functional change related to TLB sanitization within the RISC-V KVM. The change seems reachable via standard KVM ioctls. The goal here is to carefully examine the interaction with these key KVM functions when a VCPU migrates. |
| 10/2 | 2026/08/17 11:53 | tool | set-results |
0mArgs:{
"EnableConfigs": [
"KVM"
],
"FocusSymbols": [
"kvm_arch_init_vm",
"kvm_arch_destroy_vm",
"kvm_riscv_local_tlb_sanitize"
],
"Reasoning": "The patch modifies the TLB sanitization logic for RISC-V KVM, introducing a per-CPU tracking of the last VCPU ran to flush VS-stage TLB entries upon VCPU migration or switching. This is a functional change in the core RISC-V KVM implementation reachable via standard KVM ioctls.",
"WorthFuzzing": true
}Results: {
"EnableConfigs": [
"KVM"
],
"FocusSymbols": [
"kvm_arch_init_vm",
"kvm_arch_destroy_vm",
"kvm_riscv_local_tlb_sanitize"
],
"Reasoning": "The patch modifies the TLB sanitization logic for RISC-V KVM, introducing a per-CPU tracking of the last VCPU ran to flush VS-stage TLB entries upon VCPU migration or switching. This is a functional change in the core RISC-V KVM implementation reachable via standard KVM ioctls.",
"WorthFuzzing": true
} |
| 11/1 | 2026/08/17 11:53 | action | If |
0mArgs:{
"WorthFuzzing": true
}Results: null |
| 12/2 | 2026/08/17 11:53 | agent | kmsan-evaluator |
0mModel:best-expensive Args: null Results: {
"KMSANReasoning": "The patch modifies RISC-V KVM internal TLB flushing logic and introduces a new per-CPU variable `last_vcpu_ran` to track the last VCPU that ran on each physical CPU. The memory for this variable is allocated using `alloc_percpu(int)` and is explicitly initialized to `-1` for all possible CPUs immediately after allocation. There are no new structures exposed to user space, no complex data structures with potentially uninitialized fields, and no changes to bounds checking that could expose uninitialized memory. Any potential bugs introduced by this patch (such as use-after-free or out-of-bounds accesses) would be effectively caught by standard KASAN and other default bug detectors. Therefore, a dedicated KMSAN fuzzing session is not required.",
"NeedsKMSAN": false
}Instruction: You are an expert Linux kernel security engineer specializing in kernel memory
error detectors (KASAN and KMSAN). Your job is to review the provided patch series and
determine if the code changes justify spawning a dedicated KMSAN (KernelMemorySanitizer)
fuzzing session in addition to standard KASAN fuzzing.
CRITICAL DISTINCTION BETWEEN KASAN AND KMSAN:
- Standard KASAN kernel builds (upstream-apparmor-kasan.config) already enable
a comprehensive suite of debugging tools and sanitizers, including KASAN
(out-of-bounds accesses, use-after-free, double free, invalid free), LOCKDEP
(locking bugs and deadlocks), UB-sanitizers, and memory corruption checks.
- KMSAN (KernelMemorySanitizer) detects reads of UNINITIALIZED memory (stack, heap,
or page allocations) and kernel-to-user memory info-leaks.
Rule: THERE IS NO SENSE IN RUNNING A KMSAN SESSION IF A BUG CAN BE CAUGHT BY KASAN,
LOCKDEP, OR OTHER STANDARD BUG DETECTORS.
A dedicated KMSAN fuzzing session incurs significant resource costs. You must ONLY
set NeedsKMSAN=true if the code changes introduce or expose UNINITIALIZED MEMORY risks
that are detected ONLY by KMSAN.
Look holistically at the patch series and surrounding code. Even if no direct
uninitialized field accesses or new buffer allocations are added in the diff itself,
a patch may alter control flow, bounds checking, or data length calculations in ways
that change how the rest of the code operates on existing buffers (e.g. allowing
uninitialized stack/heap memory to be read, copied to user space, or used in control
flow). Do not hesitate to use your code access tools to inspect the surrounding code,
called functions, and callers.
Set NeedsKMSAN=true ONLY IF the patch introduces or modifies:
1. Kernel structures sent to user space (via copy_to_user, put_user, netlink skb
attributes, ioctl output arguments, socket options, or BPF buffers) where fields
or structure padding might not be fully initialized/zeroed.
2. Conditional logic or branching that depends on potentially uninitialized variables
or struct fields.
3. Allocation or initialization of complex data structures where uninitialized fields
could be read later in reachable code paths.
4. Bounds checks, lengths, or logic in a way that allows surrounding code to access
uninitialized bytes of existing buffers.
Set NeedsKMSAN=false IF:
- The code changes primarily risk out-of-bounds access, array overflows, NULL pointer
dereferences, locking deadlocks, or use-after-free bugs (these are already caught
by KASAN, LOCKDEP, or standard bug detectors).
- All stack/heap structures touched or introduced by the patch are fully zeroed
or initialized (e.g. using = {0}, memset, kzalloc) before being read or copied.
- The patch does not introduce any risk of uninitialized memory usage or info-leaks.
Use your code access tools to inspect the surrounding code if necessary, then provide
detailed KMSANReasoning contrasting KASAN vs KMSAN applicability for this patch.
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: For your convenience, here is the diff of the changes:
commit 06eba2a8029676d209c2a79f73116910065f9abd
Author: syz-cluster <triage@syzkaller.com>
Date: Mon Aug 17 11:52:38 2026 +0000
syz-cluster: applied patch under review
diff --git a/arch/riscv/include/asm/kvm_host.h b/arch/riscv/include/asm/kvm_host.h
index 24585304c02b1..396ac6209c50d 100644
--- a/arch/riscv/include/asm/kvm_host.h
+++ b/arch/riscv/include/asm/kvm_host.h
@@ -91,6 +91,9 @@ struct kvm_arch {
/* G-stage vmid */
struct kvm_vmid vmid;
+ /* Last VCPU that ran on each physical CPU */
+ int __percpu *last_vcpu_ran;
+
/* G-stage page table */
pgd_t *pgd;
phys_addr_t pgd_phys;
@@ -330,7 +333,4 @@ bool kvm_riscv_vcpu_stopped(struct kvm_vcpu *vcpu);
void kvm_riscv_vcpu_record_steal_time(struct kvm_vcpu *vcpu);
-/* Flags representing implementation specific details */
-DECLARE_STATIC_KEY_FALSE(kvm_riscv_vsstage_tlb_no_gpa);
-
#endif /* __RISCV_KVM_HOST_H__ */
diff --git a/arch/riscv/kvm/main.c b/arch/riscv/kvm/main.c
index 0f3fe3986fc02..b1bbc083527cf 100644
--- a/arch/riscv/kvm/main.c
+++ b/arch/riscv/kvm/main.c
@@ -10,23 +10,10 @@
#include <linux/err.h>
#include <linux/module.h>
#include <linux/kvm_host.h>
-#include <asm/cpufeature.h>
#include <asm/kvm_mmu.h>
#include <asm/kvm_nacl.h>
#include <asm/sbi.h>
-DEFINE_STATIC_KEY_FALSE(kvm_riscv_vsstage_tlb_no_gpa);
-
-static void kvm_riscv_setup_vendor_features(void)
-{
- /* Andes AX66: split two-stage TLBs */
- if (riscv_cached_mvendorid(0) == ANDES_VENDOR_ID &&
- (riscv_cached_marchid(0) & 0xFFFF) == 0x8A66) {
- static_branch_enable(&kvm_riscv_vsstage_tlb_no_gpa);
- kvm_info("VS-stage TLB does not cache guest physical address and VMID\n");
- }
-}
-
long kvm_arch_dev_ioctl(struct file *filp,
unsigned int ioctl, unsigned long arg)
{
@@ -172,8 +159,6 @@ static int __init riscv_kvm_init(void)
kvm_info("AIA available with %d guest external interrupts\n",
kvm_riscv_aia_nr_hgei);
- kvm_riscv_setup_vendor_features();
-
kvm_register_perf_callbacks();
rc = kvm_init(sizeof(struct kvm_vcpu), 0, THIS_MODULE);
diff --git a/arch/riscv/kvm/tlb.c b/arch/riscv/kvm/tlb.c
index ff1aeac4eb8eb..1541d059a42ad 100644
--- a/arch/riscv/kvm/tlb.c
+++ b/arch/riscv/kvm/tlb.c
@@ -8,6 +8,7 @@
#include <linux/errno.h>
#include <linux/err.h>
#include <linux/module.h>
+#include <linux/percpu.h>
#include <linux/smp.h>
#include <linux/kvm_host.h>
#include <asm/cacheflush.h>
@@ -160,12 +161,19 @@ void kvm_riscv_local_hfence_vvma_all(unsigned long vmid)
void kvm_riscv_local_tlb_sanitize(struct kvm_vcpu *vcpu)
{
+ bool vcpu_migrated;
+ bool vcpu_switched;
unsigned long vmid;
+ int *last_ran;
- if (!kvm_riscv_gstage_vmid_bits() ||
- vcpu->arch.last_exit_cpu == vcpu->cpu)
+ last_ran = this_cpu_ptr(vcpu->kvm->arch.last_vcpu_ran);
+ vcpu_migrated = (vcpu->arch.last_exit_cpu != vcpu->cpu);
+ vcpu_switched = (*last_ran != vcpu->vcpu_idx);
+ if (!vcpu_migrated && !vcpu_switched)
return;
+ vmid = READ_ONCE(vcpu->kvm->arch.vmid.vmid);
+
/*
* On RISC-V platforms with hardware VMID support, we share same
* VMID for all VCPUs of a particular Guest/VM. This means we might
@@ -176,16 +184,23 @@ void kvm_riscv_local_tlb_sanitize(struct kvm_vcpu *vcpu)
* To cleanup stale TLB entries, we simply flush all G-stage TLB
* entries by VMID whenever underlying Host CPU changes for a VCPU.
*/
-
- vmid = READ_ONCE(vcpu->kvm->arch.vmid.vmid);
- kvm_riscv_local_hfence_gvma_vmid_all(vmid);
+ if (vcpu_migrated && kvm_riscv_gstage_vmid_bits())
+ kvm_riscv_local_hfence_gvma_vmid_all(vmid);
/*
- * Flush VS-stage TLB entries for implementation where VS-stage
- * TLB does not cahce guest physical address and VMID.
+ * Guest-local sfence.vma only invalidates VS-stage translations on
+ * the Host CPU currently backing the VCPU. If a VCPU migrates, or
+ * if this Host CPU switches between VCPUs of the same VM, stale
+ * VS-stage entries can be left behind on a Host CPU that missed a
+ * guest-local flush. Flush the current CPU's VS-stage context before
+ * entering the guest.
*/
- if (static_branch_unlikely(&kvm_riscv_vsstage_tlb_no_gpa))
+ if (kvm_riscv_nacl_available())
+ nacl_hfence_vvma_all(nacl_shmem(), vmid);
+ else
kvm_riscv_local_hfence_vvma_all(vmid);
+
+ *last_ran = vcpu->vcpu_idx;
}
void kvm_riscv_fence_i_process(struct kvm_vcpu *vcpu)
diff --git a/arch/riscv/kvm/vm.c b/arch/riscv/kvm/vm.c
index 13c63ae1a78b2..2907218a1cf5d 100644
--- a/arch/riscv/kvm/vm.c
+++ b/arch/riscv/kvm/vm.c
@@ -9,6 +9,7 @@
#include <linux/errno.h>
#include <linux/err.h>
#include <linux/module.h>
+#include <linux/percpu.h>
#include <linux/uaccess.h>
#include <linux/kvm_host.h>
#include <asm/kvm_mmu.h>
@@ -30,7 +31,9 @@ const struct kvm_stats_header kvm_vm_stats_header = {
int kvm_arch_init_vm(struct kvm *kvm, unsigned long type)
{
+ int *last_ran;
int r;
+ int cpu;
r = kvm_riscv_mmu_alloc_pgd(kvm);
if (r)
@@ -42,6 +45,17 @@ int kvm_arch_init_vm(struct kvm *kvm, unsigned long type)
return r;
}
+ kvm->arch.last_vcpu_ran = alloc_percpu(int);
+ if (!kvm->arch.last_vcpu_ran) {
+ kvm_riscv_mmu_free_pgd(kvm);
+ return -ENOMEM;
+ }
+
+ for_each_possible_cpu(cpu) {
+ last_ran = per_cpu_ptr(kvm->arch.last_vcpu_ran, cpu);
+ *last_ran = -1;
+ }
+
kvm_riscv_aia_init_vm(kvm);
kvm_riscv_guest_timer_init(kvm);
@@ -54,6 +68,8 @@ void kvm_arch_destroy_vm(struct kvm *kvm)
kvm_destroy_vcpus(kvm);
kvm_riscv_aia_destroy_vm(kvm);
+
+ free_percpu(kvm->arch.last_vcpu_ran);
}
int kvm_vm_ioctl_irq_line(struct kvm *kvm, struct kvm_irq_level *irql,
|
| 13/3 | 2026/08/17 11:53 | llm | kmsan-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 4258 output: 185 thoughts: 531 Thoughts: **KMSAN Fuzzing Session Assessment**
Okay, let's break this down. We have a patch series touching RISC-V KVM code. Specifically, it's removing the `kvm_riscv_vsstage_tlb_no_gpa` static key and associated vendor-specific setup and adding a `last_vcpu_ran` percpu variable to `struct kvm_arch`. This `last_vcpu_ran` is allocated using `alloc_percpu(int)` during `kvm_arch_init_vm` and initialized to `-1` for each possible CPU. It gets freed in `kvm_arch_destroy_vm`. The primary use is within `kvm_riscv_local_tlb_sanitize` to check for vCPU switches on the current CPU, influencing TLB flushing logic based on migration and switching events.
My immediate concern, as always, is whether this introduces any uninitialized memory risks that might warrant a dedicated KMSAN fuzzing session. Let's analyze. The `alloc_percpu` call *does* initialize memory to zero by default, which is good. But this patch explicitly initializes it to `-1` anyway, which provides extra confidence. There are no new structures being copied to userspace, meaning no chance of exposing uninitialized internal data to the outside world. The changes are strictly within the KVM internal workings, dealing with TLB flushing, and the variable is used for internal control flow. There are no complex data structures or potential for uninitialized fields to be read during processing. This implies there's no way it introduces an uninitialized memory read leading to an information leak.
Let's double-check that `alloc_percpu` is fully initialized. The code snippet confirms this; the initialization loop is explicit.
```c
kvm->arch.last_vcpu_ran = alloc_percpu(int);
if (!kvm->arch.last_vcpu_ran) {
kvm_riscv_mmu_free_pgd(kvm);
return -ENOMEM;
}
for_each_possible_cpu(cpu) {
last_ran = per_cpu_ptr(kvm->arch.last_vcpu_ran, cpu);
*last_ran = -1;
}
```
Since the memory is fully initialized, there are no uninitialized memory risks, and nothing is exposed to userspace, I am confident that KASAN and standard bug detectors should be sufficient to catch any out-of-bounds or use-after-free issues here. I see no need for KMSAN. I'll set `NeedsKMSAN=false` and document this reasoning.
|
| 14/3 | 2026/08/17 11:53 | tool | set-results |
0mArgs:{
"KMSANReasoning": "The patch modifies RISC-V KVM internal TLB flushing logic and introduces a new per-CPU variable `last_vcpu_ran` to track the last VCPU that ran on each physical CPU. The memory for this variable is allocated using `alloc_percpu(int)` and is explicitly initialized to `-1` for all possible CPUs immediately after allocation. There are no new structures exposed to user space, no complex data structures with potentially uninitialized fields, and no changes to bounds checking that could expose uninitialized memory. Any potential bugs introduced by this patch (such as use-after-free or out-of-bounds accesses) would be effectively caught by standard KASAN and other default bug detectors. Therefore, a dedicated KMSAN fuzzing session is not required.",
"NeedsKMSAN": false
}Results: {
"KMSANReasoning": "The patch modifies RISC-V KVM internal TLB flushing logic and introduces a new per-CPU variable `last_vcpu_ran` to track the last VCPU that ran on each physical CPU. The memory for this variable is allocated using `alloc_percpu(int)` and is explicitly initialized to `-1` for all possible CPUs immediately after allocation. There are no new structures exposed to user space, no complex data structures with potentially uninitialized fields, and no changes to bounds checking that could expose uninitialized memory. Any potential bugs introduced by this patch (such as use-after-free or out-of-bounds accesses) would be effectively caught by standard KASAN and other default bug detectors. Therefore, a dedicated KMSAN fuzzing session is not required.",
"NeedsKMSAN": false
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|