The fsgsbase{_restore} tests' load_gs() helper needs a nonzero-based FS/GS segment, i.e. a selector whose hidden base differs from the kernel's saved thread base, to exercise the selector/base state tracking. 64-bit userspace can only create such a segment via modify_ldt() or, failing that, via the 32-bit set_thread_area syscall over int $0x80. On kernels built with CONFIG_MODIFY_LDT_SYSCALL=n, modify_ldt() fails and the test falls back to int $0x80. If the kernel is also built with CONFIG_IA32_EMULATION=n, no IDT gate is installed for vector 0x80, so executing int $0x80 raises a #GP fault and the test dies with SIGSEGV instead of reporting results: traps: fsgsbase_64[5460] general protection fault ip:4012a1 sp:7f38eb124de0 error:402 in fsgsbase_64[12a1,400000+2000] With both options disabled, pure 64-bit userspace cannot install a nonzero-based FS/GS segment at all, so this scenario is untestable. Probe for a working int $0x80 at startup and skip the affected subtests when neither mechanism is available instead of crashing. While at it, replace the magic syscall numbers in the int $0x80 inline asm with named __NR_ia32_getpid / __NR_ia32_set_thread_area constants. These deliberately use the ia32 syscall table numbering, which differs from the x86-64 numbering exported by on 64-bit builds. Fixes: 0051202f6ad5f ("selftests/x86: Test the FSBASE/GSBASE API and context switching") Signed-off-by: Bjoern Doebel Assisted-by: opencode:kimi-k3 Cc: stable@vger.kernel.org --- v2 - fsgsbase_restore needs that segment treatment as well --- tools/testing/selftests/x86/fsgsbase.c | 51 ++++++++++++- .../testing/selftests/x86/fsgsbase_restore.c | 72 ++++++++++++++++++- 2 files changed, 119 insertions(+), 4 deletions(-) diff --git a/tools/testing/selftests/x86/fsgsbase.c b/tools/testing/selftests/x86/fsgsbase.c index 0a75252d31b6a..8538ceb6c5785 100644 --- a/tools/testing/selftests/x86/fsgsbase.c +++ b/tools/testing/selftests/x86/fsgsbase.c @@ -218,6 +218,40 @@ static void do_remote_base() static __thread int set_thread_area_entry_number = -1; +/* + * int $0x80 dispatches through the ia32 syscall table, whose numbers + * differ from the x86-64 table exposed by on an + * x86_64 build. Define the ia32 numbers we need explicitly. + */ +#define __NR_ia32_getpid 20 +#define __NR_ia32_set_thread_area 243 + +static bool have_int80; + +static void sigsegv_int80(int sig, siginfo_t *si, void *ctx_void) +{ + siglongjmp(jmpbuf, 1); +} + +static bool probe_int80(void) +{ + /* + * Check whether int $0x80 is available. Kernels built without + * CONFIG_IA32_EMULATION do not install an IDT entry for vector + * 0x80, so executing int $0x80 causes a #GP fault. + */ + sethandler(SIGSEGV, sigsegv_int80, 0); + if (sigsetjmp(jmpbuf, 1) == 0) { + long ret; + /* getpid -- harmless if it works */ + asm volatile ("int $0x80" : "=a" (ret) : "a" (__NR_ia32_getpid)); + clearhandler(SIGSEGV); + return true; + } + clearhandler(SIGSEGV); + return false; +} + static unsigned short load_gs(void) { /* @@ -245,7 +279,7 @@ static unsigned short load_gs(void) printf("\tusing LDT slot 0\n"); asm volatile ("mov %0, %%gs" : : "rm" ((unsigned short)0x7)); return 0x7; - } else { + } else if (have_int80) { /* No modify_ldt for us (configured out, perhaps) */ struct user_desc *low_desc = mmap( @@ -260,7 +294,7 @@ static unsigned short load_gs(void) long ret; asm volatile ("int $0x80" : "=a" (ret), "+m" (*low_desc) - : "a" (243), "b" (low_desc) + : "a" (__NR_ia32_set_thread_area), "b" (low_desc) : "r8", "r9", "r10", "r11"); memcpy(&desc, low_desc, sizeof(desc)); munmap(low_desc, sizeof(desc)); @@ -275,6 +309,9 @@ static unsigned short load_gs(void) unsigned short gs = (unsigned short)((desc.entry_number << 3) | 0x3); asm volatile ("mov %0, %%gs" : : "rm" (gs)); return gs; + } else { + printf("[NOTE]\tno way to create a nonzero-based segment\n"); + return 0; } } @@ -516,6 +553,11 @@ static void test_ptrace_write_gsbase(void) gs = ptrace(PTRACE_PEEKUSER, child, gs_offset, NULL); + if (*shared_scratch == 0) { + printf("[SKIP]\tCould not create a nonzero GS selector\n"); + goto END; + } + if (gs != *shared_scratch) { nerrs++; printf("[FAIL]\tGS is not prepared with nonzero\n"); @@ -587,6 +629,11 @@ int main() } clearhandler(SIGILL); + /* Probe int $0x80 (32-bit syscall entry) */ + have_int80 = probe_int80(); + if (!have_int80) + printf("\tint $0x80 is unavailable (CONFIG_IA32_EMULATION=n?)\n"); + sethandler(SIGSEGV, sigsegv, 0); check_gs_value(0); diff --git a/tools/testing/selftests/x86/fsgsbase_restore.c b/tools/testing/selftests/x86/fsgsbase_restore.c index 224058c1e4b2e..bc2c1a2ceaf85 100644 --- a/tools/testing/selftests/x86/fsgsbase_restore.c +++ b/tools/testing/selftests/x86/fsgsbase_restore.c @@ -30,9 +30,68 @@ #include #include #include +#include +#include #define EXPECTED_VALUE 0x1337f00d +/* + * int $0x80 dispatches through the ia32 syscall table, whose numbers + * differ from the x86-64 table exposed by on an + * x86_64 build. Define the ia32 numbers we need explicitly. + */ +#define __NR_ia32_getpid 20 +#define __NR_ia32_set_thread_area 243 + +static sigjmp_buf jmpbuf; + +static void sigsegv_int80(int sig, siginfo_t *si, void *ctx_void) +{ + siglongjmp(jmpbuf, 1); +} + +static void sethandler_int80(int sig, void (*handler)(int, siginfo_t *, void *)) +{ + struct sigaction sa; + + memset(&sa, 0, sizeof(sa)); + sa.sa_sigaction = handler; + sa.sa_flags = SA_SIGINFO; + sigemptyset(&sa.sa_mask); + if (sigaction(sig, &sa, 0)) + err(1, "sigaction"); +} + +static void clearhandler_int80(int sig) +{ + struct sigaction sa; + + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = SIG_DFL; + sigemptyset(&sa.sa_mask); + if (sigaction(sig, &sa, 0)) + err(1, "sigaction"); +} + +static bool probe_int80(void) +{ + /* + * Check whether int $0x80 is available. Kernels built without + * CONFIG_IA32_EMULATION do not install an IDT entry for vector + * 0x80, so executing int $0x80 causes a #GP fault. + */ + sethandler_int80(SIGSEGV, sigsegv_int80); + if (sigsetjmp(jmpbuf, 1) == 0) { + long ret; + /* getpid -- harmless if it works */ + asm volatile ("int $0x80" : "=a" (ret) : "a" (__NR_ia32_getpid)); + clearhandler_int80(SIGSEGV); + return true; + } + clearhandler_int80(SIGSEGV); + return false; +} + #ifdef __x86_64__ # define SEG "%gs" #else @@ -72,7 +131,7 @@ static void init_seg(void) if (syscall(SYS_modify_ldt, 1, &desc, sizeof(desc)) == 0) { printf("\tusing LDT slot 0\n"); asm volatile ("mov %0, %" SEG :: "rm" ((unsigned short)0x7)); - } else { + } else if (probe_int80()) { /* No modify_ldt for us (configured out, perhaps) */ struct user_desc *low_desc = mmap( @@ -87,7 +146,7 @@ static void init_seg(void) long ret; asm volatile ("int $0x80" : "=a" (ret), "+m" (*low_desc) - : "a" (243), "b" (low_desc) + : "a" (__NR_ia32_set_thread_area), "b" (low_desc) #ifdef __x86_64__ : "r8", "r9", "r10", "r11" #endif @@ -103,6 +162,15 @@ static void init_seg(void) unsigned short sel = (unsigned short)((desc.entry_number << 3) | 0x3); asm volatile ("mov %0, %" SEG :: "rm" (sel)); + } else { + /* + * Neither modify_ldt() (CONFIG_MODIFY_LDT_SYSCALL=n) nor + * int $0x80 (CONFIG_IA32_EMULATION=n) is available, so a + * nonzero-based segment cannot be created at all. There is + * nothing to test; skip instead of crashing on int $0x80. + */ + printf("[NOTE]\tno way to create a nonzero-based segment -- can't test anything\n"); + exit(0); } } -- 2.50.1