1 // SPDX-License-Identifier: GPL-2.0-only
3 * fsgsbase_restore.c, test ptrace vs fsgsbase
4 * Copyright (c) 2020 Andy Lutomirski
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.
13 * This is not part of fsgsbase.c, because that test is 64-bit only.
21 #include <sys/syscall.h>
25 #include <asm/prctl.h>
26 #include <sys/prctl.h>
30 #include <sys/ptrace.h>
34 #define EXPECTED_VALUE 0x1337f00d
42 static unsigned int dereference_seg_base(void)
45 asm volatile ("mov %" SEG
":(0), %0" : "=rm" (ret
));
49 static void init_seg(void)
51 unsigned int *target
= mmap(
52 NULL
, sizeof(unsigned int),
53 PROT_READ
| PROT_WRITE
,
54 MAP_PRIVATE
| MAP_ANONYMOUS
| MAP_32BIT
, -1, 0);
55 if (target
== MAP_FAILED
)
58 *target
= EXPECTED_VALUE
;
60 printf("\tsegment base address = 0x%lx\n", (unsigned long)target
);
62 struct user_desc desc
= {
64 .base_addr
= (unsigned int)(uintptr_t)target
,
65 .limit
= sizeof(unsigned int) - 1,
67 .contents
= 0, /* Data, grow-up */
73 if (syscall(SYS_modify_ldt
, 1, &desc
, sizeof(desc
)) == 0) {
74 printf("\tusing LDT slot 0\n");
75 asm volatile ("mov %0, %" SEG :: "rm" ((unsigned short)0x7));
77 /* No modify_ldt for us (configured out, perhaps) */
79 struct user_desc
*low_desc
= mmap(
81 PROT_READ
| PROT_WRITE
,
82 MAP_PRIVATE
| MAP_ANONYMOUS
| MAP_32BIT
, -1, 0);
83 memcpy(low_desc
, &desc
, sizeof(desc
));
85 low_desc
->entry_number
= -1;
87 /* 32-bit set_thread_area */
89 asm volatile ("int $0x80"
90 : "=a" (ret
), "+m" (*low_desc
)
91 : "a" (243), "b" (low_desc
)
93 : "r8", "r9", "r10", "r11"
96 memcpy(&desc
, low_desc
, sizeof(desc
));
97 munmap(low_desc
, sizeof(desc
));
100 printf("[NOTE]\tcould not create a segment -- can't test anything\n");
103 printf("\tusing GDT slot %d\n", desc
.entry_number
);
105 unsigned short sel
= (unsigned short)((desc
.entry_number
<< 3) | 0x3);
106 asm volatile ("mov %0, %" SEG :: "rm" (sel
));
110 static void tracee_zap_segment(void)
113 * The tracer will redirect execution here. This is meant to
114 * work like gdb's 'p func()' feature. The tricky bit is that
115 * we modify a segment register in order to make sure that ptrace
116 * can correctly restore segment registers.
118 printf("\tTracee: in tracee_zap_segment()\n");
121 * Write a nonzero selector with base zero to the segment register.
122 * Using a null selector would defeat the test on AMD pre-Zen2
123 * CPUs, as such CPUs don't clear the base when loading a null
127 asm volatile ("mov %%ss, %0\n\t"
131 pid_t pid
= getpid(), tid
= syscall(SYS_gettid
);
133 printf("\tTracee is going back to sleep\n");
134 syscall(SYS_tgkill
, pid
, tid
, SIGSTOP
);
136 /* Should not get here. */
138 printf("[FAIL]\tTracee hit unreachable code\n");
145 printf("\tSetting up a segment\n");
148 unsigned int val
= dereference_seg_base();
149 if (val
!= EXPECTED_VALUE
) {
150 printf("[FAIL]\tseg[0] == %x; should be %x\n", val
, EXPECTED_VALUE
);
153 printf("[OK]\tThe segment points to the right place.\n");
160 prctl(PR_SET_PDEATHSIG
, SIGKILL
, 0, 0, 0, 0);
162 if (ptrace(PTRACE_TRACEME
, 0, 0, 0) != 0)
163 err(1, "PTRACE_TRACEME");
165 pid_t pid
= getpid(), tid
= syscall(SYS_gettid
);
167 printf("\tTracee will take a nap until signaled\n");
168 syscall(SYS_tgkill
, pid
, tid
, SIGSTOP
);
170 printf("\tTracee was resumed. Will re-check segment.\n");
172 val
= dereference_seg_base();
173 if (val
!= EXPECTED_VALUE
) {
174 printf("[FAIL]\tseg[0] == %x; should be %x\n", val
, EXPECTED_VALUE
);
178 printf("[OK]\tThe segment points to the right place.\n");
184 /* Wait for SIGSTOP. */
185 if (waitpid(chld
, &status
, 0) != chld
|| !WIFSTOPPED(status
))
188 struct user_regs_struct regs
;
190 if (ptrace(PTRACE_GETREGS
, chld
, NULL
, ®s
) != 0)
191 err(1, "PTRACE_GETREGS");
194 printf("\tChild GS=0x%lx, GSBASE=0x%lx\n", (unsigned long)regs
.gs
, (unsigned long)regs
.gs_base
);
196 printf("\tChild FS=0x%lx\n", (unsigned long)regs
.xfs
);
199 struct user_regs_struct regs2
= regs
;
201 regs2
.rip
= (unsigned long)tracee_zap_segment
;
202 regs2
.rsp
-= 128; /* Don't clobber the redzone. */
204 regs2
.eip
= (unsigned long)tracee_zap_segment
;
207 printf("\tTracer: redirecting tracee to tracee_zap_segment()\n");
208 if (ptrace(PTRACE_SETREGS
, chld
, NULL
, ®s2
) != 0)
209 err(1, "PTRACE_GETREGS");
210 if (ptrace(PTRACE_CONT
, chld
, NULL
, NULL
) != 0)
211 err(1, "PTRACE_GETREGS");
213 /* Wait for SIGSTOP. */
214 if (waitpid(chld
, &status
, 0) != chld
|| !WIFSTOPPED(status
))
217 printf("\tTracer: restoring tracee state\n");
218 if (ptrace(PTRACE_SETREGS
, chld
, NULL
, ®s
) != 0)
219 err(1, "PTRACE_GETREGS");
220 if (ptrace(PTRACE_DETACH
, chld
, NULL
, NULL
) != 0)
221 err(1, "PTRACE_GETREGS");
223 /* Wait for SIGSTOP. */
224 if (waitpid(chld
, &status
, 0) != chld
)
227 if (WIFSIGNALED(status
)) {
228 printf("[FAIL]\tTracee crashed\n");
232 if (!WIFEXITED(status
)) {
233 printf("[FAIL]\tTracee stopped for an unexpected reason: %d\n", status
);
237 int exitcode
= WEXITSTATUS(status
);
239 printf("[FAIL]\tTracee reported failure\n");
243 printf("[OK]\tAll is well.\n");