* [PATCH v2 0/3] KVM: selftests: arm64: Make sea_to_user skip cleanly
@ 2026-08-18 11:29 Like Xu
2026-08-18 11:29 ` [PATCH v2 1/3] KVM: selftests: arm64: Fix EINJ handling in sea_to_user Like Xu
` (2 more replies)
0 siblings, 3 replies; 8+ messages in thread
From: Like Xu @ 2026-08-18 11:29 UTC (permalink / raw)
To: Marc Zyngier, Oliver Upton
Cc: jiaqiyan, kvmarm, Paolo Bonzini, kvm, linux-kernel
This series hardens the arm64 sea_to_user selftest so that it reports a
clean skip on hosts that cannot actually run it, instead of aborting or
silently passing.
The test drives a real recoverable memory UER through APEI EINJ and
expects the guest to consume the poison and exit to userspace with
KVM_EXIT_ARM_SEA. That relies on host and firmware support that is not
present everywhere, and each unmet dependency is now turned into a skip:
- EINJ injection is driven directly through debugfs, and an unusable
EINJ (not built, no firmware support, or not running as root) is
classified from errno and skipped rather than mishandled.
- Guest memory is backed by 1GB hugepages; with an empty pool the test
now skips up front instead of failing an mmap() with -ENOMEM.
- Some firmware only arms EINJ poison as part of the trigger step that
notrigger=1 skips, so nothing consumable is left for the guest to
read. That case is detected and skipped rather than reported as a
spurious KVM_EXIT_MMIO failure.
Tested on an arm64 host with CONFIG_ACPI_APEI_EINJ: on a platform whose
firmware places no consumable poison under notrigger=1 the test now
skips cleanly, and the earlier -ENOMEM and abort paths are gone.
v1 [1] was a single patch doing only the 1GB hugepage check. Following
Marc's review [2], it is expanded into a series that also fixes the EINJ
handling the test depends on.
Test Results:
Case 1 (echo 0 > /sys/kernel/mm/hugepages/hugepages-1048576kB/nr_hugepages):
# timeout set to 120
# selftests: kvm: sea_to_user
# Random seed: 0x6b8b4567
# 1..0 # SKIP - Requirement not met: get_free_hugepages(backing_page_size) >= VM_MEM_SIZE
ok 1 selftests: kvm: sea_to_user # SKIP
# 1 skipped test(s) detected. Consider enabling relevant config options to improve coverage.
# Totals: pass:0 fail:0 xfail:0 xpass:0 skip:1 error:0
Case 2 (rmmod einj):
# selftests: kvm: sea_to_user
# Random seed: 0x6b8b4567
# # Mapped 0x40000 pages: gva=0x80000000 to gpa=0xff80000000
# # Before EINJect: data=0xbaadcafe
# # EINJ_GVA=0x81234bad, einj_gpa=0xff81234bad, einj_hva=0xffff41234bad, einj_hpa=0x42c1234bad
# 1..0 # SKIP need CONFIG_ACPI_APEI_EINJ and firmware EINJ support
ok 1 selftests: kvm: sea_to_user # SKIP
# 1 skipped test(s) detected. Consider enabling relevant config options to improve coverage.
# Totals: pass:0 fail:0 xfail:0 xpass:0 skip:1 error:0
Case 3 (modprobe einj):
# selftests: kvm: sea_to_user
# Random seed: 0x6b8b4567
# # Mapped 0x40000 pages: gva=0x80000000 to gpa=0xff80000000
# # Before EINJect: data=0xbaadcafe
# # EINJ_GVA=0x81234bad, einj_gpa=0xff81234bad, einj_hva=0xffff41234bad, einj_hpa=0x42c1234bad
# # 0x10 > /sys/kernel/debug/apei/einj/error_type - done
# # 0x2 > /sys/kernel/debug/apei/einj/flags - done
# # 0x42c1234bad > /sys/kernel/debug/apei/einj/param1 - done
# # 0xffffffffffffffff > /sys/kernel/debug/apei/einj/param2 - done
# # 0x1 > /sys/kernel/debug/apei/einj/notrigger - done
# # 0x1 > /sys/kernel/debug/apei/einj/error_inject - done
# # Memory UER EINJected
# # SIGBUS (7) received, dumping siginfo...
# # si_signo=7, si_errno=0, si_code=128, si_addr=(nil)
# not ok 1 Exit with signal unhandled
ok 1 selftests: kvm: sea_to_user
# Totals: pass:1 fail:0 xfail:0 xpass:0 skip:0 error:0
Changes since v1:
- New patch 1: drive EINJ injection directly through debugfs with
open()/write() instead of access()+popen(). Classify the open()
errno so an unprivileged run skips as "requires root" instead of
falsely reporting missing firmware EINJ, and report a genuine write
failure instead of the ambiguous "Failed... Success (0)".
- New patch 3: detect when notrigger=1 leaves no consumable poison and
skip cleanly, instead of exiting with a confusing KVM_EXIT_MMIO.
- Patch 2 is the original v1 change, unchanged.
Not yet addressed from [2]: dropping the hard 1GB-hugepage and 4kB
base-page requirements. Suggestions on an acceptable backing scheme are
welcome.
[1] https://lore.kernel.org/kvm/20260817080759.25601-1-likexu@tencent.com/
[2] https://lore.kernel.org/all/86y0le9cvz.wl-maz@kernel.org/
Like Xu (3):
KVM: selftests: arm64: Fix EINJ handling in sea_to_user
KVM: selftests: arm64: Skip sea_to_user without 1GB hugepages
KVM: selftests: arm64: Skip sea_to_user when EINJ places no poison
.../testing/selftests/kvm/arm64/sea_to_user.c | 75 +++++++++++++++----
.../testing/selftests/kvm/include/test_util.h | 1 +
tools/testing/selftests/kvm/lib/test_util.c | 15 ++++
3 files changed, 77 insertions(+), 14 deletions(-)
--
2.50.1 (Apple Git-155)
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH v2 1/3] KVM: selftests: arm64: Fix EINJ handling in sea_to_user
2026-08-18 11:29 [PATCH v2 0/3] KVM: selftests: arm64: Make sea_to_user skip cleanly Like Xu
@ 2026-08-18 11:29 ` Like Xu
2026-08-18 11:29 ` [PATCH v2 2/3] KVM: selftests: arm64: Skip sea_to_user without 1GB hugepages Like Xu
2026-08-18 11:29 ` [PATCH v2 3/3] KVM: selftests: arm64: Skip sea_to_user when EINJ places no poison Like Xu
2 siblings, 0 replies; 8+ messages in thread
From: Like Xu @ 2026-08-18 11:29 UTC (permalink / raw)
To: Marc Zyngier, Oliver Upton
Cc: jiaqiyan, kvmarm, Paolo Bonzini, kvm, linux-kernel
sea_to_user drives EINJ injection through popen("echo ... > file"). A
failed write is hidden behind the shell pipeline, so a botched injection
is silently ignored and the test proceeds as if a poison were placed.
Drive the debugfs interface directly so a write failure is reported
rather than swallowed.
The EINJ support probe is also unreliable. It checks the ACPI EINJ table
with access(R_OK), but reading that table needs CAP_SYS_ADMIN, so the
check gives a false negative for unprivileged runs and skips a test that
could otherwise report a real problem. Probe by opening the debugfs
injection interface instead and classify errno: absence means EINJ is
not built or firmware lacks support, EACCES/EPERM means the test is not
running as root -- both are skips -- while anything else is a genuine
failure.
The injection path is only reachable through debugfs; the kernel EINJ
driver exposes no other interface, so the test must depend on it.
Link: https://lore.kernel.org/kvm/86y0le9cvz.wl-maz@kernel.org/
Signed-off-by: Like Xu <likexu@tencent.com>
---
.../testing/selftests/kvm/arm64/sea_to_user.c | 45 ++++++++++++++-----
1 file changed, 33 insertions(+), 12 deletions(-)
diff --git a/tools/testing/selftests/kvm/arm64/sea_to_user.c b/tools/testing/selftests/kvm/arm64/sea_to_user.c
index e96d8982c28b8..1c2a743ca8e23 100644
--- a/tools/testing/selftests/kvm/arm64/sea_to_user.c
+++ b/tools/testing/selftests/kvm/arm64/sea_to_user.c
@@ -81,25 +81,46 @@ static u64 translate_hva_to_hpa(unsigned long hva)
static void write_einj_entry(const char *einj_path, u64 val)
{
- char cmd[256] = {0};
- FILE *cmdfile = NULL;
+ char buf[32];
+ int fd, len, ret;
- sprintf(cmd, "echo %#lx > %s", val, einj_path);
- cmdfile = popen(cmd, "r");
+ fd = open(einj_path, O_WRONLY);
+ if (fd < 0)
+ ksft_exit_fail_perror(einj_path);
- if (pclose(cmdfile) == 0)
- ksft_print_msg("echo %#lx > %s - done\n", val, einj_path);
- else
- ksft_exit_fail_perror("Failed to write EINJ entry");
+ len = snprintf(buf, sizeof(buf), "%#lx\n", val);
+ ret = write(fd, buf, len);
+ if (ret != len)
+ ksft_exit_fail_perror(einj_path);
+
+ close(fd);
+ ksft_print_msg("%#lx > %s - done\n", val, einj_path);
}
static void inject_uer(u64 hpa)
{
- if (access("/sys/firmware/acpi/tables/EINJ", R_OK) == -1)
- ksft_test_result_skip("EINJ table no available in firmware");
+ int fd;
- if (access(EINJ_ETYPE, R_OK | W_OK) == -1)
- ksft_test_result_skip("EINJ module probably not loaded?");
+ /*
+ * EINJ is exposed only through debugfs, and opening it tells us why it
+ * is unusable far more reliably than access()-ing the ACPI EINJ table:
+ * reading that table needs CAP_SYS_ADMIN, so access() gives a false
+ * negative for unprivileged runs. Open the injection interface and let
+ * errno say whether the test cannot run (skip) or genuinely failed.
+ */
+ fd = open(EINJ_ETYPE, O_WRONLY);
+ if (fd < 0) {
+ switch (errno) {
+ case ENOENT:
+ ksft_exit_skip("need CONFIG_ACPI_APEI_EINJ and firmware EINJ support\n");
+ case EACCES:
+ case EPERM:
+ ksft_exit_skip("EINJ requires running as root\n");
+ default:
+ ksft_exit_fail_perror(EINJ_ETYPE);
+ }
+ }
+ close(fd);
write_einj_entry(EINJ_ETYPE, ERROR_TYPE_MEMORY_UER);
write_einj_entry(EINJ_FLAGS, MASK_MEMORY_UER);
--
2.50.1 (Apple Git-155)
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH v2 2/3] KVM: selftests: arm64: Skip sea_to_user without 1GB hugepages
2026-08-18 11:29 [PATCH v2 0/3] KVM: selftests: arm64: Make sea_to_user skip cleanly Like Xu
2026-08-18 11:29 ` [PATCH v2 1/3] KVM: selftests: arm64: Fix EINJ handling in sea_to_user Like Xu
@ 2026-08-18 11:29 ` Like Xu
2026-08-18 11:36 ` sashiko-bot
2026-08-18 11:29 ` [PATCH v2 3/3] KVM: selftests: arm64: Skip sea_to_user when EINJ places no poison Like Xu
2 siblings, 1 reply; 8+ messages in thread
From: Like Xu @ 2026-08-18 11:29 UTC (permalink / raw)
To: Marc Zyngier, Oliver Upton
Cc: jiaqiyan, kvmarm, Paolo Bonzini, kvm, linux-kernel
sea_to_user backs guest memory with 1GB hugepages but never checks that
any are reserved. On a host with an empty pool it aborts instead of
skipping:
kvm_syscalls.h:68: mem != MAP_FAILED, errno=12 (ENOMEM)
not ok 1 selftests: kvm: sea_to_user # exit=254
The mmap() of the hugetlb region fails with -ENOMEM because no 1GB pages
are available, and the test treats that as a hard failure even though it
simply cannot run without the backing pages. Check the free 1GB hugepage
count up front and skip cleanly when the host cannot satisfy the region.
Signed-off-by: Like Xu <likexu@tencent.com>
---
tools/testing/selftests/kvm/arm64/sea_to_user.c | 6 ++++++
tools/testing/selftests/kvm/include/test_util.h | 1 +
tools/testing/selftests/kvm/lib/test_util.c | 15 +++++++++++++++
3 files changed, 22 insertions(+)
diff --git a/tools/testing/selftests/kvm/arm64/sea_to_user.c b/tools/testing/selftests/kvm/arm64/sea_to_user.c
index 1c2a743ca8e23..7cf95da8e594d 100644
--- a/tools/testing/selftests/kvm/arm64/sea_to_user.c
+++ b/tools/testing/selftests/kvm/arm64/sea_to_user.c
@@ -281,6 +281,12 @@ static struct kvm_vm *vm_create_with_sea_handler(struct kvm_vcpu **vcpu)
alignment = max(backing_page_size, guest_page_size);
num_guest_pages = VM_MEM_SIZE / guest_page_size;
+ /*
+ * The region is backed by 1GB hugepages; skip gracefully rather than
+ * failing with mmap() -ENOMEM if the host has none reserved.
+ */
+ TEST_REQUIRE(get_free_hugepages(backing_page_size) >= VM_MEM_SIZE);
+
vm = __vm_create_with_one_vcpu(vcpu, num_guest_pages, guest_code);
vm_init_descriptor_tables(vm);
vcpu_init_descriptor_tables(*vcpu);
diff --git a/tools/testing/selftests/kvm/include/test_util.h b/tools/testing/selftests/kvm/include/test_util.h
index a56271c237ae9..0624922c2735d 100644
--- a/tools/testing/selftests/kvm/include/test_util.h
+++ b/tools/testing/selftests/kvm/include/test_util.h
@@ -168,6 +168,7 @@ struct vm_mem_backing_src_alias {
bool thp_configured(void);
size_t get_trans_hugepagesz(void);
size_t get_def_hugetlb_pagesz(void);
+size_t get_free_hugepages(size_t page_size);
const struct vm_mem_backing_src_alias *vm_mem_backing_src_alias(u32 i);
size_t get_backing_src_pagesz(u32 i);
bool is_backing_src_hugetlb(u32 i);
diff --git a/tools/testing/selftests/kvm/lib/test_util.c b/tools/testing/selftests/kvm/lib/test_util.c
index bab1bd2b775b6..29f9c1d60b1b7 100644
--- a/tools/testing/selftests/kvm/lib/test_util.c
+++ b/tools/testing/selftests/kvm/lib/test_util.c
@@ -222,6 +222,21 @@ size_t get_def_hugetlb_pagesz(void)
TEST_FAIL("Error in reading /proc/meminfo");
}
+size_t get_free_hugepages(size_t page_size)
+{
+ char path[128];
+ size_t free;
+
+ snprintf(path, sizeof(path),
+ "/sys/kernel/mm/hugepages/hugepages-%zukB/free_hugepages",
+ page_size >> 10);
+ if (!test_sysfs_path(path))
+ return 0;
+
+ free = get_sysfs_val(path);
+ return free * page_size;
+}
+
#define ANON_FLAGS (MAP_PRIVATE | MAP_ANONYMOUS)
#define ANON_HUGE_FLAGS (ANON_FLAGS | MAP_HUGETLB)
--
2.50.1 (Apple Git-155)
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH v2 3/3] KVM: selftests: arm64: Skip sea_to_user when EINJ places no poison
2026-08-18 11:29 [PATCH v2 0/3] KVM: selftests: arm64: Make sea_to_user skip cleanly Like Xu
2026-08-18 11:29 ` [PATCH v2 1/3] KVM: selftests: arm64: Fix EINJ handling in sea_to_user Like Xu
2026-08-18 11:29 ` [PATCH v2 2/3] KVM: selftests: arm64: Skip sea_to_user without 1GB hugepages Like Xu
@ 2026-08-18 11:29 ` Like Xu
2026-08-18 11:41 ` sashiko-bot
2 siblings, 1 reply; 8+ messages in thread
From: Like Xu @ 2026-08-18 11:29 UTC (permalink / raw)
To: Marc Zyngier, Oliver Upton
Cc: jiaqiyan, kvmarm, Paolo Bonzini, kvm, linux-kernel
sea_to_user injects a memory UER with EINJ notrigger=1 and expects the
guest to consume it and trap to KVM as an SEA. On some firmware the test
aborts instead:
arm64/sea_to_user.c:223: exit_reason == (41)
Wanted KVM exit reason: 41 (ARM_SEA), got: 6 (MMIO)
notrigger=1 asks firmware to arm the poison without consuming it, but on
these platforms the poison is only armed as part of the trigger step that
notrigger skips, so nothing consumable is left in memory. The guest reads
back the sentinel, no SEA occurs, and GUEST_FAIL fires. On arm64 a ucall
is delivered as an MMIO write, which surfaces as the KVM_EXIT_MMIO above
rather than a KVM bug.
The file already documents that the test must be skipped when firmware
cannot deliver a consumable error. Detect the guest abort that follows a
missing SEA and skip, instead of failing on a firmware limitation the
test cannot control.
Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Like Xu <likexu@tencent.com>
---
.../testing/selftests/kvm/arm64/sea_to_user.c | 24 +++++++++++++++++--
1 file changed, 22 insertions(+), 2 deletions(-)
diff --git a/tools/testing/selftests/kvm/arm64/sea_to_user.c b/tools/testing/selftests/kvm/arm64/sea_to_user.c
index 7cf95da8e594d..d4af6e0eed288 100644
--- a/tools/testing/selftests/kvm/arm64/sea_to_user.c
+++ b/tools/testing/selftests/kvm/arm64/sea_to_user.c
@@ -215,13 +215,33 @@ static void run_vm(struct kvm_vm *vm, struct kvm_vcpu *vcpu)
ksft_print_msg("Dump kvm_run info about KVM_EXIT_%s\n",
exit_reason_str(run->exit_reason));
+
+ /*
+ * The guest's read of the injected location is expected to trap to KVM
+ * as an SEA. If it does not, the injected error was never placed as
+ * consumable poison: some firmware honours EINJ's notrigger request by
+ * arming the poison only as part of the (now skipped) trigger step, so
+ * nothing is left in memory for the guest to consume. The guest then
+ * reads back the sentinel value and reports it via GUEST_FAIL, which
+ * arm64 delivers as a ucall over MMIO (hence a KVM_EXIT_MMIO here).
+ * Treat that as "this platform cannot host the test" and skip, matching
+ * the requirement documented at the top of this file, rather than
+ * failing on a hardware/firmware limitation the test cannot control.
+ */
+ if (run->exit_reason != KVM_EXIT_ARM_SEA &&
+ get_ucall(vcpu, &uc) == UCALL_ABORT) {
+ ksft_print_msg("Guest consumed no SEA: %s", uc.buffer);
+ ksft_exit_skip("EINJ notrigger placed no consumable poison on this platform\n");
+ }
+
+ TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_ARM_SEA);
+
+ /* arm_sea holds valid data only for a KVM_EXIT_ARM_SEA exit. */
ksft_print_msg("kvm_run.arm_sea: esr=%#llx, flags=%#llx\n",
run->arm_sea.esr, run->arm_sea.flags);
ksft_print_msg("kvm_run.arm_sea: gva=%#llx, gpa=%#llx\n",
run->arm_sea.gva, run->arm_sea.gpa);
- TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_ARM_SEA);
-
esr = run->arm_sea.esr;
TEST_ASSERT_EQ(ESR_ELx_EC(esr), ESR_ELx_EC_DABT_LOW);
TEST_ASSERT_EQ(esr & ESR_ELx_FSC_TYPE, ESR_ELx_FSC_EXTABT);
--
2.50.1 (Apple Git-155)
^ permalink raw reply related [flat|nested] 8+ messages in thread
* Re: [PATCH v2 2/3] KVM: selftests: arm64: Skip sea_to_user without 1GB hugepages
2026-08-18 11:29 ` [PATCH v2 2/3] KVM: selftests: arm64: Skip sea_to_user without 1GB hugepages Like Xu
@ 2026-08-18 11:36 ` sashiko-bot
2026-08-18 12:49 ` Like Xu
0 siblings, 1 reply; 8+ messages in thread
From: sashiko-bot @ 2026-08-18 11:36 UTC (permalink / raw)
To: Like Xu; +Cc: kvmarm, Oliver Upton, kvm, Marc Zyngier
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] get_free_hugepages() overestimates available hugepages by ignoring reserved pages, which allows the test to proceed and crash with ENOMEM on systems with reserved pools.
--
--- Patch [2]: [PATCH v2 2/3] KVM: selftests: arm64: Skip sea_to_user without 1GB hugepages ---
commit 5fa5666824342b3dd28a5ef6a04bb5ac183257c8
Author: Like Xu <like.xu.linux@gmail.com>
KVM: selftests: arm64: Skip sea_to_user without 1GB hugepages
This commit adds a check to skip the sea_to_user test if the host does not
have enough 1GB hugepages available, preventing a hard failure on systems
without an empty pool.
[ ... ]
> diff --git a/tools/testing/selftests/kvm/lib/test_util.c b/tools/testing/selftests/kvm/lib/test_util.c
> index bab1bd2b775b6..29f9c1d60b1b7 100644
> --- a/tools/testing/selftests/kvm/lib/test_util.c
> +++ b/tools/testing/selftests/kvm/lib/test_util.c
> @@ -222,6 +222,21 @@ size_t get_def_hugetlb_pagesz(void)
> TEST_FAIL("Error in reading /proc/meminfo");
> }
>
> +size_t get_free_hugepages(size_t page_size)
> +{
> + char path[128];
> + size_t free;
> +
> + snprintf(path, sizeof(path),
> + "/sys/kernel/mm/hugepages/hugepages-%zukB/free_hugepages",
> + page_size >> 10);
> + if (!test_sysfs_path(path))
> + return 0;
> +
> + free = get_sysfs_val(path);
> + return free * page_size;
[Severity: Medium]
Does this calculation overestimate the available hugepages by ignoring the
reserved pool?
The sysfs free_hugepages value includes pages that are already committed to
other mappings but not yet allocated (which are tracked via resv_hugepages).
If the test runs on a host where free_hugepages meets the VM_MEM_SIZE
requirement but some or all of those pages are reserved, the test will
incorrectly proceed and still crash with an -ENOMEM abort instead of cleanly
skipping.
Could we also read and subtract resv_hugepages to determine the actual
allocatable count?
> +}
> +
> #define ANON_FLAGS (MAP_PRIVATE | MAP_ANONYMOUS)
> #define ANON_HUGE_FLAGS (ANON_FLAGS | MAP_HUGETLB)
>
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260818112920.26252-1-likexu@tencent.com?part=2
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v2 3/3] KVM: selftests: arm64: Skip sea_to_user when EINJ places no poison
2026-08-18 11:29 ` [PATCH v2 3/3] KVM: selftests: arm64: Skip sea_to_user when EINJ places no poison Like Xu
@ 2026-08-18 11:41 ` sashiko-bot
2026-08-18 12:50 ` Like Xu
0 siblings, 1 reply; 8+ messages in thread
From: sashiko-bot @ 2026-08-18 11:41 UTC (permalink / raw)
To: Like Xu; +Cc: Oliver Upton, kvm, kvmarm, Marc Zyngier
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] The check skips the test unconditionally on any guest assertion failure, masking unrelated or future bugs.
--
--- Patch [3]: [PATCH v2 3/3] KVM: selftests: arm64: Skip sea_to_user when EINJ places no poison ---
commit 3bedaa634d562c6cc85fb3726d30fa40f0ed350d
Author: Like Xu <like.xu.linux@gmail.com>
KVM: selftests: arm64: Skip sea_to_user when EINJ places no poison
This commit modifies the sea_to_user selftest to skip rather than fail
when the firmware does not leave consumable poison in memory. It detects
this condition by checking if the exit reason is not KVM_EXIT_ARM_SEA
and a UCALL_ABORT is received from the guest.
> diff --git a/tools/testing/selftests/kvm/arm64/sea_to_user.c b/tools/testing/selftests/kvm/arm64/sea_to_user.c
> index 7cf95da8e594d..d4af6e0eed288 100644
> --- a/tools/testing/selftests/kvm/arm64/sea_to_user.c
> +++ b/tools/testing/selftests/kvm/arm64/sea_to_user.c
> @@ -215,13 +215,33 @@ static void run_vm(struct kvm_vm *vm, struct kvm_vcpu *vcpu)
>
> ksft_print_msg("Dump kvm_run info about KVM_EXIT_%s\n",
> exit_reason_str(run->exit_reason));
> +
> + /*
> + * The guest's read of the injected location is expected to trap to KVM
> + * as an SEA. If it does not, the injected error was never placed as
> + * consumable poison: some firmware honours EINJ's notrigger request by
> + * arming the poison only as part of the (now skipped) trigger step, so
> + * nothing is left in memory for the guest to consume. The guest then
> + * reads back the sentinel value and reports it via GUEST_FAIL, which
> + * arm64 delivers as a ucall over MMIO (hence a KVM_EXIT_MMIO here).
> + * Treat that as "this platform cannot host the test" and skip, matching
> + * the requirement documented at the top of this file, rather than
> + * failing on a hardware/firmware limitation the test cannot control.
> + */
> + if (run->exit_reason != KVM_EXIT_ARM_SEA &&
> + get_ucall(vcpu, &uc) == UCALL_ABORT) {
[Severity: Medium]
Does this unconditionally skip the test on any guest assertion failure,
masking unrelated or future bugs?
If KVM fails to exit to userspace and instead incorrectly injects a
Data Abort to the guest with an unexpected syndrome, the guest's SEA
handler might fail a GUEST_ASSERT_EQ check. This would emit a
UCALL_ABORT over MMIO.
The host loop here interprets any UCALL_ABORT during this phase as a
missing EINJ poison limitation, skipping the test instead of reporting
the failure.
Can we check the specific assertion failure reason or uc.buffer string
instead of catching all UCALL_ABORTs?
> + ksft_print_msg("Guest consumed no SEA: %s", uc.buffer);
> + ksft_exit_skip("EINJ notrigger placed no consumable poison on this platform\n");
> + }
> +
> + TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_ARM_SEA);
> +
> + /* arm_sea holds valid data only for a KVM_EXIT_ARM_SEA exit. */
> ksft_print_msg("kvm_run.arm_sea: esr=%#llx, flags=%#llx\n",
> run->arm_sea.esr, run->arm_sea.flags);
> ksft_print_msg("kvm_run.arm_sea: gva=%#llx, gpa=%#llx\n",
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260818112920.26252-1-likexu@tencent.com?part=3
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v2 2/3] KVM: selftests: arm64: Skip sea_to_user without 1GB hugepages
2026-08-18 11:36 ` sashiko-bot
@ 2026-08-18 12:49 ` Like Xu
0 siblings, 0 replies; 8+ messages in thread
From: Like Xu @ 2026-08-18 12:49 UTC (permalink / raw)
To: Marc Zyngier; +Cc: kvmarm, Oliver Upton, kvm, sashiko-reviews
On 8/18/26 7:36 PM, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
> - [Medium] get_free_hugepages() overestimates available hugepages by ignoring reserved pages, which allows the test to proceed and crash with ENOMEM on systems with reserved pools.
> --
>
> --- Patch [2]: [PATCH v2 2/3] KVM: selftests: arm64: Skip sea_to_user without 1GB hugepages ---
> commit 5fa5666824342b3dd28a5ef6a04bb5ac183257c8
> Author: Like Xu <like.xu.linux@gmail.com>
>
> KVM: selftests: arm64: Skip sea_to_user without 1GB hugepages
>
> This commit adds a check to skip the sea_to_user test if the host does not
> have enough 1GB hugepages available, preventing a hard failure on systems
> without an empty pool.
>
> [ ... ]
>> diff --git a/tools/testing/selftests/kvm/lib/test_util.c b/tools/testing/selftests/kvm/lib/test_util.c
>> index bab1bd2b775b6..29f9c1d60b1b7 100644
>> --- a/tools/testing/selftests/kvm/lib/test_util.c
>> +++ b/tools/testing/selftests/kvm/lib/test_util.c
>> @@ -222,6 +222,21 @@ size_t get_def_hugetlb_pagesz(void)
>> TEST_FAIL("Error in reading /proc/meminfo");
>> }
>>
>> +size_t get_free_hugepages(size_t page_size)
>> +{
>> + char path[128];
>> + size_t free;
>> +
>> + snprintf(path, sizeof(path),
>> + "/sys/kernel/mm/hugepages/hugepages-%zukB/free_hugepages",
>> + page_size >> 10);
>> + if (!test_sysfs_path(path))
>> + return 0;
>> +
>> + free = get_sysfs_val(path);
>> + return free * page_size;
>
> [Severity: Medium]
> Does this calculation overestimate the available hugepages by ignoring the
> reserved pool?
>
> The sysfs free_hugepages value includes pages that are already committed to
> other mappings but not yet allocated (which are tracked via resv_hugepages).
>
> If the test runs on a host where free_hugepages meets the VM_MEM_SIZE
> requirement but some or all of those pages are reserved, the test will
> incorrectly proceed and still crash with an -ENOMEM abort instead of cleanly
> skipping.
>
> Could we also read and subtract resv_hugepages to determine the actual
> allocatable count?
For any human to double check:
diff --git a/tools/testing/selftests/kvm/arm64/sea_to_user.c
b/tools/testing/selftests/kvm/arm64/sea_to_user.c
index 1c2a743ca8e23..7cf95da8e594d 100644
--- a/tools/testing/selftests/kvm/arm64/sea_to_user.c
+++ b/tools/testing/selftests/kvm/arm64/sea_to_user.c
@@ -281,6 +281,12 @@ static struct kvm_vm
*vm_create_with_sea_handler(struct kvm_vcpu **vcpu)
alignment = max(backing_page_size, guest_page_size);
num_guest_pages = VM_MEM_SIZE / guest_page_size;
+ /*
+ * The region is backed by 1GB hugepages; skip gracefully rather than
+ * failing with mmap() -ENOMEM if the host has none reserved.
+ */
+ TEST_REQUIRE(get_free_hugepages(backing_page_size) >= VM_MEM_SIZE);
+
vm = __vm_create_with_one_vcpu(vcpu, num_guest_pages, guest_code);
vm_init_descriptor_tables(vm);
vcpu_init_descriptor_tables(*vcpu);
diff --git a/tools/testing/selftests/kvm/include/test_util.h
b/tools/testing/selftests/kvm/include/test_util.h
index a56271c237ae9..0624922c2735d 100644
--- a/tools/testing/selftests/kvm/include/test_util.h
+++ b/tools/testing/selftests/kvm/include/test_util.h
@@ -168,6 +168,7 @@ struct vm_mem_backing_src_alias {
bool thp_configured(void);
size_t get_trans_hugepagesz(void);
size_t get_def_hugetlb_pagesz(void);
+size_t get_free_hugepages(size_t page_size);
const struct vm_mem_backing_src_alias *vm_mem_backing_src_alias(u32 i);
size_t get_backing_src_pagesz(u32 i);
bool is_backing_src_hugetlb(u32 i);
diff --git a/tools/testing/selftests/kvm/lib/test_util.c
b/tools/testing/selftests/kvm/lib/test_util.c
index bab1bd2b775b6..3bff9d825e8af 100644
--- a/tools/testing/selftests/kvm/lib/test_util.c
+++ b/tools/testing/selftests/kvm/lib/test_util.c
@@ -222,6 +222,32 @@ size_t get_def_hugetlb_pagesz(void)
TEST_FAIL("Error in reading /proc/meminfo");
}
+size_t get_free_hugepages(size_t page_size)
+{
+ char path[128];
+ size_t free, resv;
+
+ snprintf(path, sizeof(path),
+ "/sys/kernel/mm/hugepages/hugepages-%zukB/free_hugepages",
+ page_size >> 10);
+ if (!test_sysfs_path(path))
+ return 0;
+ free = get_sysfs_val(path);
+
+ /*
+ * free_hugepages still counts pages that are reserved by existing
+ * mappings but not yet faulted in, so only free minus resv can back a
+ * new reservation (see available_huge_pages() in mm/hugetlb.c). Ignore
+ * surplus/overcommit: it does not apply to gigantic pages at runtime.
+ */
+ snprintf(path, sizeof(path),
+ "/sys/kernel/mm/hugepages/hugepages-%zukB/resv_hugepages",
+ page_size >> 10);
+ resv = test_sysfs_path(path) ? get_sysfs_val(path) : 0;
+
+ return free > resv ? (free - resv) * page_size : 0;
+}
+
#define ANON_FLAGS (MAP_PRIVATE | MAP_ANONYMOUS)
#define ANON_HUGE_FLAGS (ANON_FLAGS | MAP_HUGETLB)
>
>> +}
>> +
>> #define ANON_FLAGS (MAP_PRIVATE | MAP_ANONYMOUS)
>> #define ANON_HUGE_FLAGS (ANON_FLAGS | MAP_HUGETLB)
>>
>
^ permalink raw reply related [flat|nested] 8+ messages in thread
* Re: [PATCH v2 3/3] KVM: selftests: arm64: Skip sea_to_user when EINJ places no poison
2026-08-18 11:41 ` sashiko-bot
@ 2026-08-18 12:50 ` Like Xu
0 siblings, 0 replies; 8+ messages in thread
From: Like Xu @ 2026-08-18 12:50 UTC (permalink / raw)
To: Marc Zyngier; +Cc: Oliver Upton, kvm, kvmarm, sashiko-reviews
On 8/18/26 7:41 PM, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
> - [Medium] The check skips the test unconditionally on any guest assertion failure, masking unrelated or future bugs.
> --
>
> --- Patch [3]: [PATCH v2 3/3] KVM: selftests: arm64: Skip sea_to_user when EINJ places no poison ---
> commit 3bedaa634d562c6cc85fb3726d30fa40f0ed350d
> Author: Like Xu <like.xu.linux@gmail.com>
>
> KVM: selftests: arm64: Skip sea_to_user when EINJ places no poison
>
> This commit modifies the sea_to_user selftest to skip rather than fail
> when the firmware does not leave consumable poison in memory. It detects
> this condition by checking if the exit reason is not KVM_EXIT_ARM_SEA
> and a UCALL_ABORT is received from the guest.
>
>> diff --git a/tools/testing/selftests/kvm/arm64/sea_to_user.c b/tools/testing/selftests/kvm/arm64/sea_to_user.c
>> index 7cf95da8e594d..d4af6e0eed288 100644
>> --- a/tools/testing/selftests/kvm/arm64/sea_to_user.c
>> +++ b/tools/testing/selftests/kvm/arm64/sea_to_user.c
>> @@ -215,13 +215,33 @@ static void run_vm(struct kvm_vm *vm, struct kvm_vcpu *vcpu)
>>
>> ksft_print_msg("Dump kvm_run info about KVM_EXIT_%s\n",
>> exit_reason_str(run->exit_reason));
>> +
>> + /*
>> + * The guest's read of the injected location is expected to trap to KVM
>> + * as an SEA. If it does not, the injected error was never placed as
>> + * consumable poison: some firmware honours EINJ's notrigger request by
>> + * arming the poison only as part of the (now skipped) trigger step, so
>> + * nothing is left in memory for the guest to consume. The guest then
>> + * reads back the sentinel value and reports it via GUEST_FAIL, which
>> + * arm64 delivers as a ucall over MMIO (hence a KVM_EXIT_MMIO here).
>> + * Treat that as "this platform cannot host the test" and skip, matching
>> + * the requirement documented at the top of this file, rather than
>> + * failing on a hardware/firmware limitation the test cannot control.
>> + */
>> + if (run->exit_reason != KVM_EXIT_ARM_SEA &&
>> + get_ucall(vcpu, &uc) == UCALL_ABORT) {
>
> [Severity: Medium]
> Does this unconditionally skip the test on any guest assertion failure,
> masking unrelated or future bugs?
>
> If KVM fails to exit to userspace and instead incorrectly injects a
> Data Abort to the guest with an unexpected syndrome, the guest's SEA
> handler might fail a GUEST_ASSERT_EQ check. This would emit a
> UCALL_ABORT over MMIO.
>
> The host loop here interprets any UCALL_ABORT during this phase as a
> missing EINJ poison limitation, skipping the test instead of reporting
> the failure.
>
> Can we check the specific assertion failure reason or uc.buffer string
> instead of catching all UCALL_ABORTs?
For any human to double check:
diff --git a/tools/testing/selftests/kvm/arm64/sea_to_user.c
b/tools/testing/selftests/kvm/arm64/sea_to_user.c
index 7cf95da8e594d..f1d156d7645cc 100644
--- a/tools/testing/selftests/kvm/arm64/sea_to_user.c
+++ b/tools/testing/selftests/kvm/arm64/sea_to_user.c
@@ -165,11 +165,16 @@ static void guest_code(void)
{
u64 guest_data;
- /* Consumes error will cause a SEA. */
+ /* Consuming a real poison faults as an SEA and never returns here. */
guest_data = *(u64 *)EINJ_GVA;
- GUEST_FAIL("Poison not protected by SEA: gva=%#lx, guest_data=%#lx\n",
- EINJ_GVA, guest_data);
+ /*
+ * The read returned, so this platform placed no consumable poison.
+ * Report the value read as a positive signal that is distinct from a
+ * GUEST_ASSERT failure, so the host can skip on this limitation without
+ * masking a genuine SEA-handling failure.
+ */
+ GUEST_SYNC1(guest_data);
}
static void expect_sea_handler(struct ex_regs *regs)
@@ -215,13 +220,46 @@ static void run_vm(struct kvm_vm *vm, struct
kvm_vcpu *vcpu)
ksft_print_msg("Dump kvm_run info about KVM_EXIT_%s\n",
exit_reason_str(run->exit_reason));
+
+ /*
+ * The guest's read of the injected location must trap to KVM as an
+ * SEA. If the exit is something else, decode what the guest did before
+ * deciding whether to skip or fail:
+ *
+ * - UCALL_SYNC: the read returned instead of faulting, so no
+ * consumable poison was placed. Some firmware only arms EINJ poison
+ * as part of the trigger step that notrigger=1 skips, leaving
+ * nothing for the guest to consume; arm64 delivers the ucall as an
+ * MMIO write (hence KVM_EXIT_MMIO). This platform cannot host the
+ * test, so skip, matching the requirement documented at the top.
+ * - UCALL_ABORT: the guest took an abort but its SEA handler failed an
+ * assertion. That is a genuine problem, so report it rather than
+ * masking it as the limitation above.
+ * - anything else: fall through to the exit-reason assertion.
+ */
+ if (run->exit_reason != KVM_EXIT_ARM_SEA) {
+ switch (get_ucall(vcpu, &uc)) {
+ case UCALL_SYNC:
+ ksft_print_msg("Guest read back %#lx without an SEA\n",
+ uc.args[0]);
+ ksft_exit_skip("EINJ notrigger placed no consumable poison on this
platform\n");
+ break;
+ case UCALL_ABORT:
+ REPORT_GUEST_ASSERT(uc);
+ break;
+ default:
+ break;
+ }
+ }
+
+ TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_ARM_SEA);
+
+ /* arm_sea holds valid data only for a KVM_EXIT_ARM_SEA exit. */
ksft_print_msg("kvm_run.arm_sea: esr=%#llx, flags=%#llx\n",
run->arm_sea.esr, run->arm_sea.flags);
ksft_print_msg("kvm_run.arm_sea: gva=%#llx, gpa=%#llx\n",
run->arm_sea.gva, run->arm_sea.gpa);
- TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_ARM_SEA);
-
esr = run->arm_sea.esr;
TEST_ASSERT_EQ(ESR_ELx_EC(esr), ESR_ELx_EC_DABT_LOW);
TEST_ASSERT_EQ(esr & ESR_ELx_FSC_TYPE, ESR_ELx_FSC_EXTABT);
>
>> + ksft_print_msg("Guest consumed no SEA: %s", uc.buffer);
>> + ksft_exit_skip("EINJ notrigger placed no consumable poison on this platform\n");
>> + }
>> +
>> + TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_ARM_SEA);
>> +
>> + /* arm_sea holds valid data only for a KVM_EXIT_ARM_SEA exit. */
>> ksft_print_msg("kvm_run.arm_sea: esr=%#llx, flags=%#llx\n",
>> run->arm_sea.esr, run->arm_sea.flags);
>> ksft_print_msg("kvm_run.arm_sea: gva=%#llx, gpa=%#llx\n",
>
^ permalink raw reply related [flat|nested] 8+ messages in thread
end of thread, other threads:[~2026-08-18 12:50 UTC | newest]
Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-18 11:29 [PATCH v2 0/3] KVM: selftests: arm64: Make sea_to_user skip cleanly Like Xu
2026-08-18 11:29 ` [PATCH v2 1/3] KVM: selftests: arm64: Fix EINJ handling in sea_to_user Like Xu
2026-08-18 11:29 ` [PATCH v2 2/3] KVM: selftests: arm64: Skip sea_to_user without 1GB hugepages Like Xu
2026-08-18 11:36 ` sashiko-bot
2026-08-18 12:49 ` Like Xu
2026-08-18 11:29 ` [PATCH v2 3/3] KVM: selftests: arm64: Skip sea_to_user when EINJ places no poison Like Xu
2026-08-18 11:41 ` sashiko-bot
2026-08-18 12:50 ` Like Xu
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox