mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH v2] selftests/x86: Add a test for LASS enforcement
@ 2026-10-09  2:00 Sohil Mehta
  2026-10-13 18:54 ` H. Peter Anvin
  0 siblings, 1 reply; 4+ messages 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] 4+ messages in thread

* Re: [PATCH v2] selftests/x86: Add a test for LASS enforcement
  2026-10-09  2:00 [PATCH v2] selftests/x86: Add a test for LASS enforcement Sohil Mehta
@ 2026-10-13 18:54 ` H. Peter Anvin
  2026-10-13 19:51   ` Sohil Mehta
  0 siblings, 1 reply; 4+ messages in thread
From: H. Peter Anvin @ 2026-10-13 18:54 UTC (permalink / raw)
  To: Sohil Mehta, x86
  Cc: Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
	Shuah Khan, Binbin Wu, Peter Zijlstra, Chang S . Bae,
	Kishen Maloor, Sean Christopherson, Paolo Bonzini, linux-kernel,
	kvm, linux-kselftest

[-- Attachment #1: Type: text/plain, Size: 692 bytes --]

On 2026-10-08 19:00, Sohil Mehta wrote:
> 
> 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.
> 
Cumbersome, really?

Here is a set of parsing functions -- tested, even! -- that you can make
available as library routines. Integrating them into the selftests build
framework has been left as an exercise for the reader ;-)

Signed-off-by: H. Peter Anvin (Rosaic) <hpa@zytor.com>

	-hpa

[-- Attachment #2: cpuinfo.c --]
[-- Type: text/x-csrc, Size: 4995 bytes --]

#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define likely(x)	__builtin_expect(!!(x), 1)
#define unlikely(x)	__builtin_expect(!!(x), 0)

static char *read_cpuinfo(void)
{
	const size_t bufstep = 64*1024;
	size_t bufsize = 0;
	size_t len = 0;
	ssize_t rlen;
	char *buf = NULL;
	char *newbuf;
	int fd;
	int err;
	char *p;

	fd = open("/proc/cpuinfo", O_RDONLY);
	if (fd < 0)
		return NULL;

	do {
		if (len >= bufsize) {
			bufsize += bufstep;
			newbuf = realloc(buf, bufsize);
			if (!newbuf)
				goto error;
			buf = newbuf;
		}

		rlen = read(fd, buf + len, bufsize - len);
		if (rlen < 0) {
			if (errno == EINTR || errno == EAGAIN)
				continue;
			else
				goto error;
		}

		len += rlen;
	} while (rlen);

	/*
	 * There should always be a blank line after each CPU,
	 * including at the end.
	 */
	if (len < 2 || memcmp(buf + len - 2, "\n\n", 2)) {
		errno = EPROTO;
		goto error;
	}

	close(fd);

	/*
	 * buf will have at least one free byte at the end. As a result:
	 * - Each processor data block is terminated by an empty string.
	 * - The entire file is terminated by two empty strings in a row.
	 */
	buf[len] = '\0';

	/* Make each line a separate string */
	for (p = buf; p < buf + len; p++) {
		if (*p == '\n')
			*p = '\0';
	}

	newbuf = realloc(buf, len+1);
	if (newbuf)
		buf = newbuf;

	return buf;

error:
	err = errno;
	close(fd);
	free(buf);
	errno = err;
	return NULL;
}

const char *get_cpuinfo(void)
{
	static char *cpuinfo;

	if (unlikely(!cpuinfo))
		cpuinfo = read_cpuinfo();

	return cpuinfo;
}

/*
 * Sets *value to the contents of the field for a specific CPU or NULL.
 * Returns 0 on success or:
 *	-errno	- any file I/O error
 *	-EPROTO	- parsing error
 *	-ESRCH	- no such CPU
 *	-ENOMSG	- no such tag
 */
int cpuinfo_get_field(const char **value, const char *field, unsigned long cpu)
{
	static const char processor_prefix[] = "processor\t: ";
	const size_t pfxlen = sizeof processor_prefix - 1;
	size_t fieldlen;
	const char *p;

	*value = NULL;
	p = get_cpuinfo();
	if (!p)
		return -errno;

	while (1) {
		/* Blank line at start -> no more processors */
		if (!*p)
			return -ESRCH;

		/* First line must be processor */
		if (strncmp(p, processor_prefix, pfxlen))
			return -EPROTO;

		if (strtoul(p + pfxlen, NULL, 10) == cpu)
			break;	/* Found our CPU */

		/*
		 * Skip to the next processor.
		 * Each processor block is terminated by a blank line.
		 */
		do {
			p = strchr(p, '\0') + 1;
		} while (*p);
		p++;		/* Skip the blank line */
	}

	/* Look for the requested field inside the processor block. */
	fieldlen = strlen(field);

	while (*p) {
		if (!strncmp(p, field, fieldlen)) {
			p += fieldlen;
			if (!*p)
				return -EPROTO; /* No field data present */

			if (*p == '\t' || *p == ':') {
				/* Field matches */

				while (*p == '\t')
					p++;
				if (*p != ':')
					return -EPROTO; /* Colon missing */
				p++;

				/* Skip space after colon */
				if (*p == ' ')
					p++;
				*value = p;
				return 0;		/* Field found */
			}
		}
		p = strchr(p, '\0') + 1;	/* Next field */
	}

	return -ENOMSG;		/* CPU found but no such field */
}

/*
 * Look for a specific flag inside a specific multi-token field;
 * this currently applies to the "flags" or "bugs" fields.
 *
 * Return 1 if the given flag is present, 0 if it is not, or
 * a negative error code returned from cpuinfo_get_field().
 */
int cpuinfo_has_flag(const char *flag, const char *field, unsigned long cpu)
{
	const char *p;
	size_t flaglen;
	int err;

	err = cpuinfo_get_field(&p, field, cpu);
	if (err)
		return err;

	flaglen = strlen(flag);
	while (*p) {
		if (!strncmp(p, flag, flaglen) &&
		    (!p[flaglen] || p[flaglen] == ' '))
		    return 1;	/* Flag found */

		/* Skip to the next flag */
		p = strchr(p, ' ');
		if (!p)
			break;
		p++;
	}

	return 0;		/* Not found */
}

#ifdef TEST_CPUINFO

#include <stdio.h>
#include <sysexits.h>

int main(int argc, char **argv)
{
	const char *field, *flag;
	unsigned long cpu;
	int rv;

	if (argc < 3) {
		fprintf(stderr, "Usage: %s cpu field\n", argv[0]);
		exit(EX_USAGE);
	}

	cpu = strtoul(argv[1], NULL, 10);
	field = argv[2];
	flag = argv[3];

	if (flag) {
		rv = cpuinfo_has_flag(flag, field, cpu);
		if (rv >= 0) {
			printf("CPU %lu %s(%s) = %d\n", cpu, field, flag, rv);
			exit(rv);
		}
	} else {
		const char *data;
		rv = cpuinfo_get_field(&data, field, cpu);
		if (!rv) {
			printf("CPU %lu %s = %s\n", cpu, field, data);
			exit(0);
		}
	}

	/*
	 * On error. Errors that should never happen no matter what the
	 * input are sent to stderr.
	 */
	switch (rv) {
	case -ESRCH:
		printf("CPU %lu does not exist\n", cpu);
		exit(EX_NOUSER);
	case -ENOMSG:
		printf("CPU %lu %s : field does not exist\n", cpu, field);
		exit(EX_NOINPUT);
	case -EPROTO:
		fprintf(stderr, "%s: could not parse /proc/cpuinfo\n", argv[0]);
		exit(EX_PROTOCOL);
	default:
		fprintf(stderr, "/proc/cpuinfo: %s\n", strerror(-rv));
		exit(EX_OSFILE);
	}
}

#endif /* TEST_CPUINFO */

^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [PATCH v2] selftests/x86: Add a test for LASS enforcement
  2026-10-13 18:54 ` H. Peter Anvin
@ 2026-10-13 19:51   ` Sohil Mehta
  2026-10-13 23:06     ` H. Peter Anvin
  0 siblings, 1 reply; 4+ messages in thread
From: Sohil Mehta @ 2026-10-13 19:51 UTC (permalink / raw)
  To: H. Peter Anvin, x86, Borislav Petkov, Dave Hansen
  Cc: Thomas Gleixner, Ingo Molnar, Shuah Khan, Binbin Wu,
	Peter Zijlstra, Chang S . Bae, Kishen Maloor,
	Sean Christopherson, Paolo Bonzini, linux-kernel, kvm,
	linux-kselftest

On 10/13/2026 11:54 AM, H. Peter Anvin wrote:
> On 2026-10-08 19:00, Sohil Mehta wrote:
>>
>> 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.
>>
> Cumbersome, really?
> 

I had it in v1 of this patch. It seemed excessive to do specifically for
LASS:
https://lore.kernel.org/lkml/20260806011536.4172258-8-sohil.mehta@intel.com/

As LASS is always enabled by the kernel except for the really odd cases
the trade-off seems practical to me.

> Here is a set of parsing functions -- tested, even! -- that you can make
> available as library routines. Integrating them into the selftests build
> framework has been left as an exercise for the reader ;-)
> 

I agree, adding them as generic selftest helpers may be useful. But,
/proc/cpuinfo has been unreliable in general. It is currently buggy for
some features with Kconfig options. See:

https://lore.kernel.org/lkml/f6f113b46199d996c4f81fc89858c2f484256418.1774623092.git.m.wieczorretman@pm.me/

Also, (I think) we have been generally discouraging folks to add more
bits there. So, I don't know if creating these library routines is
useful right now. IIUC, Boris said that the CPUID rework would make some
of cpuinfo stuff moot.

https://lore.kernel.org/lkml/20260331140216.GDacvT6IPW1gf003z-@fat_crate.local/

Thoughts?


^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [PATCH v2] selftests/x86: Add a test for LASS enforcement
  2026-10-13 19:51   ` Sohil Mehta
@ 2026-10-13 23:06     ` H. Peter Anvin
  0 siblings, 0 replies; 4+ messages in thread
From: H. Peter Anvin @ 2026-10-13 23:06 UTC (permalink / raw)
  To: Sohil Mehta, x86, Borislav Petkov, Dave Hansen
  Cc: Thomas Gleixner, Ingo Molnar, Shuah Khan, Binbin Wu,
	Peter Zijlstra, Chang S . Bae, Kishen Maloor,
	Sean Christopherson, Paolo Bonzini, linux-kernel, kvm,
	linux-kselftest

On 2026-10-13 12:51, Sohil Mehta wrote:
> 
> I had it in v1 of this patch. It seemed excessive to do specifically for
> LASS:
> https://lore.kernel.org/lkml/20260806011536.4172258-8-sohil.mehta@intel.com/
> 

"Specificially for LASS" is the problem here.

This goes under the "do it generally, do it right, do it once." Librarize it.

> I agree, adding them as generic selftest helpers may be useful. But,
> /proc/cpuinfo has been unreliable in general. It is currently buggy for
> some features with Kconfig options. See:
> 
> https://lore.kernel.org/lkml/f6f113b46199d996c4f81fc89858c2f484256418.1774623092.git.m.wieczorretman@pm.me/

> Also, (I think) we have been generally discouraging folks to add more
> bits there. So, I don't know if creating these library routines is
> useful right now. IIUC, Boris said that the CPUID rework would make some
> of cpuinfo stuff moot.
> 
> https://lore.kernel.org/lkml/20260331140216.GDacvT6IPW1gf003z-@fat_crate.local/

> Thoughts?

This is a great example of why these interfaces *are* useful and why they
*should* be fixed.

	-hpa


^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2026-10-13 23:07 UTC | newest]

Thread overview: 4+ messages (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
2026-10-13 18:54 ` H. Peter Anvin
2026-10-13 19:51   ` Sohil Mehta
2026-10-13 23:06     ` H. Peter Anvin

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®