| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/08/22 10:20 | flow | patch-triage |
1mArgs:null Results: {
"EnableConfigs": [
"KVM"
],
"FocusSymbols": [
"vgic_v3_set_redist_base",
"vgic_register_redist_iodev",
"vgic_unregister_redist_iodev",
"__vgic_unassign_redist_iodev",
"kvm_vgic_vcpu_destroy",
"kvm_vgic_destroy"
],
"KMSANReasoning": "The patch modifies KVM VGIC v3 redistributor initialization, teardown, and rollback logic. It introduces proper cleanup functions (`vgic_undo_redist_assignment`, `__vgic_unassign_redist_iodev`, `vgic_v3_rollback_redist_region`) and adjusts locking to prevent dangling pointers and lock inversions during vCPU creation failures or redistributor base setup failures. The changes are strictly related to resource management, concurrency, and object lifecycle. There are no new structures exposed to user space, no new complex allocations that could leak uninitialized memory, and no changes to bounds checking that would allow reading uninitialized bytes. Any bugs related to these changes (such as use-after-free, dangling pointers, or deadlocks) are well within the scope of KASAN and LOCKDEP. A dedicated KMSAN session is not justified.",
"NeedsKMSAN": false,
"Reasoning": "The patch modifies the KVM ARM64 VGICv3 redistributor region handling, specifically fixing race conditions and rollback logic during vCPU creation and redistributor base address assignment. This is reachable from userspace via KVM ioctls (e.g., KVM_SET_DEVICE_ATTR) and affects core KVM functionality on ARM64, making it highly relevant for fuzzing.",
"WorthFuzzing": true
} |
| 1/1 | 2026/08/22 10:20 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit 7e6b471e25afad3d0fa9d7cd12a51c9187de0dfe\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Sat Aug 22 10:20:28 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/arch/arm64/kvm/vgic/vgic-init.c b/arch/arm64/kvm/vgic/vgic-init.c\nindex 907057881b26a..81ab5a6bf8bc8 100644\n--- a/arch/arm64/kvm/vgic/vgic-init.c\n+++ b/arch/arm64/kvm/vgic/vgic-init.c\n@@ -519,29 +519,6 @@ static void __kvm_vgic_vcpu_destroy(struct kvm_vcpu *vcpu)\n \tINIT_LIST_HEAD(\u0026vgic_cpu-\u003eap_list_head);\n \tkfree(vgic_cpu-\u003eprivate_irqs);\n \tvgic_cpu-\u003eprivate_irqs = NULL;\n-\n-\tif (vcpu-\u003ekvm-\u003earch.vgic.vgic_model == KVM_DEV_TYPE_ARM_VGIC_V3) {\n-\t\t/*\n-\t\t * If this vCPU is being destroyed because of a failed creation\n-\t\t * then unregister the redistributor to avoid leaving behind a\n-\t\t * dangling pointer to the vCPU struct.\n-\t\t *\n-\t\t * vCPUs that have been successfully created (i.e. added to\n-\t\t * kvm-\u003evcpu_array) get unregistered in kvm_vgic_destroy(), as\n-\t\t * this function gets called while holding kvm-\u003earch.config_lock\n-\t\t * in the VM teardown path and would otherwise introduce a lock\n-\t\t * inversion w.r.t. kvm-\u003esrcu.\n-\t\t *\n-\t\t * vCPUs that failed creation are torn down outside of the\n-\t\t * kvm-\u003earch.config_lock and do not get unregistered in\n-\t\t * kvm_vgic_destroy(), meaning it is both safe and necessary to\n-\t\t * do so here.\n-\t\t */\n-\t\tif (kvm_get_vcpu_by_id(vcpu-\u003ekvm, vcpu-\u003evcpu_id) != vcpu)\n-\t\t\tvgic_unregister_redist_iodev(vcpu);\n-\n-\t\tvgic_cpu-\u003erd_iodev.base_addr = VGIC_ADDR_UNDEF;\n-\t}\n }\n \n void kvm_vgic_vcpu_destroy(struct kvm_vcpu *vcpu)\n@@ -549,7 +526,16 @@ void kvm_vgic_vcpu_destroy(struct kvm_vcpu *vcpu)\n \tstruct kvm *kvm = vcpu-\u003ekvm;\n \n \tmutex_lock(\u0026kvm-\u003eslots_lock);\n+\tif (kvm-\u003earch.vgic.vgic_model == KVM_DEV_TYPE_ARM_VGIC_V3)\n+\t\tvgic_unregister_redist_iodev(vcpu);\n+\n \t__kvm_vgic_vcpu_destroy(vcpu);\n+\n+\tif (kvm-\u003earch.vgic.vgic_model == KVM_DEV_TYPE_ARM_VGIC_V3) {\n+\t\tmutex_lock(\u0026kvm-\u003earch.config_lock);\n+\t\t__vgic_unassign_redist_iodev(vcpu);\n+\t\tmutex_unlock(\u0026kvm-\u003earch.config_lock);\n+\t}\n \tmutex_unlock(\u0026kvm-\u003eslots_lock);\n }\n \n@@ -559,21 +545,23 @@ void kvm_vgic_destroy(struct kvm *kvm)\n \tunsigned long i;\n \n \tmutex_lock(\u0026kvm-\u003eslots_lock);\n+\tif (kvm-\u003earch.vgic.vgic_model == KVM_DEV_TYPE_ARM_VGIC_V3)\n+\t\tkvm_for_each_vcpu(i, vcpu, kvm)\n+\t\t\tvgic_unregister_redist_iodev(vcpu);\n+\n \tmutex_lock(\u0026kvm-\u003earch.config_lock);\n \n \tvgic_debug_destroy(kvm);\n \n-\tkvm_for_each_vcpu(i, vcpu, kvm)\n+\tkvm_for_each_vcpu(i, vcpu, kvm) {\n \t\t__kvm_vgic_vcpu_destroy(vcpu);\n+\t\tif (kvm-\u003earch.vgic.vgic_model == KVM_DEV_TYPE_ARM_VGIC_V3)\n+\t\t\t__vgic_unassign_redist_iodev(vcpu);\n+\t}\n \n \tkvm_vgic_dist_destroy(kvm);\n \n \tmutex_unlock(\u0026kvm-\u003earch.config_lock);\n-\n-\tif (kvm-\u003earch.vgic.vgic_model == KVM_DEV_TYPE_ARM_VGIC_V3)\n-\t\tkvm_for_each_vcpu(i, vcpu, kvm)\n-\t\t\tvgic_unregister_redist_iodev(vcpu);\n-\n \tmutex_unlock(\u0026kvm-\u003eslots_lock);\n }\n \ndiff --git a/arch/arm64/kvm/vgic/vgic-kvm-device.c b/arch/arm64/kvm/vgic/vgic-kvm-device.c\nindex 90be99443df3b..48c3b2a48c202 100644\n--- a/arch/arm64/kvm/vgic/vgic-kvm-device.c\n+++ b/arch/arm64/kvm/vgic/vgic-kvm-device.c\n@@ -97,6 +97,9 @@ static int kvm_vgic_addr(struct kvm *kvm, struct kvm_device_attr *attr, bool wri\n \tphys_addr_t *addr_ptr, alignment, size;\n \tu64 undef_value = VGIC_ADDR_UNDEF;\n \tu64 addr;\n+\tbool redist_write = write \u0026\u0026\n+\t\t(attr-\u003eattr == KVM_VGIC_V3_ADDR_TYPE_REDIST ||\n+\t\t attr-\u003eattr == KVM_VGIC_V3_ADDR_TYPE_REDIST_REGION);\n \tint r;\n \n \t/* Reading a redistributor region addr implies getting the index */\n@@ -104,6 +107,19 @@ static int kvm_vgic_addr(struct kvm *kvm, struct kvm_device_attr *attr, bool wri\n \t\tif (get_user(addr, uaddr))\n \t\t\treturn -EFAULT;\n \n+\t/*\n+\t * A vCPU can have an RD assignment before it is visible to\n+\t * kvm_for_each_vcpu(). Reject redistributor updates while vCPU creation\n+\t * is in progress, so rollback can reset every assignment.\n+\t */\n+\tif (redist_write) {\n+\t\tmutex_lock(\u0026kvm-\u003elock);\n+\t\tif (kvm-\u003ecreated_vcpus != atomic_read(\u0026kvm-\u003eonline_vcpus)) {\n+\t\t\tr = -EBUSY;\n+\t\t\tgoto out_unlock_kvm;\n+\t\t}\n+\t}\n+\n \t/*\n \t * Since we can't hold config_lock while registering the redistributor\n \t * iodevs, take the slots_lock immediately.\n@@ -201,6 +217,10 @@ static int kvm_vgic_addr(struct kvm *kvm, struct kvm_device_attr *attr, bool wri\n out:\n \tmutex_unlock(\u0026kvm-\u003eslots_lock);\n \n+out_unlock_kvm:\n+\tif (redist_write)\n+\t\tmutex_unlock(\u0026kvm-\u003elock);\n+\n \tif (!r \u0026\u0026 !write)\n \t\tr = put_user(addr, uaddr);\n \ndiff --git a/arch/arm64/kvm/vgic/vgic-mmio-v3.c b/arch/arm64/kvm/vgic/vgic-mmio-v3.c\nindex 5913a20d83019..dc860178105d9 100644\n--- a/arch/arm64/kvm/vgic/vgic-mmio-v3.c\n+++ b/arch/arm64/kvm/vgic/vgic-mmio-v3.c\n@@ -766,6 +766,18 @@ unsigned int vgic_v3_init_dist_iodev(struct vgic_io_device *dev)\n \treturn SZ_64K;\n }\n \n+static void vgic_undo_redist_assignment(struct kvm_vcpu *vcpu)\n+{\n+\tstruct vgic_cpu *vgic_cpu = \u0026vcpu-\u003earch.vgic_cpu;\n+\n+\tlockdep_assert_held(\u0026vcpu-\u003ekvm-\u003eslots_lock);\n+\n+\tguard(mutex)(\u0026vcpu-\u003ekvm-\u003earch.config_lock);\n+\n+\tvgic_cpu-\u003erdreg-\u003efree_index--;\n+\t__vgic_unassign_redist_iodev(vcpu);\n+}\n+\n /**\n * vgic_register_redist_iodev - register a single redist iodev\n * @vcpu: The VCPU to which the redistributor belongs\n@@ -818,16 +830,17 @@ int vgic_register_redist_iodev(struct kvm_vcpu *vcpu)\n \trd_dev-\u003enr_regions = ARRAY_SIZE(vgic_v3_rd_registers);\n \trd_dev-\u003eredist_vcpu = vcpu;\n \n+\t/* Protected by slots_lock */\n+\trdreg-\u003efree_index++;\n+\n \tmutex_unlock(\u0026kvm-\u003earch.config_lock);\n \n \tret = kvm_io_bus_register_dev(kvm, KVM_MMIO_BUS, rd_base,\n \t\t\t\t 2 * SZ_64K, \u0026rd_dev-\u003edev);\n \tif (ret)\n-\t\treturn ret;\n+\t\tvgic_undo_redist_assignment(vcpu);\n \n-\t/* Protected by slots_lock */\n-\trdreg-\u003efree_index++;\n-\treturn 0;\n+\treturn ret;\n \n out_unlock:\n \tmutex_unlock(\u0026kvm-\u003earch.config_lock);\n@@ -841,6 +854,40 @@ void vgic_unregister_redist_iodev(struct kvm_vcpu *vcpu)\n \tkvm_io_bus_unregister_dev(vcpu-\u003ekvm, KVM_MMIO_BUS, \u0026rd_dev-\u003edev);\n }\n \n+void __vgic_unassign_redist_iodev(struct kvm_vcpu *vcpu)\n+{\n+\tstruct vgic_cpu *vgic_cpu = \u0026vcpu-\u003earch.vgic_cpu;\n+\n+\tlockdep_assert_held(\u0026vcpu-\u003ekvm-\u003earch.config_lock);\n+\n+\tvgic_cpu-\u003erdreg = NULL;\n+\tvgic_cpu-\u003erd_iodev.base_addr = VGIC_ADDR_UNDEF;\n+}\n+\n+static void vgic_v3_rollback_redist_region(struct kvm *kvm, u32 index)\n+{\n+\tstruct vgic_redist_region *rdreg, *iter;\n+\tstruct kvm_vcpu *vcpu;\n+\tunsigned long c;\n+\n+\tlockdep_assert_held(\u0026kvm-\u003eslots_lock);\n+\n+\trdreg = vgic_v3_rdist_region_from_index(kvm, index);\n+\n+\tkvm_for_each_vcpu(c, vcpu, kvm)\n+\t\tvgic_unregister_redist_iodev(vcpu);\n+\n+\tguard(mutex)(\u0026kvm-\u003earch.config_lock);\n+\n+\tkvm_for_each_vcpu(c, vcpu, kvm)\n+\t\t__vgic_unassign_redist_iodev(vcpu);\n+\n+\tlist_for_each_entry(iter, \u0026kvm-\u003earch.vgic.rd_regions, list)\n+\t\titer-\u003efree_index = 0;\n+\n+\tvgic_v3_free_redist_region(kvm, rdreg);\n+}\n+\n static int vgic_register_all_redist_iodevs(struct kvm *kvm)\n {\n \tstruct kvm_vcpu *vcpu;\n@@ -855,16 +902,6 @@ static int vgic_register_all_redist_iodevs(struct kvm *kvm)\n \t\t\tbreak;\n \t}\n \n-\tif (ret) {\n-\t\t/* The current c failed, so iterate over the previous ones. */\n-\t\tint i;\n-\n-\t\tfor (i = 0; i \u003c c; i++) {\n-\t\t\tvcpu = kvm_get_vcpu(kvm, i);\n-\t\t\tvgic_unregister_redist_iodev(vcpu);\n-\t\t}\n-\t}\n-\n \treturn ret;\n }\n \n@@ -953,17 +990,8 @@ static int vgic_v3_alloc_redist_region(struct kvm *kvm, uint32_t index,\n \n void vgic_v3_free_redist_region(struct kvm *kvm, struct vgic_redist_region *rdreg)\n {\n-\tstruct kvm_vcpu *vcpu;\n-\tunsigned long c;\n-\n \tlockdep_assert_held(\u0026kvm-\u003earch.config_lock);\n \n-\t/* Garbage collect the region */\n-\tkvm_for_each_vcpu(c, vcpu, kvm) {\n-\t\tif (vcpu-\u003earch.vgic_cpu.rdreg == rdreg)\n-\t\t\tvcpu-\u003earch.vgic_cpu.rdreg = NULL;\n-\t}\n-\n \tlist_del(\u0026rdreg-\u003elist);\n \tkfree(rdreg);\n }\n@@ -972,6 +1000,8 @@ int vgic_v3_set_redist_base(struct kvm *kvm, u32 index, u64 addr, u32 count)\n {\n \tint ret;\n \n+\tlockdep_assert_held(\u0026kvm-\u003elock);\n+\n \tmutex_lock(\u0026kvm-\u003earch.config_lock);\n \tret = vgic_v3_alloc_redist_region(kvm, index, addr, count);\n \tmutex_unlock(\u0026kvm-\u003earch.config_lock);\n@@ -984,12 +1014,7 @@ int vgic_v3_set_redist_base(struct kvm *kvm, u32 index, u64 addr, u32 count)\n \t */\n \tret = vgic_register_all_redist_iodevs(kvm);\n \tif (ret) {\n-\t\tstruct vgic_redist_region *rdreg;\n-\n-\t\tmutex_lock(\u0026kvm-\u003earch.config_lock);\n-\t\trdreg = vgic_v3_rdist_region_from_index(kvm, index);\n-\t\tvgic_v3_free_redist_region(kvm, rdreg);\n-\t\tmutex_unlock(\u0026kvm-\u003earch.config_lock);\n+\t\tvgic_v3_rollback_redist_region(kvm, index);\n \t\treturn ret;\n \t}\n \ndiff --git a/arch/arm64/kvm/vgic/vgic.h b/arch/arm64/kvm/vgic/vgic.h\nindex f45f7e3ec4d6e..e32e2b56a47ef 100644\n--- a/arch/arm64/kvm/vgic/vgic.h\n+++ b/arch/arm64/kvm/vgic/vgic.h\n@@ -333,6 +333,7 @@ int vgic_v3_save_pending_tables(struct kvm *kvm);\n int vgic_v3_set_redist_base(struct kvm *kvm, u32 index, u64 addr, u32 count);\n int vgic_register_redist_iodev(struct kvm_vcpu *vcpu);\n void vgic_unregister_redist_iodev(struct kvm_vcpu *vcpu);\n+void __vgic_unassign_redist_iodev(struct kvm_vcpu *vcpu);\n bool vgic_v3_check_base(struct kvm *kvm);\n \n void vgic_v3_load(struct kvm_vcpu *vcpu);\ndiff --git a/tools/testing/selftests/kvm/arm64/vgic_init.c b/tools/testing/selftests/kvm/arm64/vgic_init.c\nindex 47e34b43afb29..5a30f3cb039be 100644\n--- a/tools/testing/selftests/kvm/arm64/vgic_init.c\n+++ b/tools/testing/selftests/kvm/arm64/vgic_init.c\n@@ -5,6 +5,7 @@\n * Copyright (C) 2020, Red Hat, Inc.\n */\n #include \u003clinux/kernel.h\u003e\n+#include \u003clinux/sizes.h\u003e\n #include \u003csys/syscall.h\u003e\n #include \u003casm/kvm.h\u003e\n #include \u003casm/kvm_para.h\u003e\n@@ -13,12 +14,21 @@\n \n #include \"test_util.h\"\n #include \"kvm_util.h\"\n+#include \"gic.h\"\n #include \"processor.h\"\n #include \"vgic.h\"\n #include \"gic_v3.h\"\n \n #define NR_VCPUS\t\t4\n \n+#define REDIST_RETRY_REGION0_BASE\tGICR_BASE_GPA\n+#define REDIST_RETRY_REGION1_BASE\t\\\n+\t(REDIST_RETRY_REGION0_BASE + 2 * KVM_VGIC_V3_REDIST_SIZE)\n+#define REDIST_RETRY_DIST_BASE\t\t\\\n+\t(REDIST_RETRY_REGION1_BASE + KVM_VGIC_V3_REDIST_SIZE)\n+#define REDIST_RETRY_REGION2_BASE\t\\\n+\t(REDIST_RETRY_DIST_BASE + KVM_VGIC_V3_DIST_SIZE)\n+\n #define REG_OFFSET(vcpu, offset) (((u64)vcpu \u003c\u003c 32) | offset)\n \n #define VGIC_DEV_IS_V2(_d) ((_d) == KVM_DEV_TYPE_ARM_VGIC_V2)\n@@ -65,6 +75,23 @@ static void guest_code(void)\n \tGUEST_DONE();\n }\n \n+static void guest_check_redist_retry(void)\n+{\n+\tunsigned int i;\n+\n+\t/* The first three redistributors span adjacent regions 0 and 1. */\n+\tfor (i = 0; i \u003c NR_VCPUS; i++) {\n+\t\tu64 base = i \u003c 3 ? REDIST_RETRY_REGION0_BASE +\n+\t\t\t\t i * KVM_VGIC_V3_REDIST_SIZE :\n+\t\t\t\t REDIST_RETRY_REGION2_BASE;\n+\t\tu64 typer = readq((void *)(unsigned long)(base + GICR_TYPER));\n+\n+\t\tGUEST_ASSERT_EQ(GICR_TYPER_CPU_NUMBER(typer), i);\n+\t}\n+\n+\tGUEST_DONE();\n+}\n+\n /* we don't want to assert on run execution, hence that helper */\n static int run_vcpu(struct kvm_vcpu *vcpu)\n {\n@@ -73,6 +100,7 @@ static int run_vcpu(struct kvm_vcpu *vcpu)\n \n static struct vm_gic vm_gic_create_with_vcpus(u32 gic_dev_type,\n \t\t\t\t\t u32 nr_vcpus,\n+\t\t\t\t\t void *guest_code,\n \t\t\t\t\t struct kvm_vcpu *vcpus[])\n {\n \tstruct vm_gic v;\n@@ -338,7 +366,7 @@ static void test_vgic_then_vcpus(u32 gic_dev_type)\n \tstruct vm_gic v;\n \tint ret, i;\n \n-\tv = vm_gic_create_with_vcpus(gic_dev_type, 1, vcpus);\n+\tv = vm_gic_create_with_vcpus(gic_dev_type, 1, guest_code, vcpus);\n \n \tsubtest_dist_rdist(\u0026v);\n \n@@ -359,7 +387,8 @@ static void test_vcpus_then_vgic(u32 gic_dev_type)\n \tstruct vm_gic v;\n \tint ret;\n \n-\tv = vm_gic_create_with_vcpus(gic_dev_type, NR_VCPUS, vcpus);\n+\tv = vm_gic_create_with_vcpus(gic_dev_type, NR_VCPUS, guest_code,\n+\t\t\t\t vcpus);\n \n \tsubtest_dist_rdist(\u0026v);\n \n@@ -411,7 +440,8 @@ static void test_v3_new_redist_regions(void)\n \tu64 addr;\n \tint ret;\n \n-\tv = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, NR_VCPUS, vcpus);\n+\tv = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, NR_VCPUS,\n+\t\t\t\t guest_code, vcpus);\n \tsubtest_v3_redist_regions(\u0026v);\n \tkvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_CTRL,\n \t\t\t KVM_DEV_ARM_VGIC_CTRL_INIT, NULL);\n@@ -422,7 +452,8 @@ static void test_v3_new_redist_regions(void)\n \n \t/* step2 */\n \n-\tv = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, NR_VCPUS, vcpus);\n+\tv = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, NR_VCPUS,\n+\t\t\t\t guest_code, vcpus);\n \tsubtest_v3_redist_regions(\u0026v);\n \n \taddr = REDIST_REGION_ATTR_ADDR(1, 0x280000, 0, 2);\n@@ -436,7 +467,8 @@ static void test_v3_new_redist_regions(void)\n \n \t/* step 3 */\n \n-\tv = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, NR_VCPUS, vcpus);\n+\tv = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, NR_VCPUS,\n+\t\t\t\t guest_code, vcpus);\n \tsubtest_v3_redist_regions(\u0026v);\n \n \tret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,\n@@ -457,6 +489,70 @@ static void test_v3_new_redist_regions(void)\n \tvm_gic_destroy(\u0026v);\n }\n \n+static void test_v3_redist_region_retry(void)\n+{\n+\tstruct kvm_vcpu *vcpus[NR_VCPUS];\n+\tstruct vm_gic v;\n+\tstruct ucall uc;\n+\tu64 addr;\n+\tint ret;\n+\n+\tv = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, NR_VCPUS,\n+\t\t\t\t guest_check_redist_retry, vcpus);\n+\n+\taddr = REDIST_REGION_ATTR_ADDR(2, REDIST_RETRY_REGION0_BASE, 0, 0);\n+\tkvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,\n+\t\t\t KVM_VGIC_V3_ADDR_TYPE_REDIST_REGION, \u0026addr);\n+\n+\taddr = REDIST_REGION_ATTR_ADDR(1, REDIST_RETRY_REGION1_BASE, 0, 1);\n+\tkvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,\n+\t\t\t KVM_VGIC_V3_ADDR_TYPE_REDIST_REGION, \u0026addr);\n+\n+\taddr = REDIST_RETRY_DIST_BASE;\n+\tkvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,\n+\t\t\t KVM_VGIC_V3_ADDR_TYPE_DIST, \u0026addr);\n+\n+\taddr = REDIST_REGION_ATTR_ADDR(1, REDIST_RETRY_DIST_BASE, 0, 2);\n+\tret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,\n+\t\t\t\t KVM_VGIC_V3_ADDR_TYPE_REDIST_REGION,\n+\t\t\t\t \u0026addr);\n+\tTEST_ASSERT(ret \u0026\u0026 errno == EINVAL,\n+\t\t \"register redist region colliding with dist\");\n+\n+\taddr = REDIST_REGION_ATTR_ADDR(1, REDIST_RETRY_REGION2_BASE, 0, 2);\n+\tkvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,\n+\t\t\t KVM_VGIC_V3_ADDR_TYPE_REDIST_REGION, \u0026addr);\n+\n+\tvirt_map(v.vm, REDIST_RETRY_REGION0_BASE, REDIST_RETRY_REGION0_BASE,\n+\t\t vm_calc_num_guest_pages(v.vm-\u003emode,\n+\t\t\t\t\t 3 * KVM_VGIC_V3_REDIST_SIZE));\n+\tvirt_map(v.vm, REDIST_RETRY_REGION2_BASE, REDIST_RETRY_REGION2_BASE,\n+\t\t vm_calc_num_guest_pages(v.vm-\u003emode,\n+\t\t\t\t\t KVM_VGIC_V3_REDIST_SIZE));\n+\n+\tkvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_CTRL,\n+\t\t\t KVM_DEV_ARM_VGIC_CTRL_INIT, NULL);\n+\n+\tvcpu_run(vcpus[0]);\n+\tswitch (get_ucall(vcpus[0], \u0026uc)) {\n+\tcase UCALL_DONE:\n+\t\tbreak;\n+\tcase UCALL_ABORT:\n+\t\tREPORT_GUEST_ASSERT(uc);\n+\t\tbreak;\n+\tcase UCALL_NONE:\n+\t\tif (vcpus[0]-\u003erun-\u003eexit_reason == KVM_EXIT_MMIO)\n+\t\t\tTEST_FAIL(\"Unexpected MMIO exit at 0x%llx\",\n+\t\t\t\t vcpus[0]-\u003erun-\u003emmio.phys_addr);\n+\t\tfallthrough;\n+\tdefault:\n+\t\tTEST_FAIL(\"Unexpected ucall %lu, exit_reason %u\",\n+\t\t\t uc.cmd, vcpus[0]-\u003erun-\u003eexit_reason);\n+\t}\n+\n+\tvm_gic_destroy(\u0026v);\n+}\n+\n static void test_v3_typer_accesses(void)\n {\n \tstruct vm_gic v;\n@@ -608,7 +704,8 @@ static void test_v3_redist_ipa_range_check_at_vcpu_run(void)\n \tint ret, i;\n \tu64 addr;\n \n-\tv = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, 1, vcpus);\n+\tv = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, 1, guest_code,\n+\t\t\t\t vcpus);\n \n \t/* Set space for 3 redists, we have 1 vcpu, so this succeeds. */\n \taddr = max_phys_size - (3 * 2 * 0x10000);\n@@ -641,7 +738,8 @@ static void test_v3_its_region(void)\n \tu64 addr;\n \tint its_fd, ret;\n \n-\tv = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, NR_VCPUS, vcpus);\n+\tv = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, NR_VCPUS,\n+\t\t\t\t guest_code, vcpus);\n \tits_fd = kvm_create_device(v.vm, KVM_DEV_TYPE_ARM_VGIC_ITS);\n \n \taddr = 0x401000;\n@@ -684,7 +782,8 @@ static void test_v3_nassgicap(void)\n \tu32 typer2;\n \tint ret;\n \n-\tvm = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, NR_VCPUS, vcpus);\n+\tvm = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, NR_VCPUS,\n+\t\t\t\t guest_code, vcpus);\n \tkvm_device_attr_get(vm.gic_fd, KVM_DEV_ARM_VGIC_GRP_DIST_REGS,\n \t\t\t GICD_TYPER2, \u0026typer2);\n \thas_nassgicap = typer2 \u0026 GICD_TYPER2_nASSGIcap;\n@@ -978,6 +1077,7 @@ void run_tests(u32 gic_dev_type)\n \n \tif (VGIC_DEV_IS_V3(gic_dev_type)) {\n \t\ttest_v3_new_redist_regions();\n+\t\ttest_v3_redist_region_retry();\n \t\ttest_v3_typer_accesses();\n \t\ttest_v3_last_bit_redist_regions();\n \t\ttest_v3_last_bit_single_rdist();\n"
} |
| 2/1 | 2026/08/22 10:20 | agent | patch-evaluator |
1mModel:best-expensive Args: null Results: {
"EnableConfigs": [
"KVM"
],
"FocusSymbols": [
"vgic_v3_set_redist_base",
"vgic_register_redist_iodev",
"vgic_unregister_redist_iodev",
"__vgic_unassign_redist_iodev",
"kvm_vgic_vcpu_destroy",
"kvm_vgic_destroy"
],
"Reasoning": "The patch modifies the KVM ARM64 VGICv3 redistributor region handling, specifically fixing race conditions and rollback logic during vCPU creation and redistributor base address assignment. This is reachable from userspace via KVM ioctls (e.g., KVM_SET_DEVICE_ATTR) and affects core KVM functionality on ARM64, making it highly relevant for fuzzing.",
"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 7e6b471e25afad3d0fa9d7cd12a51c9187de0dfe
Author: syz-cluster <triage@syzkaller.com>
Date: Sat Aug 22 10:20:28 2026 +0000
syz-cluster: applied patch under review
diff --git a/arch/arm64/kvm/vgic/vgic-init.c b/arch/arm64/kvm/vgic/vgic-init.c
index 907057881b26a..81ab5a6bf8bc8 100644
--- a/arch/arm64/kvm/vgic/vgic-init.c
+++ b/arch/arm64/kvm/vgic/vgic-init.c
@@ -519,29 +519,6 @@ static void __kvm_vgic_vcpu_destroy(struct kvm_vcpu *vcpu)
INIT_LIST_HEAD(&vgic_cpu->ap_list_head);
kfree(vgic_cpu->private_irqs);
vgic_cpu->private_irqs = NULL;
-
- if (vcpu->kvm->arch.vgic.vgic_model == KVM_DEV_TYPE_ARM_VGIC_V3) {
- /*
- * If this vCPU is being destroyed because of a failed creation
- * then unregister the redistributor to avoid leaving behind a
- * dangling pointer to the vCPU struct.
- *
- * vCPUs that have been successfully created (i.e. added to
- * kvm->vcpu_array) get unregistered in kvm_vgic_destroy(), as
- * this function gets called while holding kvm->arch.config_lock
- * in the VM teardown path and would otherwise introduce a lock
- * inversion w.r.t. kvm->srcu.
- *
- * vCPUs that failed creation are torn down outside of the
- * kvm->arch.config_lock and do not get unregistered in
- * kvm_vgic_destroy(), meaning it is both safe and necessary to
- * do so here.
- */
- if (kvm_get_vcpu_by_id(vcpu->kvm, vcpu->vcpu_id) != vcpu)
- vgic_unregister_redist_iodev(vcpu);
-
- vgic_cpu->rd_iodev.base_addr = VGIC_ADDR_UNDEF;
- }
}
void kvm_vgic_vcpu_destroy(struct kvm_vcpu *vcpu)
@@ -549,7 +526,16 @@ void kvm_vgic_vcpu_destroy(struct kvm_vcpu *vcpu)
struct kvm *kvm = vcpu->kvm;
mutex_lock(&kvm->slots_lock);
+ if (kvm->arch.vgic.vgic_model == KVM_DEV_TYPE_ARM_VGIC_V3)
+ vgic_unregister_redist_iodev(vcpu);
+
__kvm_vgic_vcpu_destroy(vcpu);
+
+ if (kvm->arch.vgic.vgic_model == KVM_DEV_TYPE_ARM_VGIC_V3) {
+ mutex_lock(&kvm->arch.config_lock);
+ __vgic_unassign_redist_iodev(vcpu);
+ mutex_unlock(&kvm->arch.config_lock);
+ }
mutex_unlock(&kvm->slots_lock);
}
@@ -559,21 +545,23 @@ void kvm_vgic_destroy(struct kvm *kvm)
unsigned long i;
mutex_lock(&kvm->slots_lock);
+ if (kvm->arch.vgic.vgic_model == KVM_DEV_TYPE_ARM_VGIC_V3)
+ kvm_for_each_vcpu(i, vcpu, kvm)
+ vgic_unregister_redist_iodev(vcpu);
+
mutex_lock(&kvm->arch.config_lock);
vgic_debug_destroy(kvm);
- kvm_for_each_vcpu(i, vcpu, kvm)
+ kvm_for_each_vcpu(i, vcpu, kvm) {
__kvm_vgic_vcpu_destroy(vcpu);
+ if (kvm->arch.vgic.vgic_model == KVM_DEV_TYPE_ARM_VGIC_V3)
+ __vgic_unassign_redist_iodev(vcpu);
+ }
kvm_vgic_dist_destroy(kvm);
mutex_unlock(&kvm->arch.config_lock);
-
- if (kvm->arch.vgic.vgic_model == KVM_DEV_TYPE_ARM_VGIC_V3)
- kvm_for_each_vcpu(i, vcpu, kvm)
- vgic_unregister_redist_iodev(vcpu);
-
mutex_unlock(&kvm->slots_lock);
}
diff --git a/arch/arm64/kvm/vgic/vgic-kvm-device.c b/arch/arm64/kvm/vgic/vgic-kvm-device.c
index 90be99443df3b..48c3b2a48c202 100644
--- a/arch/arm64/kvm/vgic/vgic-kvm-device.c
+++ b/arch/arm64/kvm/vgic/vgic-kvm-device.c
@@ -97,6 +97,9 @@ static int kvm_vgic_addr(struct kvm *kvm, struct kvm_device_attr *attr, bool wri
phys_addr_t *addr_ptr, alignment, size;
u64 undef_value = VGIC_ADDR_UNDEF;
u64 addr;
+ bool redist_write = write &&
+ (attr->attr == KVM_VGIC_V3_ADDR_TYPE_REDIST ||
+ attr->attr == KVM_VGIC_V3_ADDR_TYPE_REDIST_REGION);
int r;
/* Reading a redistributor region addr implies getting the index */
@@ -104,6 +107,19 @@ static int kvm_vgic_addr(struct kvm *kvm, struct kvm_device_attr *attr, bool wri
if (get_user(addr, uaddr))
return -EFAULT;
+ /*
+ * A vCPU can have an RD assignment before it is visible to
+ * kvm_for_each_vcpu(). Reject redistributor updates while vCPU creation
+ * is in progress, so rollback can reset every assignment.
+ */
+ if (redist_write) {
+ mutex_lock(&kvm->lock);
+ if (kvm->created_vcpus != atomic_read(&kvm->online_vcpus)) {
+ r = -EBUSY;
+ goto out_unlock_kvm;
+ }
+ }
+
/*
* Since we can't hold config_lock while registering the redistributor
* iodevs, take the slots_lock immediately.
@@ -201,6 +217,10 @@ static int kvm_vgic_addr(struct kvm *kvm, struct kvm_device_attr *attr, bool wri
out:
mutex_unlock(&kvm->slots_lock);
+out_unlock_kvm:
+ if (redist_write)
+ mutex_unlock(&kvm->lock);
+
if (!r && !write)
r = put_user(addr, uaddr);
diff --git a/arch/arm64/kvm/vgic/vgic-mmio-v3.c b/arch/arm64/kvm/vgic/vgic-mmio-v3.c
index 5913a20d83019..dc860178105d9 100644
--- a/arch/arm64/kvm/vgic/vgic-mmio-v3.c
+++ b/arch/arm64/kvm/vgic/vgic-mmio-v3.c
@@ -766,6 +766,18 @@ unsigned int vgic_v3_init_dist_iodev(struct vgic_io_device *dev)
return SZ_64K;
}
+static void vgic_undo_redist_assignment(struct kvm_vcpu *vcpu)
+{
+ struct vgic_cpu *vgic_cpu = &vcpu->arch.vgic_cpu;
+
+ lockdep_assert_held(&vcpu->kvm->slots_lock);
+
+ guard(mutex)(&vcpu->kvm->arch.config_lock);
+
+ vgic_cpu->rdreg->free_index--;
+ __vgic_unassign_redist_iodev(vcpu);
+}
+
/**
* vgic_register_redist_iodev - register a single redist iodev
* @vcpu: The VCPU to which the redistributor belongs
@@ -818,16 +830,17 @@ int vgic_register_redist_iodev(struct kvm_vcpu *vcpu)
rd_dev->nr_regions = ARRAY_SIZE(vgic_v3_rd_registers);
rd_dev->redist_vcpu = vcpu;
+ /* Protected by slots_lock */
+ rdreg->free_index++;
+
mutex_unlock(&kvm->arch.config_lock);
ret = kvm_io_bus_register_dev(kvm, KVM_MMIO_BUS, rd_base,
2 * SZ_64K, &rd_dev->dev);
if (ret)
- return ret;
+ vgic_undo_redist_assignment(vcpu);
- /* Protected by slots_lock */
- rdreg->free_index++;
- return 0;
+ return ret;
out_unlock:
mutex_unlock(&kvm->arch.config_lock);
@@ -841,6 +854,40 @@ void vgic_unregister_redist_iodev(struct kvm_vcpu *vcpu)
kvm_io_bus_unregister_dev(vcpu->kvm, KVM_MMIO_BUS, &rd_dev->dev);
}
+void __vgic_unassign_redist_iodev(struct kvm_vcpu *vcpu)
+{
+ struct vgic_cpu *vgic_cpu = &vcpu->arch.vgic_cpu;
+
+ lockdep_assert_held(&vcpu->kvm->arch.config_lock);
+
+ vgic_cpu->rdreg = NULL;
+ vgic_cpu->rd_iodev.base_addr = VGIC_ADDR_UNDEF;
+}
+
+static void vgic_v3_rollback_redist_region(struct kvm *kvm, u32 index)
+{
+ struct vgic_redist_region *rdreg, *iter;
+ struct kvm_vcpu *vcpu;
+ unsigned long c;
+
+ lockdep_assert_held(&kvm->slots_lock);
+
+ rdreg = vgic_v3_rdist_region_from_index(kvm, index);
+
+ kvm_for_each_vcpu(c, vcpu, kvm)
+ vgic_unregister_redist_iodev(vcpu);
+
+ guard(mutex)(&kvm->arch.config_lock);
+
+ kvm_for_each_vcpu(c, vcpu, kvm)
+ __vgic_unassign_redist_iodev(vcpu);
+
+ list_for_each_entry(iter, &kvm->arch.vgic.rd_regions, list)
+ iter->free_index = 0;
+
+ vgic_v3_free_redist_region(kvm, rdreg);
+}
+
static int vgic_register_all_redist_iodevs(struct kvm *kvm)
{
struct kvm_vcpu *vcpu;
@@ -855,16 +902,6 @@ static int vgic_register_all_redist_iodevs(struct kvm *kvm)
break;
}
- if (ret) {
- /* The current c failed, so iterate over the previous ones. */
- int i;
-
- for (i = 0; i < c; i++) {
- vcpu = kvm_get_vcpu(kvm, i);
- vgic_unregister_redist_iodev(vcpu);
- }
- }
-
return ret;
}
@@ -953,17 +990,8 @@ static int vgic_v3_alloc_redist_region(struct kvm *kvm, uint32_t index,
void vgic_v3_free_redist_region(struct kvm *kvm, struct vgic_redist_region *rdreg)
{
- struct kvm_vcpu *vcpu;
- unsigned long c;
-
lockdep_assert_held(&kvm->arch.config_lock);
- /* Garbage collect the region */
- kvm_for_each_vcpu(c, vcpu, kvm) {
- if (vcpu->arch.vgic_cpu.rdreg == rdreg)
- vcpu->arch.vgic_cpu.rdreg = NULL;
- }
-
list_del(&rdreg->list);
kfree(rdreg);
}
@@ -972,6 +1000,8 @@ int vgic_v3_set_redist_base(struct kvm *kvm, u32 index, u64 addr, u32 count)
{
int ret;
+ lockdep_assert_held(&kvm->lock);
+
mutex_lock(&kvm->arch.config_lock);
ret = vgic_v3_alloc_redist_region(kvm, index, addr, count);
mutex_unlock(&kvm->arch.config_lock);
@@ -984,12 +1014,7 @@ int vgic_v3_set_redist_base(struct kvm *kvm, u32 index, u64 addr, u32 count)
*/
ret = vgic_register_all_redist_iodevs(kvm);
if (ret) {
- struct vgic_redist_region *rdreg;
-
- mutex_lock(&kvm->arch.config_lock);
- rdreg = vgic_v3_rdist_region_from_index(kvm, index);
- vgic_v3_free_redist_region(kvm, rdreg);
- mutex_unlock(&kvm->arch.config_lock);
+ vgic_v3_rollback_redist_region(kvm, index);
return ret;
}
diff --git a/arch/arm64/kvm/vgic/vgic.h b/arch/arm64/kvm/vgic/vgic.h
index f45f7e3ec4d6e..e32e2b56a47ef 100644
--- a/arch/arm64/kvm/vgic/vgic.h
+++ b/arch/arm64/kvm/vgic/vgic.h
@@ -333,6 +333,7 @@ int vgic_v3_save_pending_tables(struct kvm *kvm);
int vgic_v3_set_redist_base(struct kvm *kvm, u32 index, u64 addr, u32 count);
int vgic_register_redist_iodev(struct kvm_vcpu *vcpu);
void vgic_unregister_redist_iodev(struct kvm_vcpu *vcpu);
+void __vgic_unassign_redist_iodev(struct kvm_vcpu *vcpu);
bool vgic_v3_check_base(struct kvm *kvm);
void vgic_v3_load(struct kvm_vcpu *vcpu);
diff --git a/tools/testing/selftests/kvm/arm64/vgic_init.c b/tools/testing/selftests/kvm/arm64/vgic_init.c
index 47e34b43afb29..5a30f3cb039be 100644
--- a/tools/testing/selftests/kvm/arm64/vgic_init.c
+++ b/tools/testing/selftests/kvm/arm64/vgic_init.c
@@ -5,6 +5,7 @@
* Copyright (C) 2020, Red Hat, Inc.
*/
#include <linux/kernel.h>
+#include <linux/sizes.h>
#include <sys/syscall.h>
#include <asm/kvm.h>
#include <asm/kvm_para.h>
@@ -13,12 +14,21 @@
#include "test_util.h"
#include "kvm_util.h"
+#include "gic.h"
#include "processor.h"
#include "vgic.h"
#include "gic_v3.h"
#define NR_VCPUS 4
+#define REDIST_RETRY_REGION0_BASE GICR_BASE_GPA
+#define REDIST_RETRY_REGION1_BASE \
+ (REDIST_RETRY_REGION0_BASE + 2 * KVM_VGIC_V3_REDIST_SIZE)
+#define REDIST_RETRY_DIST_BASE \
+ (REDIST_RETRY_REGION1_BASE + KVM_VGIC_V3_REDIST_SIZE)
+#define REDIST_RETRY_REGION2_BASE \
+ (REDIST_RETRY_DIST_BASE + KVM_VGIC_V3_DIST_SIZE)
+
#define REG_OFFSET(vcpu, offset) (((u64)vcpu << 32) | offset)
#define VGIC_DEV_IS_V2(_d) ((_d) == KVM_DEV_TYPE_ARM_VGIC_V2)
@@ -65,6 +75,23 @@ static void guest_code(void)
GUEST_DONE();
}
+static void guest_check_redist_retry(void)
+{
+ unsigned int i;
+
+ /* The first three redistributors span adjacent regions 0 and 1. */
+ for (i = 0; i < NR_VCPUS; i++) {
+ u64 base = i < 3 ? REDIST_RETRY_REGION0_BASE +
+ i * KVM_VGIC_V3_REDIST_SIZE :
+ REDIST_RETRY_REGION2_BASE;
+ u64 typer = readq((void *)(unsigned long)(base + GICR_TYPER));
+
+ GUEST_ASSERT_EQ(GICR_TYPER_CPU_NUMBER(typer), i);
+ }
+
+ GUEST_DONE();
+}
+
/* we don't want to assert on run execution, hence that helper */
static int run_vcpu(struct kvm_vcpu *vcpu)
{
@@ -73,6 +100,7 @@ static int run_vcpu(struct kvm_vcpu *vcpu)
static struct vm_gic vm_gic_create_with_vcpus(u32 gic_dev_type,
u32 nr_vcpus,
+ void *guest_code,
struct kvm_vcpu *vcpus[])
{
struct vm_gic v;
@@ -338,7 +366,7 @@ static void test_vgic_then_vcpus(u32 gic_dev_type)
struct vm_gic v;
int ret, i;
- v = vm_gic_create_with_vcpus(gic_dev_type, 1, vcpus);
+ v = vm_gic_create_with_vcpus(gic_dev_type, 1, guest_code, vcpus);
subtest_dist_rdist(&v);
@@ -359,7 +387,8 @@ static void test_vcpus_then_vgic(u32 gic_dev_type)
struct vm_gic v;
int ret;
- v = vm_gic_create_with_vcpus(gic_dev_type, NR_VCPUS, vcpus);
+ v = vm_gic_create_with_vcpus(gic_dev_type, NR_VCPUS, guest_code,
+ vcpus);
subtest_dist_rdist(&v);
@@ -411,7 +440,8 @@ static void test_v3_new_redist_regions(void)
u64 addr;
int ret;
- v = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, NR_VCPUS, vcpus);
+ v = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, NR_VCPUS,
+ guest_code, vcpus);
subtest_v3_redist_regions(&v);
kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_CTRL,
KVM_DEV_ARM_VGIC_CTRL_INIT, NULL);
@@ -422,7 +452,8 @@ static void test_v3_new_redist_regions(void)
/* step2 */
- v = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, NR_VCPUS, vcpus);
+ v = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, NR_VCPUS,
+ guest_code, vcpus);
subtest_v3_redist_regions(&v);
addr = REDIST_REGION_ATTR_ADDR(1, 0x280000, 0, 2);
@@ -436,7 +467,8 @@ static void test_v3_new_redist_regions(void)
/* step 3 */
- v = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, NR_VCPUS, vcpus);
+ v = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, NR_VCPUS,
+ guest_code, vcpus);
subtest_v3_redist_regions(&v);
ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
@@ -457,6 +489,70 @@ static void test_v3_new_redist_regions(void)
vm_gic_destroy(&v);
}
+static void test_v3_redist_region_retry(void)
+{
+ struct kvm_vcpu *vcpus[NR_VCPUS];
+ struct vm_gic v;
+ struct ucall uc;
+ u64 addr;
+ int ret;
+
+ v = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, NR_VCPUS,
+ guest_check_redist_retry, vcpus);
+
+ addr = REDIST_REGION_ATTR_ADDR(2, REDIST_RETRY_REGION0_BASE, 0, 0);
+ kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
+ KVM_VGIC_V3_ADDR_TYPE_REDIST_REGION, &addr);
+
+ addr = REDIST_REGION_ATTR_ADDR(1, REDIST_RETRY_REGION1_BASE, 0, 1);
+ kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
+ KVM_VGIC_V3_ADDR_TYPE_REDIST_REGION, &addr);
+
+ addr = REDIST_RETRY_DIST_BASE;
+ kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
+ KVM_VGIC_V3_ADDR_TYPE_DIST, &addr);
+
+ addr = REDIST_REGION_ATTR_ADDR(1, REDIST_RETRY_DIST_BASE, 0, 2);
+ ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
+ KVM_VGIC_V3_ADDR_TYPE_REDIST_REGION,
+ &addr);
+ TEST_ASSERT(ret && errno == EINVAL,
+ "register redist region colliding with dist");
+
+ addr = REDIST_REGION_ATTR_ADDR(1, REDIST_RETRY_REGION2_BASE, 0, 2);
+ kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
+ KVM_VGIC_V3_ADDR_TYPE_REDIST_REGION, &addr);
+
+ virt_map(v.vm, REDIST_RETRY_REGION0_BASE, REDIST_RETRY_REGION0_BASE,
+ vm_calc_num_guest_pages(v.vm->mode,
+ 3 * KVM_VGIC_V3_REDIST_SIZE));
+ virt_map(v.vm, REDIST_RETRY_REGION2_BASE, REDIST_RETRY_REGION2_BASE,
+ vm_calc_num_guest_pages(v.vm->mode,
+ KVM_VGIC_V3_REDIST_SIZE));
+
+ kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_CTRL,
+ KVM_DEV_ARM_VGIC_CTRL_INIT, NULL);
+
+ vcpu_run(vcpus[0]);
+ switch (get_ucall(vcpus[0], &uc)) {
+ case UCALL_DONE:
+ break;
+ case UCALL_ABORT:
+ REPORT_GUEST_ASSERT(uc);
+ break;
+ case UCALL_NONE:
+ if (vcpus[0]->run->exit_reason == KVM_EXIT_MMIO)
+ TEST_FAIL("Unexpected MMIO exit at 0x%llx",
+ vcpus[0]->run->mmio.phys_addr);
+ fallthrough;
+ default:
+ TEST_FAIL("Unexpected ucall %lu, exit_reason %u",
+ uc.cmd, vcpus[0]->run->exit_reason);
+ }
+
+ vm_gic_destroy(&v);
+}
+
static void test_v3_typer_accesses(void)
{
struct vm_gic v;
@@ -608,7 +704,8 @@ static void test_v3_redist_ipa_range_check_at_vcpu_run(void)
int ret, i;
u64 addr;
- v = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, 1, vcpus);
+ v = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, 1, guest_code,
+ vcpus);
/* Set space for 3 redists, we have 1 vcpu, so this succeeds. */
addr = max_phys_size - (3 * 2 * 0x10000);
@@ -641,7 +738,8 @@ static void test_v3_its_region(void)
u64 addr;
int its_fd, ret;
- v = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, NR_VCPUS, vcpus);
+ v = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, NR_VCPUS,
+ guest_code, vcpus);
its_fd = kvm_create_device(v.vm, KVM_DEV_TYPE_ARM_VGIC_ITS);
addr = 0x401000;
@@ -684,7 +782,8 @@ static void test_v3_nassgicap(void)
u32 typer2;
int ret;
- vm = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, NR_VCPUS, vcpus);
+ vm = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, NR_VCPUS,
+ guest_code, vcpus);
kvm_device_attr_get(vm.gic_fd, KVM_DEV_ARM_VGIC_GRP_DIST_REGS,
GICD_TYPER2, &typer2);
has_nassgicap = typer2 & GICD_TYPER2_nASSGIcap;
@@ -978,6 +1077,7 @@ void run_tests(u32 gic_dev_type)
if (VGIC_DEV_IS_V3(gic_dev_type)) {
test_v3_new_redist_regions();
+ test_v3_redist_region_retry();
test_v3_typer_accesses();
test_v3_last_bit_redist_regions();
test_v3_last_bit_single_rdist();
|
| 3/2 | 2026/08/22 10:20 | llm | patch-evaluator |
1mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 9487 output: 44 thoughts: 800 Thoughts: **Fuzzing Target Identification: KVM ARM64 VGICv3 Redistributor Logic** Okay, so I've got a patch here, and my initial assessment is that this definitely warrants fuzzing. The changes are focused on the ARM64 KVM VGIC implementation, specifically dealing with the VGICv3 redistributor regions. This is core functionality, and it's reachable from userspace via KVM ioctls like `KVM_SET_DEVICE_ATTR` for VGIC v3. Since we *can* fuzz KVM ARM64 – we've got tools like syzkaller that work on it – this is a prime target. The patch touches a number of files: `arch/arm64/kvm/vgic/vgic-init.c`, `arch/arm64/kvm/vgic/vgic-kvm-device.c`, `arch/arm64/kvm/vgic/vgic-mmio-v3.c`, and `arch/arm64/kvm/vgic/vgic.h`. This gives a strong clue that we are dealing with core logic. Specifically, it looks like these changes are modifying how redistributor iodevs are registered, unregistered, and how redistributor assignments are handled, including rollbacks and undo operations. I see functions like `vgic_undo_redist_assignment`, `__vgic_unassign_redist_iodev`, and `vgic_v3_rollback_redist_region` added, which is a good indicator of complex state management. There's also a check in `kvm_vgic_addr` that restricts redistributor updates during vCPU creation. So, there is state tracking. Now, the important part. I need to figure out *which* specific functions are the most important for fuzzing. After going through the code, I am looking at: * `kvm_vgic_vcpu_destroy` - global * `kvm_vgic_destroy` - global * `kvm_vgic_addr` - static, but likely we can trigger via `vgic_v3_set_redist_base` or `vgic_register_redist_iodev` * `vgic_v3_set_redist_base` - global * `vgic_register_redist_iodev` - global * `vgic_unregister_redist_iodev` - global * `__vgic_unassign_redist_iodev` - global I can confirm this functionality has the `CONFIG_KVM` Kconfig option. So, in conclusion, this is a definite fuzzing target. I need to focus on these exposed functions and how they relate to userspace interaction via ioctls. I will now set up the appropriate syzkaller targets. |
| 4/2 | 2026/08/22 10:21 | tool | grepper |
0mArgs:{
"Expression": "vgic_v3_set_redist_base",
"PathPrefix": "arch/arm64/kvm/vgic/"
}Results: {
"Output": "arch/arm64/kvm/vgic/vgic-kvm-device.c=93=static int kvm_vgic_addr(struct kvm *kvm, struct kvm_device_attr *attr, bool write)\n--\narch/arm64/kvm/vgic/vgic-kvm-device.c-153-\t\tif (write) {\narch/arm64/kvm/vgic/vgic-kvm-device.c:154:\t\t\tr = vgic_v3_set_redist_base(kvm, 0, addr, 0);\narch/arm64/kvm/vgic/vgic-kvm-device.c-155-\t\t\tgoto out;\n--\narch/arm64/kvm/vgic/vgic-kvm-device.c-183-\t\t\telse\narch/arm64/kvm/vgic/vgic-kvm-device.c:184:\t\t\t\tr = vgic_v3_set_redist_base(kvm, index,\narch/arm64/kvm/vgic/vgic-kvm-device.c-185-\t\t\t\t\t\t\t base, count);\n--\narch/arm64/kvm/vgic/vgic-mmio-v3.c=991=void vgic_v3_free_redist_region(struct kvm *kvm, struct vgic_redist_region *rdreg)\n--\narch/arm64/kvm/vgic/vgic-mmio-v3.c-998-\narch/arm64/kvm/vgic/vgic-mmio-v3.c:999:int vgic_v3_set_redist_base(struct kvm *kvm, u32 index, u64 addr, u32 count)\narch/arm64/kvm/vgic/vgic-mmio-v3.c-1000-{\n--\narch/arm64/kvm/vgic/vgic.h=332=int vgic_v3_save_pending_tables(struct kvm *kvm);\narch/arm64/kvm/vgic/vgic.h:333:int vgic_v3_set_redist_base(struct kvm *kvm, u32 index, u64 addr, u32 count);\narch/arm64/kvm/vgic/vgic.h-334-int vgic_register_redist_iodev(struct kvm_vcpu *vcpu);\n"
} |
| 5/2 | 2026/08/22 10:21 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 11594 output: 34 thoughts: 98 Thoughts: **Considering Vgic Cleanup in the KVM/ARM64 Context** Okay, so I'm looking at cleaning up the Virtual Generic Interrupt Controller (vGIC) code, specifically on ARM64 within the KVM environment. I've got a few functions that immediately jump out as potential candidates for examination: `vgic_v3_set_redist_base`, `vgic_register_redist_iodev`, `vgic_unregister_redist_iodev`, `__vgic_unassign_redist_iodev`, `kvm_vgic_vcpu_destroy`, and `kvm_vgic_destroy`. These all seem intimately involved in the allocation, deallocation, and lifecycle management of the vGIC redistributor and associated resources. Seems like a solid place to start digging deeper. Before I dive into each function individually though, I should double-check the relevant Kconfig options, specifically regarding KVM support on ARM64. I need to make sure I'm fully aware of how KVM is configured, the build flags, and any dependencies before proceeding. Understanding the configuration will help me avoid any potential missteps or overlooking critical dependencies. |
| 6/2 | 2026/08/22 10:21 | tool | grepper |
0mArgs:{
"Expression": "config KVM",
"PathPrefix": "arch/arm64/kvm/Kconfig"
}Results: {
"Output": "arch/arm64/kvm/Kconfig=18=if VIRTUALIZATION\narch/arm64/kvm/Kconfig-19-\narch/arm64/kvm/Kconfig:20:menuconfig KVM\narch/arm64/kvm/Kconfig-21-\tbool \"Kernel-based Virtual Machine (KVM) support\"\n"
} |
| 7/2 | 2026/08/22 10:21 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 12078 output: 35 thoughts: 27 Thoughts: Here's my take on those thoughts, tailored for an expert audience: **KVM Configuration Deep Dive** Okay, so we're starting with `KVM`, which means we're dealing with the core virtualization environment. Now, I need to immediately drill down into the specifics of the configuration. My focus, as always, is on performance optimization and platform specifics. Given the architecture of the system, I need to check the KVM configuration for the inclusion of `VGICv3` details. I am going to see what sort of specific tunings we have in place there. Is there a dedicated configuration block for VGICv3, or are the settings implied through some broader settings? It’s crucial to know if we have explicit control or if we're inheriting defaults. Either way, I'm going to determine what our setup is. |
| 8/2 | 2026/08/22 10:21 | tool | grepper |
0mError:no matches Args: {
"Expression": "config.*VGIC",
"PathPrefix": "arch/arm64/kvm/Kconfig"
}Results: {
"error": "no matches"
} |
| 9/2 | 2026/08/22 10:21 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 12331 output: 197 thoughts: 282 Thoughts: **My Assessment of the KVM ARM64 VGICv3 Patch** Okay, so this isn't anything exotic; it's just dealing with `KVM`. Let me make sure I'm following. The key seems to be the `FocusSymbols`, let's double check those: `vgic_v3_set_redist_base`, `vgic_register_redist_iodev`, `vgic_unregister_redist_iodev`, `__vgic_unassign_redist_iodev`, `kvm_vgic_vcpu_destroy`, and `kvm_vgic_destroy`. Right, these are all global API entrypoints, not static or inlined. The core of this patch is about how the KVM ARM64 VGICv3 handles the redistributor region. It’s about fixing races and rollback issues that pop up when we're creating vCPUs and assigning the base address of the redistributor. This means userspace can trigger this through those `KVM_SET_DEVICE_ATTR` ioctls – so, core KVM functionality on ARM64. Just to be sure, I need to check `kvm_vgic_addr`. Aha, yes, it's static in `arch/arm64/kvm/vgic/vgic-kvm-device.c`. But it's alright, `vgic_v3_set_redist_base` is global and called from it, so it's all making sense. Alright, I'm confident in my analysis; I'll record my findings now. |
| 10/2 | 2026/08/22 10:21 | tool | set-results |
0mArgs:{
"EnableConfigs": [
"KVM"
],
"FocusSymbols": [
"vgic_v3_set_redist_base",
"vgic_register_redist_iodev",
"vgic_unregister_redist_iodev",
"__vgic_unassign_redist_iodev",
"kvm_vgic_vcpu_destroy",
"kvm_vgic_destroy"
],
"Reasoning": "The patch modifies the KVM ARM64 VGICv3 redistributor region handling, specifically fixing race conditions and rollback logic during vCPU creation and redistributor base address assignment. This is reachable from userspace via KVM ioctls (e.g., KVM_SET_DEVICE_ATTR) and affects core KVM functionality on ARM64, making it highly relevant for fuzzing.",
"WorthFuzzing": true
}Results: {
"EnableConfigs": [
"KVM"
],
"FocusSymbols": [
"vgic_v3_set_redist_base",
"vgic_register_redist_iodev",
"vgic_unregister_redist_iodev",
"__vgic_unassign_redist_iodev",
"kvm_vgic_vcpu_destroy",
"kvm_vgic_destroy"
],
"Reasoning": "The patch modifies the KVM ARM64 VGICv3 redistributor region handling, specifically fixing race conditions and rollback logic during vCPU creation and redistributor base address assignment. This is reachable from userspace via KVM ioctls (e.g., KVM_SET_DEVICE_ATTR) and affects core KVM functionality on ARM64, making it highly relevant for fuzzing.",
"WorthFuzzing": true
} |
| 11/1 | 2026/08/22 10:21 | action | If |
0mArgs:{
"WorthFuzzing": true
}Results: null |
| 12/2 | 2026/08/22 10:21 | agent | kmsan-evaluator |
0mModel:best-expensive Args: null Results: {
"KMSANReasoning": "The patch modifies KVM VGIC v3 redistributor initialization, teardown, and rollback logic. It introduces proper cleanup functions (`vgic_undo_redist_assignment`, `__vgic_unassign_redist_iodev`, `vgic_v3_rollback_redist_region`) and adjusts locking to prevent dangling pointers and lock inversions during vCPU creation failures or redistributor base setup failures. The changes are strictly related to resource management, concurrency, and object lifecycle. There are no new structures exposed to user space, no new complex allocations that could leak uninitialized memory, and no changes to bounds checking that would allow reading uninitialized bytes. Any bugs related to these changes (such as use-after-free, dangling pointers, or deadlocks) are well within the scope of KASAN and LOCKDEP. A dedicated KMSAN session is not justified.",
"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 7e6b471e25afad3d0fa9d7cd12a51c9187de0dfe
Author: syz-cluster <triage@syzkaller.com>
Date: Sat Aug 22 10:20:28 2026 +0000
syz-cluster: applied patch under review
diff --git a/arch/arm64/kvm/vgic/vgic-init.c b/arch/arm64/kvm/vgic/vgic-init.c
index 907057881b26a..81ab5a6bf8bc8 100644
--- a/arch/arm64/kvm/vgic/vgic-init.c
+++ b/arch/arm64/kvm/vgic/vgic-init.c
@@ -519,29 +519,6 @@ static void __kvm_vgic_vcpu_destroy(struct kvm_vcpu *vcpu)
INIT_LIST_HEAD(&vgic_cpu->ap_list_head);
kfree(vgic_cpu->private_irqs);
vgic_cpu->private_irqs = NULL;
-
- if (vcpu->kvm->arch.vgic.vgic_model == KVM_DEV_TYPE_ARM_VGIC_V3) {
- /*
- * If this vCPU is being destroyed because of a failed creation
- * then unregister the redistributor to avoid leaving behind a
- * dangling pointer to the vCPU struct.
- *
- * vCPUs that have been successfully created (i.e. added to
- * kvm->vcpu_array) get unregistered in kvm_vgic_destroy(), as
- * this function gets called while holding kvm->arch.config_lock
- * in the VM teardown path and would otherwise introduce a lock
- * inversion w.r.t. kvm->srcu.
- *
- * vCPUs that failed creation are torn down outside of the
- * kvm->arch.config_lock and do not get unregistered in
- * kvm_vgic_destroy(), meaning it is both safe and necessary to
- * do so here.
- */
- if (kvm_get_vcpu_by_id(vcpu->kvm, vcpu->vcpu_id) != vcpu)
- vgic_unregister_redist_iodev(vcpu);
-
- vgic_cpu->rd_iodev.base_addr = VGIC_ADDR_UNDEF;
- }
}
void kvm_vgic_vcpu_destroy(struct kvm_vcpu *vcpu)
@@ -549,7 +526,16 @@ void kvm_vgic_vcpu_destroy(struct kvm_vcpu *vcpu)
struct kvm *kvm = vcpu->kvm;
mutex_lock(&kvm->slots_lock);
+ if (kvm->arch.vgic.vgic_model == KVM_DEV_TYPE_ARM_VGIC_V3)
+ vgic_unregister_redist_iodev(vcpu);
+
__kvm_vgic_vcpu_destroy(vcpu);
+
+ if (kvm->arch.vgic.vgic_model == KVM_DEV_TYPE_ARM_VGIC_V3) {
+ mutex_lock(&kvm->arch.config_lock);
+ __vgic_unassign_redist_iodev(vcpu);
+ mutex_unlock(&kvm->arch.config_lock);
+ }
mutex_unlock(&kvm->slots_lock);
}
@@ -559,21 +545,23 @@ void kvm_vgic_destroy(struct kvm *kvm)
unsigned long i;
mutex_lock(&kvm->slots_lock);
+ if (kvm->arch.vgic.vgic_model == KVM_DEV_TYPE_ARM_VGIC_V3)
+ kvm_for_each_vcpu(i, vcpu, kvm)
+ vgic_unregister_redist_iodev(vcpu);
+
mutex_lock(&kvm->arch.config_lock);
vgic_debug_destroy(kvm);
- kvm_for_each_vcpu(i, vcpu, kvm)
+ kvm_for_each_vcpu(i, vcpu, kvm) {
__kvm_vgic_vcpu_destroy(vcpu);
+ if (kvm->arch.vgic.vgic_model == KVM_DEV_TYPE_ARM_VGIC_V3)
+ __vgic_unassign_redist_iodev(vcpu);
+ }
kvm_vgic_dist_destroy(kvm);
mutex_unlock(&kvm->arch.config_lock);
-
- if (kvm->arch.vgic.vgic_model == KVM_DEV_TYPE_ARM_VGIC_V3)
- kvm_for_each_vcpu(i, vcpu, kvm)
- vgic_unregister_redist_iodev(vcpu);
-
mutex_unlock(&kvm->slots_lock);
}
diff --git a/arch/arm64/kvm/vgic/vgic-kvm-device.c b/arch/arm64/kvm/vgic/vgic-kvm-device.c
index 90be99443df3b..48c3b2a48c202 100644
--- a/arch/arm64/kvm/vgic/vgic-kvm-device.c
+++ b/arch/arm64/kvm/vgic/vgic-kvm-device.c
@@ -97,6 +97,9 @@ static int kvm_vgic_addr(struct kvm *kvm, struct kvm_device_attr *attr, bool wri
phys_addr_t *addr_ptr, alignment, size;
u64 undef_value = VGIC_ADDR_UNDEF;
u64 addr;
+ bool redist_write = write &&
+ (attr->attr == KVM_VGIC_V3_ADDR_TYPE_REDIST ||
+ attr->attr == KVM_VGIC_V3_ADDR_TYPE_REDIST_REGION);
int r;
/* Reading a redistributor region addr implies getting the index */
@@ -104,6 +107,19 @@ static int kvm_vgic_addr(struct kvm *kvm, struct kvm_device_attr *attr, bool wri
if (get_user(addr, uaddr))
return -EFAULT;
+ /*
+ * A vCPU can have an RD assignment before it is visible to
+ * kvm_for_each_vcpu(). Reject redistributor updates while vCPU creation
+ * is in progress, so rollback can reset every assignment.
+ */
+ if (redist_write) {
+ mutex_lock(&kvm->lock);
+ if (kvm->created_vcpus != atomic_read(&kvm->online_vcpus)) {
+ r = -EBUSY;
+ goto out_unlock_kvm;
+ }
+ }
+
/*
* Since we can't hold config_lock while registering the redistributor
* iodevs, take the slots_lock immediately.
@@ -201,6 +217,10 @@ static int kvm_vgic_addr(struct kvm *kvm, struct kvm_device_attr *attr, bool wri
out:
mutex_unlock(&kvm->slots_lock);
+out_unlock_kvm:
+ if (redist_write)
+ mutex_unlock(&kvm->lock);
+
if (!r && !write)
r = put_user(addr, uaddr);
diff --git a/arch/arm64/kvm/vgic/vgic-mmio-v3.c b/arch/arm64/kvm/vgic/vgic-mmio-v3.c
index 5913a20d83019..dc860178105d9 100644
--- a/arch/arm64/kvm/vgic/vgic-mmio-v3.c
+++ b/arch/arm64/kvm/vgic/vgic-mmio-v3.c
@@ -766,6 +766,18 @@ unsigned int vgic_v3_init_dist_iodev(struct vgic_io_device *dev)
return SZ_64K;
}
+static void vgic_undo_redist_assignment(struct kvm_vcpu *vcpu)
+{
+ struct vgic_cpu *vgic_cpu = &vcpu->arch.vgic_cpu;
+
+ lockdep_assert_held(&vcpu->kvm->slots_lock);
+
+ guard(mutex)(&vcpu->kvm->arch.config_lock);
+
+ vgic_cpu->rdreg->free_index--;
+ __vgic_unassign_redist_iodev(vcpu);
+}
+
/**
* vgic_register_redist_iodev - register a single redist iodev
* @vcpu: The VCPU to which the redistributor belongs
@@ -818,16 +830,17 @@ int vgic_register_redist_iodev(struct kvm_vcpu *vcpu)
rd_dev->nr_regions = ARRAY_SIZE(vgic_v3_rd_registers);
rd_dev->redist_vcpu = vcpu;
+ /* Protected by slots_lock */
+ rdreg->free_index++;
+
mutex_unlock(&kvm->arch.config_lock);
ret = kvm_io_bus_register_dev(kvm, KVM_MMIO_BUS, rd_base,
2 * SZ_64K, &rd_dev->dev);
if (ret)
- return ret;
+ vgic_undo_redist_assignment(vcpu);
- /* Protected by slots_lock */
- rdreg->free_index++;
- return 0;
+ return ret;
out_unlock:
mutex_unlock(&kvm->arch.config_lock);
@@ -841,6 +854,40 @@ void vgic_unregister_redist_iodev(struct kvm_vcpu *vcpu)
kvm_io_bus_unregister_dev(vcpu->kvm, KVM_MMIO_BUS, &rd_dev->dev);
}
+void __vgic_unassign_redist_iodev(struct kvm_vcpu *vcpu)
+{
+ struct vgic_cpu *vgic_cpu = &vcpu->arch.vgic_cpu;
+
+ lockdep_assert_held(&vcpu->kvm->arch.config_lock);
+
+ vgic_cpu->rdreg = NULL;
+ vgic_cpu->rd_iodev.base_addr = VGIC_ADDR_UNDEF;
+}
+
+static void vgic_v3_rollback_redist_region(struct kvm *kvm, u32 index)
+{
+ struct vgic_redist_region *rdreg, *iter;
+ struct kvm_vcpu *vcpu;
+ unsigned long c;
+
+ lockdep_assert_held(&kvm->slots_lock);
+
+ rdreg = vgic_v3_rdist_region_from_index(kvm, index);
+
+ kvm_for_each_vcpu(c, vcpu, kvm)
+ vgic_unregister_redist_iodev(vcpu);
+
+ guard(mutex)(&kvm->arch.config_lock);
+
+ kvm_for_each_vcpu(c, vcpu, kvm)
+ __vgic_unassign_redist_iodev(vcpu);
+
+ list_for_each_entry(iter, &kvm->arch.vgic.rd_regions, list)
+ iter->free_index = 0;
+
+ vgic_v3_free_redist_region(kvm, rdreg);
+}
+
static int vgic_register_all_redist_iodevs(struct kvm *kvm)
{
struct kvm_vcpu *vcpu;
@@ -855,16 +902,6 @@ static int vgic_register_all_redist_iodevs(struct kvm *kvm)
break;
}
- if (ret) {
- /* The current c failed, so iterate over the previous ones. */
- int i;
-
- for (i = 0; i < c; i++) {
- vcpu = kvm_get_vcpu(kvm, i);
- vgic_unregister_redist_iodev(vcpu);
- }
- }
-
return ret;
}
@@ -953,17 +990,8 @@ static int vgic_v3_alloc_redist_region(struct kvm *kvm, uint32_t index,
void vgic_v3_free_redist_region(struct kvm *kvm, struct vgic_redist_region *rdreg)
{
- struct kvm_vcpu *vcpu;
- unsigned long c;
-
lockdep_assert_held(&kvm->arch.config_lock);
- /* Garbage collect the region */
- kvm_for_each_vcpu(c, vcpu, kvm) {
- if (vcpu->arch.vgic_cpu.rdreg == rdreg)
- vcpu->arch.vgic_cpu.rdreg = NULL;
- }
-
list_del(&rdreg->list);
kfree(rdreg);
}
@@ -972,6 +1000,8 @@ int vgic_v3_set_redist_base(struct kvm *kvm, u32 index, u64 addr, u32 count)
{
int ret;
+ lockdep_assert_held(&kvm->lock);
+
mutex_lock(&kvm->arch.config_lock);
ret = vgic_v3_alloc_redist_region(kvm, index, addr, count);
mutex_unlock(&kvm->arch.config_lock);
@@ -984,12 +1014,7 @@ int vgic_v3_set_redist_base(struct kvm *kvm, u32 index, u64 addr, u32 count)
*/
ret = vgic_register_all_redist_iodevs(kvm);
if (ret) {
- struct vgic_redist_region *rdreg;
-
- mutex_lock(&kvm->arch.config_lock);
- rdreg = vgic_v3_rdist_region_from_index(kvm, index);
- vgic_v3_free_redist_region(kvm, rdreg);
- mutex_unlock(&kvm->arch.config_lock);
+ vgic_v3_rollback_redist_region(kvm, index);
return ret;
}
diff --git a/arch/arm64/kvm/vgic/vgic.h b/arch/arm64/kvm/vgic/vgic.h
index f45f7e3ec4d6e..e32e2b56a47ef 100644
--- a/arch/arm64/kvm/vgic/vgic.h
+++ b/arch/arm64/kvm/vgic/vgic.h
@@ -333,6 +333,7 @@ int vgic_v3_save_pending_tables(struct kvm *kvm);
int vgic_v3_set_redist_base(struct kvm *kvm, u32 index, u64 addr, u32 count);
int vgic_register_redist_iodev(struct kvm_vcpu *vcpu);
void vgic_unregister_redist_iodev(struct kvm_vcpu *vcpu);
+void __vgic_unassign_redist_iodev(struct kvm_vcpu *vcpu);
bool vgic_v3_check_base(struct kvm *kvm);
void vgic_v3_load(struct kvm_vcpu *vcpu);
diff --git a/tools/testing/selftests/kvm/arm64/vgic_init.c b/tools/testing/selftests/kvm/arm64/vgic_init.c
index 47e34b43afb29..5a30f3cb039be 100644
--- a/tools/testing/selftests/kvm/arm64/vgic_init.c
+++ b/tools/testing/selftests/kvm/arm64/vgic_init.c
@@ -5,6 +5,7 @@
* Copyright (C) 2020, Red Hat, Inc.
*/
#include <linux/kernel.h>
+#include <linux/sizes.h>
#include <sys/syscall.h>
#include <asm/kvm.h>
#include <asm/kvm_para.h>
@@ -13,12 +14,21 @@
#include "test_util.h"
#include "kvm_util.h"
+#include "gic.h"
#include "processor.h"
#include "vgic.h"
#include "gic_v3.h"
#define NR_VCPUS 4
+#define REDIST_RETRY_REGION0_BASE GICR_BASE_GPA
+#define REDIST_RETRY_REGION1_BASE \
+ (REDIST_RETRY_REGION0_BASE + 2 * KVM_VGIC_V3_REDIST_SIZE)
+#define REDIST_RETRY_DIST_BASE \
+ (REDIST_RETRY_REGION1_BASE + KVM_VGIC_V3_REDIST_SIZE)
+#define REDIST_RETRY_REGION2_BASE \
+ (REDIST_RETRY_DIST_BASE + KVM_VGIC_V3_DIST_SIZE)
+
#define REG_OFFSET(vcpu, offset) (((u64)vcpu << 32) | offset)
#define VGIC_DEV_IS_V2(_d) ((_d) == KVM_DEV_TYPE_ARM_VGIC_V2)
@@ -65,6 +75,23 @@ static void guest_code(void)
GUEST_DONE();
}
+static void guest_check_redist_retry(void)
+{
+ unsigned int i;
+
+ /* The first three redistributors span adjacent regions 0 and 1. */
+ for (i = 0; i < NR_VCPUS; i++) {
+ u64 base = i < 3 ? REDIST_RETRY_REGION0_BASE +
+ i * KVM_VGIC_V3_REDIST_SIZE :
+ REDIST_RETRY_REGION2_BASE;
+ u64 typer = readq((void *)(unsigned long)(base + GICR_TYPER));
+
+ GUEST_ASSERT_EQ(GICR_TYPER_CPU_NUMBER(typer), i);
+ }
+
+ GUEST_DONE();
+}
+
/* we don't want to assert on run execution, hence that helper */
static int run_vcpu(struct kvm_vcpu *vcpu)
{
@@ -73,6 +100,7 @@ static int run_vcpu(struct kvm_vcpu *vcpu)
static struct vm_gic vm_gic_create_with_vcpus(u32 gic_dev_type,
u32 nr_vcpus,
+ void *guest_code,
struct kvm_vcpu *vcpus[])
{
struct vm_gic v;
@@ -338,7 +366,7 @@ static void test_vgic_then_vcpus(u32 gic_dev_type)
struct vm_gic v;
int ret, i;
- v = vm_gic_create_with_vcpus(gic_dev_type, 1, vcpus);
+ v = vm_gic_create_with_vcpus(gic_dev_type, 1, guest_code, vcpus);
subtest_dist_rdist(&v);
@@ -359,7 +387,8 @@ static void test_vcpus_then_vgic(u32 gic_dev_type)
struct vm_gic v;
int ret;
- v = vm_gic_create_with_vcpus(gic_dev_type, NR_VCPUS, vcpus);
+ v = vm_gic_create_with_vcpus(gic_dev_type, NR_VCPUS, guest_code,
+ vcpus);
subtest_dist_rdist(&v);
@@ -411,7 +440,8 @@ static void test_v3_new_redist_regions(void)
u64 addr;
int ret;
- v = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, NR_VCPUS, vcpus);
+ v = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, NR_VCPUS,
+ guest_code, vcpus);
subtest_v3_redist_regions(&v);
kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_CTRL,
KVM_DEV_ARM_VGIC_CTRL_INIT, NULL);
@@ -422,7 +452,8 @@ static void test_v3_new_redist_regions(void)
/* step2 */
- v = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, NR_VCPUS, vcpus);
+ v = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, NR_VCPUS,
+ guest_code, vcpus);
subtest_v3_redist_regions(&v);
addr = REDIST_REGION_ATTR_ADDR(1, 0x280000, 0, 2);
@@ -436,7 +467,8 @@ static void test_v3_new_redist_regions(void)
/* step 3 */
- v = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, NR_VCPUS, vcpus);
+ v = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, NR_VCPUS,
+ guest_code, vcpus);
subtest_v3_redist_regions(&v);
ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
@@ -457,6 +489,70 @@ static void test_v3_new_redist_regions(void)
vm_gic_destroy(&v);
}
+static void test_v3_redist_region_retry(void)
+{
+ struct kvm_vcpu *vcpus[NR_VCPUS];
+ struct vm_gic v;
+ struct ucall uc;
+ u64 addr;
+ int ret;
+
+ v = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, NR_VCPUS,
+ guest_check_redist_retry, vcpus);
+
+ addr = REDIST_REGION_ATTR_ADDR(2, REDIST_RETRY_REGION0_BASE, 0, 0);
+ kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
+ KVM_VGIC_V3_ADDR_TYPE_REDIST_REGION, &addr);
+
+ addr = REDIST_REGION_ATTR_ADDR(1, REDIST_RETRY_REGION1_BASE, 0, 1);
+ kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
+ KVM_VGIC_V3_ADDR_TYPE_REDIST_REGION, &addr);
+
+ addr = REDIST_RETRY_DIST_BASE;
+ kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
+ KVM_VGIC_V3_ADDR_TYPE_DIST, &addr);
+
+ addr = REDIST_REGION_ATTR_ADDR(1, REDIST_RETRY_DIST_BASE, 0, 2);
+ ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
+ KVM_VGIC_V3_ADDR_TYPE_REDIST_REGION,
+ &addr);
+ TEST_ASSERT(ret && errno == EINVAL,
+ "register redist region colliding with dist");
+
+ addr = REDIST_REGION_ATTR_ADDR(1, REDIST_RETRY_REGION2_BASE, 0, 2);
+ kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
+ KVM_VGIC_V3_ADDR_TYPE_REDIST_REGION, &addr);
+
+ virt_map(v.vm, REDIST_RETRY_REGION0_BASE, REDIST_RETRY_REGION0_BASE,
+ vm_calc_num_guest_pages(v.vm->mode,
+ 3 * KVM_VGIC_V3_REDIST_SIZE));
+ virt_map(v.vm, REDIST_RETRY_REGION2_BASE, REDIST_RETRY_REGION2_BASE,
+ vm_calc_num_guest_pages(v.vm->mode,
+ KVM_VGIC_V3_REDIST_SIZE));
+
+ kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_CTRL,
+ KVM_DEV_ARM_VGIC_CTRL_INIT, NULL);
+
+ vcpu_run(vcpus[0]);
+ switch (get_ucall(vcpus[0], &uc)) {
+ case UCALL_DONE:
+ break;
+ case UCALL_ABORT:
+ REPORT_GUEST_ASSERT(uc);
+ break;
+ case UCALL_NONE:
+ if (vcpus[0]->run->exit_reason == KVM_EXIT_MMIO)
+ TEST_FAIL("Unexpected MMIO exit at 0x%llx",
+ vcpus[0]->run->mmio.phys_addr);
+ fallthrough;
+ default:
+ TEST_FAIL("Unexpected ucall %lu, exit_reason %u",
+ uc.cmd, vcpus[0]->run->exit_reason);
+ }
+
+ vm_gic_destroy(&v);
+}
+
static void test_v3_typer_accesses(void)
{
struct vm_gic v;
@@ -608,7 +704,8 @@ static void test_v3_redist_ipa_range_check_at_vcpu_run(void)
int ret, i;
u64 addr;
- v = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, 1, vcpus);
+ v = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, 1, guest_code,
+ vcpus);
/* Set space for 3 redists, we have 1 vcpu, so this succeeds. */
addr = max_phys_size - (3 * 2 * 0x10000);
@@ -641,7 +738,8 @@ static void test_v3_its_region(void)
u64 addr;
int its_fd, ret;
- v = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, NR_VCPUS, vcpus);
+ v = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, NR_VCPUS,
+ guest_code, vcpus);
its_fd = kvm_create_device(v.vm, KVM_DEV_TYPE_ARM_VGIC_ITS);
addr = 0x401000;
@@ -684,7 +782,8 @@ static void test_v3_nassgicap(void)
u32 typer2;
int ret;
- vm = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, NR_VCPUS, vcpus);
+ vm = vm_gic_create_with_vcpus(KVM_DEV_TYPE_ARM_VGIC_V3, NR_VCPUS,
+ guest_code, vcpus);
kvm_device_attr_get(vm.gic_fd, KVM_DEV_ARM_VGIC_GRP_DIST_REGS,
GICD_TYPER2, &typer2);
has_nassgicap = typer2 & GICD_TYPER2_nASSGIcap;
@@ -978,6 +1077,7 @@ void run_tests(u32 gic_dev_type)
if (VGIC_DEV_IS_V3(gic_dev_type)) {
test_v3_new_redist_regions();
+ test_v3_redist_region_retry();
test_v3_typer_accesses();
test_v3_last_bit_redist_regions();
test_v3_last_bit_single_rdist();
|
| 13/3 | 2026/08/22 10:21 | llm | kmsan-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 9484 output: 208 thoughts: 1537 Thoughts: **KVM VGIC v3 Redistributor Initialization and Teardown - Focused Review** Okay, let's break this down. My initial assessment is that this patch is all about tightening up the VGIC v3 redistributor initialization and teardown procedures, specifically to handle scenarios where vCPU creation fails or setting the redistributor base fails. The key here is rollback. It seems the core problem is potentially dangling pointers or locking issues that could lead to crashes or corruption during these failure cases. My first thought was, are there any uninitialized memory vulnerabilities being introduced? A quick look at the modified functions suggests no, which is a relief. We're seeing modifications to `__kvm_vgic_vcpu_destroy`, `kvm_vgic_vcpu_destroy`, `kvm_vgic_destroy`, `kvm_vgic_addr`, `vgic_v3_init_dist_iodev`, `vgic_register_redist_iodev`, `__vgic_unassign_redist_iodev`, `vgic_v3_rollback_redist_region`, `vgic_v3_free_redist_region`, and `vgic_v3_set_redist_base`. It looks like the patch is adding `vgic_unregister_redist_iodev`, `__vgic_unassign_redist_iodev`, and `vgic_undo_redist_assignment` to ensure that resources are properly cleaned up in error paths. The new `kvm_vgic_addr` check looks like a simple concurrency check to prevent redistributor updates while vCPU creation is still in progress. After checking `kvm_vgic_addr`, the code seems sensible. The `vgic_undo_redist_assignment` and `__vgic_unassign_redist_iodev` functions are just resource cleanup, and `vgic_v3_rollback_redist_region` does cleanup across all vcpus. Basically, this looks like a defensive patch: it's making sure that in the event of an error during vCPU creation or redistributor setup, we properly release all resources and don't end up with dangling pointers, use-after-free, or lock inversion bugs. These types of bugs are typically detected by KASAN and LOCKDEP. The patch *isn't* adding any new structures that could have uninitialized memory problems. So, given this analysis, the patch looks clean from a KMSAN perspective, and I don't see any obvious new attack surfaces. This looks like a solid fix to increase the robustness and reliability of the VGIC v3 code. |
| 14/3 | 2026/08/22 10:22 | tool | set-results |
0mArgs:{
"KMSANReasoning": "The patch modifies KVM VGIC v3 redistributor initialization, teardown, and rollback logic. It introduces proper cleanup functions (`vgic_undo_redist_assignment`, `__vgic_unassign_redist_iodev`, `vgic_v3_rollback_redist_region`) and adjusts locking to prevent dangling pointers and lock inversions during vCPU creation failures or redistributor base setup failures. The changes are strictly related to resource management, concurrency, and object lifecycle. There are no new structures exposed to user space, no new complex allocations that could leak uninitialized memory, and no changes to bounds checking that would allow reading uninitialized bytes. Any bugs related to these changes (such as use-after-free, dangling pointers, or deadlocks) are well within the scope of KASAN and LOCKDEP. A dedicated KMSAN session is not justified.",
"NeedsKMSAN": false
}Results: {
"KMSANReasoning": "The patch modifies KVM VGIC v3 redistributor initialization, teardown, and rollback logic. It introduces proper cleanup functions (`vgic_undo_redist_assignment`, `__vgic_unassign_redist_iodev`, `vgic_v3_rollback_redist_region`) and adjusts locking to prevent dangling pointers and lock inversions during vCPU creation failures or redistributor base setup failures. The changes are strictly related to resource management, concurrency, and object lifecycle. There are no new structures exposed to user space, no new complex allocations that could leak uninitialized memory, and no changes to bounds checking that would allow reading uninitialized bytes. Any bugs related to these changes (such as use-after-free, dangling pointers, or deadlocks) are well within the scope of KASAN and LOCKDEP. A dedicated KMSAN session is not justified.",
"NeedsKMSAN": false
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|