~ [ source navigation ] ~ [ diff markup ] ~ [ identifier search ] ~

TOMOYO Linux Cross Reference
Linux/tools/testing/selftests/x86/fsgsbase_restore.c

Version: ~ [ linux-6.11.5 ] ~ [ linux-6.10.14 ] ~ [ linux-6.9.12 ] ~ [ linux-6.8.12 ] ~ [ linux-6.7.12 ] ~ [ linux-6.6.58 ] ~ [ linux-6.5.13 ] ~ [ linux-6.4.16 ] ~ [ linux-6.3.13 ] ~ [ linux-6.2.16 ] ~ [ linux-6.1.114 ] ~ [ linux-6.0.19 ] ~ [ linux-5.19.17 ] ~ [ linux-5.18.19 ] ~ [ linux-5.17.15 ] ~ [ linux-5.16.20 ] ~ [ linux-5.15.169 ] ~ [ linux-5.14.21 ] ~ [ linux-5.13.19 ] ~ [ linux-5.12.19 ] ~ [ linux-5.11.22 ] ~ [ linux-5.10.228 ] ~ [ linux-5.9.16 ] ~ [ linux-5.8.18 ] ~ [ linux-5.7.19 ] ~ [ linux-5.6.19 ] ~ [ linux-5.5.19 ] ~ [ linux-5.4.284 ] ~ [ linux-5.3.18 ] ~ [ linux-5.2.21 ] ~ [ linux-5.1.21 ] ~ [ linux-5.0.21 ] ~ [ linux-4.20.17 ] ~ [ linux-4.19.322 ] ~ [ linux-4.18.20 ] ~ [ linux-4.17.19 ] ~ [ linux-4.16.18 ] ~ [ linux-4.15.18 ] ~ [ linux-4.14.336 ] ~ [ linux-4.13.16 ] ~ [ linux-4.12.14 ] ~ [ linux-4.11.12 ] ~ [ linux-4.10.17 ] ~ [ linux-4.9.337 ] ~ [ linux-4.4.302 ] ~ [ linux-3.10.108 ] ~ [ linux-2.6.32.71 ] ~ [ linux-2.6.0 ] ~ [ linux-2.4.37.11 ] ~ [ unix-v6-master ] ~ [ ccs-tools-1.8.9 ] ~ [ policy-sample ] ~
Architecture: ~ [ i386 ] ~ [ alpha ] ~ [ m68k ] ~ [ mips ] ~ [ ppc ] ~ [ sparc ] ~ [ sparc64 ] ~

  1 // SPDX-License-Identifier: GPL-2.0-only
  2 /*
  3  * fsgsbase_restore.c, test ptrace vs fsgsbase
  4  * Copyright (c) 2020 Andy Lutomirski
  5  *
  6  * This test case simulates a tracer redirecting tracee execution to
  7  * a function and then restoring tracee state using PTRACE_GETREGS and
  8  * PTRACE_SETREGS.  This is similar to what gdb does when doing
  9  * 'p func()'.  The catch is that this test has the called function
 10  * modify a segment register.  This makes sure that ptrace correctly
 11  * restores segment state when using PTRACE_SETREGS.
 12  *
 13  * This is not part of fsgsbase.c, because that test is 64-bit only.
 14  */
 15 
 16 #define _GNU_SOURCE
 17 #include <stdio.h>
 18 #include <stdlib.h>
 19 #include <stdbool.h>
 20 #include <string.h>
 21 #include <sys/syscall.h>
 22 #include <unistd.h>
 23 #include <err.h>
 24 #include <sys/user.h>
 25 #include <asm/prctl.h>
 26 #include <sys/prctl.h>
 27 #include <asm/ldt.h>
 28 #include <sys/mman.h>
 29 #include <stddef.h>
 30 #include <sys/ptrace.h>
 31 #include <sys/wait.h>
 32 #include <stdint.h>
 33 
 34 #define EXPECTED_VALUE 0x1337f00d
 35 
 36 #ifdef __x86_64__
 37 # define SEG "%gs"
 38 #else
 39 # define SEG "%fs"
 40 #endif
 41 
 42 /*
 43  * Defined in clang_helpers_[32|64].S, because unlike gcc, clang inline asm does
 44  * not support segmentation prefixes.
 45  */
 46 unsigned int dereference_seg_base(void);
 47 
 48 static void init_seg(void)
 49 {
 50         unsigned int *target = mmap(
 51                 NULL, sizeof(unsigned int),
 52                 PROT_READ | PROT_WRITE,
 53                 MAP_PRIVATE | MAP_ANONYMOUS | MAP_32BIT, -1, 0);
 54         if (target == MAP_FAILED)
 55                 err(1, "mmap");
 56 
 57         *target = EXPECTED_VALUE;
 58 
 59         printf("\tsegment base address = 0x%lx\n", (unsigned long)target);
 60 
 61         struct user_desc desc = {
 62                 .entry_number    = 0,
 63                 .base_addr       = (unsigned int)(uintptr_t)target,
 64                 .limit           = sizeof(unsigned int) - 1,
 65                 .seg_32bit       = 1,
 66                 .contents        = 0, /* Data, grow-up */
 67                 .read_exec_only  = 0,
 68                 .limit_in_pages  = 0,
 69                 .seg_not_present = 0,
 70                 .useable         = 0
 71         };
 72         if (syscall(SYS_modify_ldt, 1, &desc, sizeof(desc)) == 0) {
 73                 printf("\tusing LDT slot 0\n");
 74                 asm volatile ("mov %0, %" SEG :: "rm" ((unsigned short)0x7));
 75         } else {
 76                 /* No modify_ldt for us (configured out, perhaps) */
 77 
 78                 struct user_desc *low_desc = mmap(
 79                         NULL, sizeof(desc),
 80                         PROT_READ | PROT_WRITE,
 81                         MAP_PRIVATE | MAP_ANONYMOUS | MAP_32BIT, -1, 0);
 82                 memcpy(low_desc, &desc, sizeof(desc));
 83 
 84                 low_desc->entry_number = -1;
 85 
 86                 /* 32-bit set_thread_area */
 87                 long ret;
 88                 asm volatile ("int $0x80"
 89                               : "=a" (ret), "+m" (*low_desc)
 90                               : "a" (243), "b" (low_desc)
 91 #ifdef __x86_64__
 92                               : "r8", "r9", "r10", "r11"
 93 #endif
 94                         );
 95                 memcpy(&desc, low_desc, sizeof(desc));
 96                 munmap(low_desc, sizeof(desc));
 97 
 98                 if (ret != 0) {
 99                         printf("[NOTE]\tcould not create a segment -- can't test anything\n");
100                         exit(0);
101                 }
102                 printf("\tusing GDT slot %d\n", desc.entry_number);
103 
104                 unsigned short sel = (unsigned short)((desc.entry_number << 3) | 0x3);
105                 asm volatile ("mov %0, %" SEG :: "rm" (sel));
106         }
107 }
108 
109 static void tracee_zap_segment(void)
110 {
111         /*
112          * The tracer will redirect execution here.  This is meant to
113          * work like gdb's 'p func()' feature.  The tricky bit is that
114          * we modify a segment register in order to make sure that ptrace
115          * can correctly restore segment registers.
116          */
117         printf("\tTracee: in tracee_zap_segment()\n");
118 
119         /*
120          * Write a nonzero selector with base zero to the segment register.
121          * Using a null selector would defeat the test on AMD pre-Zen2
122          * CPUs, as such CPUs don't clear the base when loading a null
123          * selector.
124          */
125         unsigned short sel;
126         asm volatile ("mov %%ss, %0\n\t"
127                       "mov %0, %" SEG
128                       : "=rm" (sel));
129 
130         pid_t pid = getpid(), tid = syscall(SYS_gettid);
131 
132         printf("\tTracee is going back to sleep\n");
133         syscall(SYS_tgkill, pid, tid, SIGSTOP);
134 
135         /* Should not get here. */
136         while (true) {
137                 printf("[FAIL]\tTracee hit unreachable code\n");
138                 pause();
139         }
140 }
141 
142 int main()
143 {
144         printf("\tSetting up a segment\n");
145         init_seg();
146 
147         unsigned int val = dereference_seg_base();
148         if (val != EXPECTED_VALUE) {
149                 printf("[FAIL]\tseg[0] == %x; should be %x\n", val, EXPECTED_VALUE);
150                 return 1;
151         }
152         printf("[OK]\tThe segment points to the right place.\n");
153 
154         pid_t chld = fork();
155         if (chld < 0)
156                 err(1, "fork");
157 
158         if (chld == 0) {
159                 prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0, 0);
160 
161                 if (ptrace(PTRACE_TRACEME, 0, 0, 0) != 0)
162                         err(1, "PTRACE_TRACEME");
163 
164                 pid_t pid = getpid(), tid = syscall(SYS_gettid);
165 
166                 printf("\tTracee will take a nap until signaled\n");
167                 syscall(SYS_tgkill, pid, tid, SIGSTOP);
168 
169                 printf("\tTracee was resumed.  Will re-check segment.\n");
170 
171                 val = dereference_seg_base();
172                 if (val != EXPECTED_VALUE) {
173                         printf("[FAIL]\tseg[0] == %x; should be %x\n", val, EXPECTED_VALUE);
174                         exit(1);
175                 }
176 
177                 printf("[OK]\tThe segment points to the right place.\n");
178                 exit(0);
179         }
180 
181         int status;
182 
183         /* Wait for SIGSTOP. */
184         if (waitpid(chld, &status, 0) != chld || !WIFSTOPPED(status))
185                 err(1, "waitpid");
186 
187         struct user_regs_struct regs;
188 
189         if (ptrace(PTRACE_GETREGS, chld, NULL, &regs) != 0)
190                 err(1, "PTRACE_GETREGS");
191 
192 #ifdef __x86_64__
193         printf("\tChild GS=0x%lx, GSBASE=0x%lx\n", (unsigned long)regs.gs, (unsigned long)regs.gs_base);
194 #else
195         printf("\tChild FS=0x%lx\n", (unsigned long)regs.xfs);
196 #endif
197 
198         struct user_regs_struct regs2 = regs;
199 #ifdef __x86_64__
200         regs2.rip = (unsigned long)tracee_zap_segment;
201         regs2.rsp -= 128;       /* Don't clobber the redzone. */
202 #else
203         regs2.eip = (unsigned long)tracee_zap_segment;
204 #endif
205 
206         printf("\tTracer: redirecting tracee to tracee_zap_segment()\n");
207         if (ptrace(PTRACE_SETREGS, chld, NULL, &regs2) != 0)
208                 err(1, "PTRACE_GETREGS");
209         if (ptrace(PTRACE_CONT, chld, NULL, NULL) != 0)
210                 err(1, "PTRACE_GETREGS");
211 
212         /* Wait for SIGSTOP. */
213         if (waitpid(chld, &status, 0) != chld || !WIFSTOPPED(status))
214                 err(1, "waitpid");
215 
216         printf("\tTracer: restoring tracee state\n");
217         if (ptrace(PTRACE_SETREGS, chld, NULL, &regs) != 0)
218                 err(1, "PTRACE_GETREGS");
219         if (ptrace(PTRACE_DETACH, chld, NULL, NULL) != 0)
220                 err(1, "PTRACE_GETREGS");
221 
222         /* Wait for SIGSTOP. */
223         if (waitpid(chld, &status, 0) != chld)
224                 err(1, "waitpid");
225 
226         if (WIFSIGNALED(status)) {
227                 printf("[FAIL]\tTracee crashed\n");
228                 return 1;
229         }
230 
231         if (!WIFEXITED(status)) {
232                 printf("[FAIL]\tTracee stopped for an unexpected reason: %d\n", status);
233                 return 1;
234         }
235 
236         int exitcode = WEXITSTATUS(status);
237         if (exitcode != 0) {
238                 printf("[FAIL]\tTracee reported failure\n");
239                 return 1;
240         }
241 
242         printf("[OK]\tAll is well.\n");
243         return 0;
244 }
245 

~ [ source navigation ] ~ [ diff markup ] ~ [ identifier search ] ~

kernel.org | git.kernel.org | LWN.net | Project Home | SVN repository | Mail admin

Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.

sflogo.php