* [PATCH v2] selftests/x86: Add a test for LASS enforcement
@ 2026-10-09 2:00 Sohil Mehta
0 siblings, 0 replies; only message in thread
From: Sohil Mehta @ 2026-10-09 2:00 UTC (permalink / raw)
To: x86
Cc: Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
H . Peter Anvin, Shuah Khan, Binbin Wu, Peter Zijlstra,
Chang S . Bae, Kishen Maloor, Sean Christopherson, Paolo Bonzini,
Sohil Mehta, linux-kernel, kvm, linux-kselftest
With LASS enabled, a user-mode access to a kernel address raises a #GP
instead of the #PF that paging alone would produce. Nothing in the x86
selftests specifically tests for a LASS violation. The vsyscall selftest
exercises this flow but doesn't verify the resulting #GP.
Add a test that reads, writes and executes at a kernel address and
verifies each one faults with a #GP and a zero error code. For the
instruction fetch, also verify the fault is reported at the target,
since LASS enforcement only occurs when the branch target is fetched.
LASS rejects an address based on bit 63 alone, but a non-canonical
address raises the very same #GP for a different reason. So, access a
canonical kernel address to attribute the fault specifically to LASS.
Note, the CPUID bit does not reflect whether the kernel enabled LASS;
only the cpuinfo flag does. But parsing cpuinfo is cumbersome, and the
kernel enables LASS whenever the CPU supports it unless the command line
opts out (vsyscall=emulate or clearcpuid=lass). To keep it simple, rely
on the CPUID bit and accept a failure in these unusual cases.
The test was initially generated by an LLM but has been heavily modified
to match upstream expectations.
Signed-off-by: Sohil Mehta <sohil.mehta@intel.com>
---
v2:
- Simplified using CPUID instead of /proc/cpuinfo for detecting LASS
- Used jmp for instruction fetch tests instead of a call.
- Removed the SIGBUS handler since it can never generate a #SS here
(Binbin)
- Did not pick up the review and test tags as the code has
significantly changed.
v1 (Part of LASS KVM v4):
- https://lore.kernel.org/lkml/20260806011536.4172258-8-sohil.mehta@intel.com/
---
tools/testing/selftests/x86/Makefile | 3 +-
tools/testing/selftests/x86/lass.c | 158 +++++++++++++++++++++++++++
2 files changed, 160 insertions(+), 1 deletion(-)
create mode 100644 tools/testing/selftests/x86/lass.c
diff --git a/tools/testing/selftests/x86/Makefile b/tools/testing/selftests/x86/Makefile
index d478b13cc8d5..025c187a563d 100644
--- a/tools/testing/selftests/x86/Makefile
+++ b/tools/testing/selftests/x86/Makefile
@@ -19,7 +19,8 @@ TARGETS_C_32BIT_ONLY := entry_from_vm86 test_syscall_vdso unwind_vdso \
test_FCMOV test_FCOMI test_FISTTP \
vdso_restorer
TARGETS_C_64BIT_ONLY := fsgsbase sysret_rip syscall_numbering \
- corrupt_xstate_header amx lam test_shadow_stack avx apx
+ corrupt_xstate_header amx lam test_shadow_stack avx apx \
+ lass
# Some selftests require 32bit support enabled also on 64bit systems
TARGETS_C_32BIT_NEEDED := ldt_gdt ptrace_syscall
diff --git a/tools/testing/selftests/x86/lass.c b/tools/testing/selftests/x86/lass.c
new file mode 100644
index 000000000000..5f6e326e4f4a
--- /dev/null
+++ b/tools/testing/selftests/x86/lass.c
@@ -0,0 +1,158 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Test Linear Address Space Separation (LASS) enforcement
+ *
+ * With LASS enabled, a user-mode read, write or instruction fetch at a kernel
+ * address raises a #GP instead of the #PF that paging alone would produce.
+ */
+#define _GNU_SOURCE
+
+#include <setjmp.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <sys/ucontext.h>
+
+#include "helpers.h"
+
+#ifndef __x86_64__
+# error This test is 64-bit only
+#endif
+
+/*
+ * Canonicality enforcement generates the same #GP as LASS for non-canonical
+ * addresses. So, use a canonical address to specifically attribute the fault
+ * to LASS. Bits 63:47 are all set here, which is canonical with 4-level
+ * paging as well as 5-level paging.
+ */
+#define KERNEL_ADDR 0xffff800000000000UL
+
+static sigjmp_buf jmpbuf;
+
+static volatile unsigned long fault_trapno, fault_err, fault_rip;
+
+/* Handle SIGSEGV (#GP and #PF) */
+static void fault_handler(int sig, siginfo_t *info, void *ctx_void)
+{
+ ucontext_t *ctx = (ucontext_t *)ctx_void;
+
+ fault_trapno = ctx->uc_mcontext.gregs[REG_TRAPNO];
+ fault_err = ctx->uc_mcontext.gregs[REG_ERR];
+ fault_rip = ctx->uc_mcontext.gregs[REG_RIP];
+ siglongjmp(jmpbuf, 1);
+}
+
+/* Check CPUID bit corresponding to X86_FEATURE_LASS */
+static bool is_lass_supported(void)
+{
+ unsigned int eax, ebx, ecx, edx;
+
+ __cpuid_count(0x7, 0x1, eax, ebx, ecx, edx);
+ return eax & (1 << 6);
+}
+
+/* A LASS violation raises a #GP with a null error code. */
+static bool is_lass_violation(void)
+{
+ return fault_trapno == 13 && !fault_err;
+}
+
+static void test_kernel_read(void)
+{
+ if (sigsetjmp(jmpbuf, 1) == 0) {
+ *(volatile unsigned long *)KERNEL_ADDR;
+ ksft_test_result_fail("The read did not fault\n");
+ return;
+ }
+
+ ksft_test_result(is_lass_violation(),
+ "The read faulted with trap=%ld, error=0x%lx\n",
+ fault_trapno, fault_err);
+}
+
+static void test_kernel_write(void)
+{
+ if (sigsetjmp(jmpbuf, 1) == 0) {
+ *(volatile unsigned long *)KERNEL_ADDR = 0x1a55;
+ ksft_test_result_fail("The write did not fault\n");
+ return;
+ }
+
+ ksft_test_result(is_lass_violation(),
+ "The write faulted with trap=%ld, error=0x%lx\n",
+ fault_trapno, fault_err);
+}
+
+/*
+ * Branch to the kernel address by hand: a direct 'call rel32' cannot reach
+ * that far, and an indirect call through a function pointer is free to be
+ * elided, which would stop testing the fetch.
+ *
+ * A 'jmp' fetches the target without writing the stack and never returns, so
+ * there is no call-clobbered register state to describe.
+ */
+static void do_fetch(unsigned long addr)
+{
+ asm volatile ("jmp *%[fn]" : : [fn] "r" (addr));
+}
+
+static void test_kernel_fetch(void)
+{
+ if (sigsetjmp(jmpbuf, 1) == 0) {
+ do_fetch(KERNEL_ADDR);
+
+ /*
+ * Control does not come back from the jump, so reaching here
+ * means execution went somewhere unknown. Don't try to run
+ * the rest of the tests.
+ */
+ ksft_exit_fail_msg("The fetch did not fault\n");
+ }
+
+ /*
+ * Branch instructions do not check their target against LASS. The
+ * violation happens when the target address is used to fetch the next
+ * instruction, so the fault must be reported at the target rather
+ * than at the branch.
+ */
+ if (fault_rip != KERNEL_ADDR) {
+ ksft_test_result_fail("The fetch faulted at RIP 0x%lx instead of 0x%lx\n",
+ fault_rip, KERNEL_ADDR);
+ return;
+ }
+
+ ksft_test_result(is_lass_violation(),
+ "The fetch faulted with trap=%ld, error=0x%lx\n",
+ fault_trapno, fault_err);
+}
+
+#define TOTAL_TESTS 3
+
+int main(void)
+{
+ ksft_print_header();
+
+ /*
+ * Only the cpuinfo flag truly reflects whether the kernel enabled
+ * LASS. In practice, the kernel always enables LASS when the CPU
+ * supports it, so check the CPUID bit instead.
+ *
+ * The test is expected to fail if LASS is disabled via the kernel
+ * command line.
+ */
+ if (!is_lass_supported())
+ ksft_exit_skip("LASS is not present\n");
+
+ ksft_set_plan(TOTAL_TESTS);
+
+ sethandler(SIGSEGV, fault_handler, 0);
+
+ ksft_print_msg("Accessing the kernel address 0x%lx from userspace\n",
+ KERNEL_ADDR);
+ test_kernel_read();
+ test_kernel_write();
+ test_kernel_fetch();
+
+ clearhandler(SIGSEGV);
+
+ ksft_finished();
+}
--
2.43.0
^ permalink raw reply [flat|nested] only message in thread
only message in thread, other threads:[~2026-10-09 2:02 UTC | newest]
Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-09 2:00 [PATCH v2] selftests/x86: Add a test for LASS enforcement Sohil Mehta
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®