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 <sys/syscall.h>
on 64-bit builds.

Fixes: 0051202f6ad5f ("selftests/x86: Test the FSBASE/GSBASE API and context 
switching")
Signed-off-by: Bjoern Doebel <[email protected]>
Assisted-by: opencode:kimi-k3
Cc: [email protected]
---
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 <sys/syscall.h> 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 <sys/ptrace.h>
 #include <sys/wait.h>
 #include <stdint.h>
+#include <setjmp.h>
+#include <signal.h>
 
 #define EXPECTED_VALUE 0x1337f00d
 
+/*
+ * int $0x80 dispatches through the ia32 syscall table, whose numbers
+ * differ from the x86-64 table exposed by <sys/syscall.h> 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


Reply via email to