From nobody Thu Sep 24 16:07:11 2026 Received: from mail-pj2-f13.google.com (mail-pj2-f13.google.com [74.125.227.141]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 5A3C35326B2 for ; Tue, 22 Sep 2026 10:16:47 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.227.141 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790072215; cv=none; b=Vpkfr96JoPsRggoyyjoNOFwS2eQzFJ0SBOIWXXvJA66Oh4VbAHa3Fl2ocrupcL1Yo27mBo9+rcNFW01n1gQF7NoYphl/OtCucEeyvPnMPN60pJMNehCLYOmiAEa0N9ZV0ux5rS32u/eTRjCw+o8CgGZYMkgls5zMndoKZMkCRMc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790072215; c=relaxed/simple; bh=hegXeIXRTRgEK0qzFVv3OsCH8UC5FDu3BXOdP7RcURQ=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=HDz4xUDbM60pipVB/3gy4leAMeJiu8F/M5PavJ6ibM4T0712b3yevZ3bxcbmwPwuzu2ShXjDrfagcFpJTzrBAJ/Yawg/gqLIGYHGuBBGxcNebCqZLCayoLbX/x1lgpecPUFYyFmz2fvmwy2XXP+Caspn4HQ3eiBDv8JRI5rl0hY= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=AIkBpbr0; arc=none smtp.client-ip=74.125.227.141 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="AIkBpbr0" Received: by mail-pj2-f13.google.com with SMTP id d9443c01a7336-2d8fdc579daso44000385ad.1 for ; Tue, 22 Sep 2026 03:16:45 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1790072200; x=1790677000; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=l6chsZ4b9REVXaCQX8gguiTmxYUwvB09uxReH1aMXZM=; b=AIkBpbr0Iw5tJ+zS0O8gnNeZtEzmYBVpvCFQh32II/pa384ceyyhhD8HWMqAuDN5HK KPmlHIefGe9ooG0Q2j2YBEHWPgOxjMBy0CM+fTBSOevm9Gnp8enCJuEDr7Vd18HWo+nm ozpJIQCwovQM+peoiEuyAm/0mbYl5ry9frMaNUNfERlteM9FH9w8o0gOLRQWTqMmdK2d wpSyKI1nOyZBJ3EePoHAl5iBDgB/Oq4p2LPVau+AI7nWw4Wu96toOGeODG68XwfCXv/r J4RYys1w8WNY6GEcDS+FggQCKVrPo5zVlxrX4H3kPSWdCoJWqBFlE/BSBZT7OygimIZA mn4A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1790072200; x=1790677000; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=l6chsZ4b9REVXaCQX8gguiTmxYUwvB09uxReH1aMXZM=; b=GBYie0RZoxUew9elgFfUbCfDt3fas3zkyI3kOE+RG/rkkfjPPvFEtH1Q0LoADNieum zNuZ/d5jU0FNP8bwHbyBkdAvxXhCxTZWzCk6i6287d5JOX51hVs0DXGnyXlj9Y3RuIWS JfoeM3wew0i/3QtZ8otf+E7lTVuTfPAqZhO/osKpIjSTlzVtqlu0FD+6cQZfN5ZQy3nR d/B57nYhTpM+spP4HaHh6XnhDK8s5gkL1zm6gAkw3DxNcs+d8VXOHK2wR4cSbLdt3GvO yEbafq/N1JFvJs9PgvspHN7cojr9TkJSy7gkREI1y134jRN1ffs/HLCUhvR8JY/I7WR6 MloQ== X-Forwarded-Encrypted: i=1; AKwUvBy/Kl2mYIqQkYxd2UqQUq6gxmSr0Hei+O48ozarg+Xf3dY2gJCCxDxEtcN43jd5KpFnekVJKVp5BFJvy4k=@vger.kernel.org X-Gm-Message-State: AFuF++ka7Enox1JoMHgxPD9D22s+jCeEZCdLEqh36bJi2EXc+DtPnUOR TzlyNu4hfsLE5K6O6W/X0vFDqaxVBtZ76CnRSgQny6Qr9NgpP4qpDryM X-Gm-Gg: AYBFou3mWT3Od5rIFAmWoD/TPSzYcN7AzpgSsDmL0SoD1/n9kmkkEfH+k2ZzEnqvD70 /HoYtjjU0F/U2HampMteBRtCFCoOwM4n0H2KiOh9aXm5oXYpqMjOuHunTnxi5tvuSo6vpBEdTxK KP0A8NBI4L6pcsTKASmJnzovMZVUxIDllWSLyPMvHVKpt/D6ILJw+HseW0nz1K6QT5Z36bnm46I 0vJ1Q9xGAZgiS+5xP00824cQdQX2qtoInikk/nkOFiC15oBs/S6eKxwrGxvf8mGCHYMjFJI6SiS XQXuSMmEPUUzuc87YaDaLsQKjvb9zmnuzSH0r2M6FKGpW4jJxiX/fS7XU1jrmlraJaQZfCFaLn3 8pHTFArloXpnwE/MVWiIz4EEGftrpPx1NpyR1sRJ8mw5M2ypLWCUj+7GPBLlndn4Cbff4YxStz7 DfnUUMKwTj6DXwZ0KktGA1uy8flLTDB8ljHXOQJKytiKXPhwe5yEMQgUnP8u7A2weQ/j/TOcdZn 4WGHXDYQDNr9XkXLNiDQH5RLHjwsh6FUwrbbQ== X-Received: by 2002:a17:902:ecc5:b0:2df:5a5b:5763 with SMTP id d9443c01a7336-2df609ac388mr8409685ad.10.1790072199985; Tue, 22 Sep 2026 03:16:39 -0700 (PDT) Received: from localhost.localdomain ([2409:8a1e:2e81:7320:4d0:75ae:9920:3973]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-2df5d0199c4sm7330805ad.29.2026.09.22.03.16.36 (version=TLS1_3 cipher=TLS_CHACHA20_POLY1305_SHA256 bits=256/256); Tue, 22 Sep 2026 03:16:39 -0700 (PDT) From: Yuchao Zhang To: Marc Zyngier Cc: Oliver Upton , Fuad Tabba , James Morse , Suzuki K Poulose , Zenghui Yu , Catalin Marinas , Will Deacon , kvmarm@lists.linux.dev, linux-arm-kernel@lists.infradead.org, linux-kernel@vger.kernel.org, Yuchao Zhang Subject: [PATCH] KVM: selftests: arm64: Add test for cross-vCPU LPI disable race Date: Tue, 22 Sep 2026 18:16:32 +0800 Message-ID: <20260922101632.39497-1-ndaugoing@gmail.com> X-Mailer: git-send-email 2.50.1 In-Reply-To: <87bj9r4h9z.wl-maz@kernel.org> References: <87bj9r4h9z.wl-maz@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Add a selftest that validates the behavior of remote LPI disabling while the target vCPU has in-flight/overflowing LPIs. The test configures an ITS with multiple LPIs targeting vCPU 0, which receives a continuous stream of MSIs forcing its List Registers to overflow into the ap_list. Concurrently, vCPU 1 repeatedly toggles GICR_CTLR.EnableLPIs on vCPU 0's redistributor. On unpatched kernels, this race can lead to a use-after-free or host kernel panic in vgic_fold_lr_state() due to a dangling last_lr_irq pointer. With the fix in place (stopping the VM and holding a refcount on last_lr_irq), the test runs to completion without errors. Signed-off-by: Yuchao Zhang --- tools/testing/selftests/kvm/Makefile.kvm | 1 + .../selftests/kvm/arm64/vgic_lpi_disable.c | 401 ++++++++++++++++++ 2 files changed, 402 insertions(+) create mode 100644 tools/testing/selftests/kvm/arm64/vgic_lpi_disable.c diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selft= ests/kvm/Makefile.kvm index 96bab7002d39..cf0baec3f6c7 100644 --- a/tools/testing/selftests/kvm/Makefile.kvm +++ b/tools/testing/selftests/kvm/Makefile.kvm @@ -190,6 +190,7 @@ TEST_GEN_PROGS_arm64 +=3D arm64/vcpu_width_config TEST_GEN_PROGS_arm64 +=3D arm64/vgic_init TEST_GEN_PROGS_arm64 +=3D arm64/vgic_irq TEST_GEN_PROGS_arm64 +=3D arm64/vgic_lpi_stress +TEST_GEN_PROGS_arm64 +=3D arm64/vgic_lpi_disable TEST_GEN_PROGS_arm64 +=3D arm64/vgic_v5 TEST_GEN_PROGS_arm64 +=3D arm64/vpmu_counter_access TEST_GEN_PROGS_arm64 +=3D arm64/no-vgic diff --git a/tools/testing/selftests/kvm/arm64/vgic_lpi_disable.c b/tools/t= esting/selftests/kvm/arm64/vgic_lpi_disable.c new file mode 100644 index 000000000000..078134919228 --- /dev/null +++ b/tools/testing/selftests/kvm/arm64/vgic_lpi_disable.c @@ -0,0 +1,401 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * vgic_lpi_disable - Test cross-vCPU LPI disabling race condition + * + * Copyright (c) 2026 Yuchao Zhang + * + * This test verifies that disabling LPIs from a remote vCPU while the + * target vCPU has in-flight/overflowing LPIs does not lead to use-after-f= ree + * or kernel panic. + */ + +#include +#include +#include +#include + +#include "kvm_util.h" +#include "delay.h" +#include "gic.h" +#include "gic_v3.h" +#include "gic_v3_its.h" +#include "processor.h" +#include "ucall.h" +#include "vgic.h" + +#define TEST_MEMSLOT_INDEX 1 +#define GIC_LPI_OFFSET 8192 + +#define TARGET_VCPU_ID 0 +#define DISABLER_VCPU_ID 1 + +static size_t nr_iterations =3D 200; +static gpa_t gpa_base; + +static struct kvm_vm *vm; +static struct kvm_vcpu **vcpus; +static int its_fd; + +static struct test_data { + bool request_vcpus_stop; + u32 nr_cpus; + u32 nr_devices; + u32 nr_event_ids; + + gpa_t device_table; + gpa_t collection_table; + gpa_t cmdq_base; + void *cmdq_base_va; + gpa_t itt_tables; + + gpa_t lpi_prop_table; + gpa_t lpi_pend_tables; +} test_data =3D { + .nr_cpus =3D 2, + .nr_devices =3D 1, + .nr_event_ids =3D 64, +}; + +static void guest_irq_handler(struct ex_regs *regs) +{ + u32 intid =3D gic_get_and_ack_irq(); + + if (intid =3D=3D IAR_SPURIOUS) + return; + + GUEST_ASSERT(intid >=3D GIC_LPI_OFFSET); + gic_set_eoi(intid); +} + +static void guest_setup_its_mappings(void) +{ + u32 device_id, event_id, intid =3D GIC_LPI_OFFSET; + u32 nr_events =3D test_data.nr_event_ids; + u32 nr_devices =3D test_data.nr_devices; + + /* Map collection 0 to TARGET_VCPU_ID */ + its_send_mapc_cmd(test_data.cmdq_base_va, TARGET_VCPU_ID, TARGET_VCPU_ID,= true); + + /* Map all LPIs to TARGET_VCPU_ID to force LR overflow */ + for (device_id =3D 0; device_id < nr_devices; device_id++) { + gpa_t itt_base =3D test_data.itt_tables + (device_id * SZ_64K); + + its_send_mapd_cmd(test_data.cmdq_base_va, device_id, + itt_base, SZ_64K, true); + + for (event_id =3D 0; event_id < nr_events; event_id++) { + its_send_mapti_cmd(test_data.cmdq_base_va, device_id, + event_id, TARGET_VCPU_ID, intid++); + } + } +} + +static void guest_invalidate_all_rdists(void) +{ + int i; + + for (i =3D 0; i < test_data.nr_cpus; i++) + its_send_invall_cmd(test_data.cmdq_base_va, i); +} + +static void guest_setup_gic(void) +{ + static atomic_int nr_cpus_ready; + u32 cpuid =3D guest_get_vcpuid(); + + gic_init(GIC_V3, test_data.nr_cpus); + gic_rdist_enable_lpis(test_data.lpi_prop_table, SZ_64K, + test_data.lpi_pend_tables + (cpuid * SZ_64K)); + + atomic_fetch_add(&nr_cpus_ready, 1); + + if (cpuid > 0) + return; + + while (atomic_load(&nr_cpus_ready) < test_data.nr_cpus) + cpu_relax(); + + its_init(test_data.collection_table, SZ_64K, + test_data.device_table, SZ_64K, + test_data.cmdq_base, SZ_64K); + + guest_setup_its_mappings(); + guest_invalidate_all_rdists(); + + /* SYNC to ensure ITS setup is complete */ + for (cpuid =3D 0; cpuid < test_data.nr_cpus; cpuid++) + its_send_sync_cmd(test_data.cmdq_base_va, cpuid); +} + +static inline void *test_gicr_base_cpu(u32 cpu) +{ + return (void *)(GICR_BASE_GPA + cpu * SZ_64K * 2); +} + +static void test_gicv3_gicr_wait_for_rwp(u32 cpu) +{ + unsigned int count =3D 100000; + + while (readl(test_gicr_base_cpu(cpu) + GICR_CTLR) & GICR_CTLR_RWP) { + GUEST_ASSERT(count--); + udelay(10); + } +} + +static void guest_code(size_t nr_lpis) +{ + u32 cpuid =3D guest_get_vcpuid(); + + guest_setup_gic(); + + if (cpuid =3D=3D TARGET_VCPU_ID) { + local_irq_enable(); + GUEST_SYNC(0); + + while (!READ_ONCE(test_data.request_vcpus_stop)) + cpu_relax(); + } else { + GUEST_SYNC(0); + + for (size_t i =3D 0; i < nr_iterations; i++) { + /* Remotely disable LPIs on target vCPU */ + writel(0, test_gicr_base_cpu(TARGET_VCPU_ID) + GICR_CTLR); + test_gicv3_gicr_wait_for_rwp(TARGET_VCPU_ID); + + for (int d =3D 0; d < 50; d++) + cpu_relax(); + + /* Remotely re-enable LPIs on target vCPU */ + writel(GICR_CTLR_ENABLE_LPIS, + test_gicr_base_cpu(TARGET_VCPU_ID) + GICR_CTLR); + test_gicv3_gicr_wait_for_rwp(TARGET_VCPU_ID); + } + + WRITE_ONCE(test_data.request_vcpus_stop, true); + } + + GUEST_DONE(); +} + +static void setup_memslot(void) +{ + size_t pages; + size_t sz; + + sz =3D (3 + test_data.nr_devices) * SZ_64K; + sz +=3D (1 + test_data.nr_cpus) * SZ_64K; + + pages =3D sz / vm->page_size; + gpa_base =3D ((vm_compute_max_gfn(vm) + 1) * vm->page_size) - sz; + vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS, gpa_base, + TEST_MEMSLOT_INDEX, pages, 0); +} + +#define LPI_PROP_DEFAULT_PRIO 0xa0 + +static void configure_lpis(void) +{ + size_t nr_lpis =3D test_data.nr_devices * test_data.nr_event_ids; + u8 *tbl =3D addr_gpa2hva(vm, test_data.lpi_prop_table); + size_t i; + + for (i =3D 0; i < nr_lpis; i++) { + tbl[i] =3D LPI_PROP_DEFAULT_PRIO | + LPI_PROP_GROUP1 | + LPI_PROP_ENABLED; + } +} + +static void setup_test_data(void) +{ + size_t pages_per_64k =3D vm_calc_num_guest_pages(vm->mode, SZ_64K); + u32 nr_devices =3D test_data.nr_devices; + u32 nr_cpus =3D test_data.nr_cpus; + gpa_t cmdq_base; + + test_data.device_table =3D vm_phy_pages_alloc(vm, pages_per_64k, + gpa_base, + TEST_MEMSLOT_INDEX); + + test_data.collection_table =3D vm_phy_pages_alloc(vm, pages_per_64k, + gpa_base, + TEST_MEMSLOT_INDEX); + + cmdq_base =3D vm_phy_pages_alloc(vm, pages_per_64k, gpa_base, + TEST_MEMSLOT_INDEX); + virt_map(vm, cmdq_base, cmdq_base, pages_per_64k); + test_data.cmdq_base =3D cmdq_base; + test_data.cmdq_base_va =3D (void *)cmdq_base; + + test_data.itt_tables =3D vm_phy_pages_alloc(vm, pages_per_64k * nr_device= s, + gpa_base, TEST_MEMSLOT_INDEX); + + test_data.lpi_prop_table =3D vm_phy_pages_alloc(vm, pages_per_64k, + gpa_base, TEST_MEMSLOT_INDEX); + configure_lpis(); + + test_data.lpi_pend_tables =3D vm_phy_pages_alloc(vm, pages_per_64k * nr_c= pus, + gpa_base, TEST_MEMSLOT_INDEX); + + sync_global_to_guest(vm, test_data); +} + +static void setup_gic(void) +{ + its_fd =3D vgic_its_setup(vm); +} + +static void signal_lpi(u32 device_id, u32 event_id) +{ + gpa_t db_addr =3D GITS_BASE_GPA + GITS_TRANSLATER; + + struct kvm_msi msi =3D { + .address_lo =3D db_addr, + .address_hi =3D db_addr >> 32, + .data =3D event_id, + .devid =3D device_id, + .flags =3D KVM_MSI_VALID_DEVID, + }; + + __vm_ioctl(vm, KVM_SIGNAL_MSI, &msi); +} + +static pthread_barrier_t test_setup_barrier; + +static atomic_bool stop_lpi_thread; + +static void *lpi_worker_thread(void *data) +{ + u32 device_id =3D (size_t)data; + u32 event_id; + + pthread_barrier_wait(&test_setup_barrier); + + while (!atomic_load(&stop_lpi_thread)) { + for (event_id =3D 0; event_id < test_data.nr_event_ids; event_id++) + signal_lpi(device_id, event_id); + usleep(100); + } + + return NULL; +} + +static void *vcpu_worker_thread(void *data) +{ + struct kvm_vcpu *vcpu =3D data; + struct ucall uc; + + while (true) { + vcpu_run(vcpu); + + switch (get_ucall(vcpu, &uc)) { + case UCALL_SYNC: + pthread_barrier_wait(&test_setup_barrier); + continue; + case UCALL_DONE: + if (vcpu =3D=3D vcpus[DISABLER_VCPU_ID]) { + atomic_store(&stop_lpi_thread, true); + write_guest_global(vm, test_data.request_vcpus_stop, true); + } + return NULL; + case UCALL_ABORT: + REPORT_GUEST_ASSERT(uc); + break; + default: + TEST_FAIL("Unknown ucall: %lu", uc.cmd); + } + } + + return NULL; +} + +static void run_test(void) +{ + pthread_t *vcpu_threads; + pthread_t lpi_thread; + u32 i; + + pthread_barrier_init(&test_setup_barrier, NULL, test_data.nr_cpus + 1); + + vcpu_threads =3D malloc(sizeof(pthread_t) * test_data.nr_cpus); + TEST_ASSERT(vcpu_threads, "Failed to allocate vcpu_threads"); + + for (i =3D 0; i < test_data.nr_cpus; i++) + pthread_create(&vcpu_threads[i], NULL, vcpu_worker_thread, vcpus[i]); + + pthread_create(&lpi_thread, NULL, lpi_worker_thread, (void *)(size_t)0); + + pthread_join(lpi_thread, NULL); + for (i =3D 0; i < test_data.nr_cpus; i++) + pthread_join(vcpu_threads[i], NULL); + + free(vcpu_threads); +} + +static void setup_vm(void) +{ + int i; + + vm =3D vm_create_with_vcpus(test_data.nr_cpus, guest_code, vcpus); + + vm_init_descriptor_tables(vm); + for (i =3D 0; i < test_data.nr_cpus; i++) + vcpu_init_descriptor_tables(vcpus[i]); + + vm_install_exception_handler(vm, VECTOR_IRQ_CURRENT, guest_irq_handler); + + setup_memslot(); + setup_gic(); + setup_test_data(); +} + +static void destroy_vm(void) +{ + close(its_fd); + kvm_vm_free(vm); +} + +static void help(const char *name) +{ + pr_info("Usage: %s [-i iterations] [-e event_ids]\n", name); + pr_info(" -i: number of iterations to toggle GICR_CTLR.EnableLPIs (defaul= t %lu)\n", + nr_iterations); + pr_info(" -e: number of event IDs/LPIs to inject (default %u)\n", + test_data.nr_event_ids); +} + +int main(int argc, char **argv) +{ + int opt; + + TEST_REQUIRE(kvm_supports_vgic_v3()); + + while ((opt =3D getopt(argc, argv, "i:e:h")) !=3D -1) { + switch (opt) { + case 'i': + nr_iterations =3D atoi_positive("iterations", optarg); + break; + case 'e': + test_data.nr_event_ids =3D atoi_positive("event_ids", optarg); + break; + case 'h': + default: + help(argv[0]); + exit(opt =3D=3D 'h' ? 0 : 1); + } + } + + vcpus =3D malloc(sizeof(struct kvm_vcpu *) * test_data.nr_cpus); + TEST_ASSERT(vcpus, "Failed to allocate vcpus array"); + + setup_vm(); + run_test(); + destroy_vm(); + + free(vcpus); + + pr_info("Completed %lu iterations of remote LPI disable successfully\n", + nr_iterations); + + return 0; +} --=20 2.53.0