Skip to content

Commit 795e2a0

Browse files
H. Peter Anvin (Intel)KAGA-KOKO
authored andcommitted
selftests/x86/syscall: Add tests under ptrace to syscall_numbering_64
Add tests running under ptrace for syscall_numbering_64. ptrace stopping on syscall entry and possibly modifying the syscall number (regs.orig_rax) or the default return value (regs.rax) can have different results than the normal system call path. Signed-off-by: H. Peter Anvin (Intel) <hpa@zytor.com> Signed-off-by: Thomas Gleixner <tglx@linutronix.de> Link: https://lore.kernel.org/r/20210518191303.4135296-4-hpa@zytor.com
1 parent c5c3948 commit 795e2a0

1 file changed

Lines changed: 207 additions & 25 deletions

File tree

tools/testing/selftests/x86/syscall_numbering.c

Lines changed: 207 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,16 @@
1616
#include <string.h>
1717
#include <fcntl.h>
1818
#include <limits.h>
19+
#include <signal.h>
1920
#include <sysexits.h>
2021

22+
#include <sys/ptrace.h>
23+
#include <sys/user.h>
24+
#include <sys/wait.h>
25+
#include <sys/mman.h>
26+
27+
#include <linux/ptrace.h>
28+
2129
/* Common system call numbers */
2230
#define SYS_READ 0
2331
#define SYS_WRITE 1
@@ -33,13 +41,45 @@
3341

3442
#define X32_BIT 0x40000000
3543

36-
static unsigned int nerr = 0; /* Cumulative error count */
3744
static int nullfd = -1; /* File descriptor for /dev/null */
38-
static int indent = 0;
45+
static bool with_x32; /* x32 supported on this kernel? */
46+
47+
enum ptrace_pass {
48+
PTP_NOTHING,
49+
PTP_GETREGS,
50+
PTP_WRITEBACK,
51+
PTP_FUZZRET,
52+
PTP_FUZZHIGH,
53+
PTP_INTNUM,
54+
PTP_DONE
55+
};
56+
57+
static const char * const ptrace_pass_name[] =
58+
{
59+
[PTP_NOTHING] = "just stop, no data read",
60+
[PTP_GETREGS] = "only getregs",
61+
[PTP_WRITEBACK] = "getregs, unmodified setregs",
62+
[PTP_FUZZRET] = "modifying the default return",
63+
[PTP_FUZZHIGH] = "clobbering the top 32 bits",
64+
[PTP_INTNUM] = "sign-extending the syscall number",
65+
};
66+
67+
/*
68+
* Shared memory block between tracer and test
69+
*/
70+
struct shared {
71+
unsigned int nerr; /* Total error count */
72+
unsigned int indent; /* Message indentation level */
73+
enum ptrace_pass ptrace_pass;
74+
bool probing_syscall; /* In probe_syscall() */
75+
};
76+
static volatile struct shared *sh;
3977

4078
static inline unsigned int offset(void)
4179
{
42-
return 8 + indent * 4;
80+
unsigned int level = sh ? sh->indent : 0;
81+
82+
return 8 + level * 4;
4383
}
4484

4585
#define msg(lvl, fmt, ...) printf("%-*s" fmt, offset(), "[" #lvl "]", \
@@ -52,24 +92,27 @@ static inline unsigned int offset(void)
5292
#define fail(fmt, ...) \
5393
do { \
5494
msg(FAIL, fmt, ## __VA_ARGS__); \
55-
nerr++; \
56-
} while (0)
95+
sh->nerr++; \
96+
} while (0)
5797

5898
#define crit(fmt, ...) \
5999
do { \
60-
indent = 0; \
100+
sh->indent = 0; \
61101
msg(FAIL, fmt, ## __VA_ARGS__); \
62102
msg(SKIP, "Unable to run test\n"); \
63-
exit(EX_OSERR);
64-
} while (0)
103+
exit(EX_OSERR); \
104+
} while (0)
105+
106+
/* Sentinel for ptrace-modified return value */
107+
#define MODIFIED_BY_PTRACE -9999
65108

66109
/*
67110
* Directly invokes the given syscall with nullfd as the first argument
68111
* and the rest zero. Avoids involving glibc wrappers in case they ever
69112
* end up intercepting some system calls for some reason, or modify
70113
* the system call number itself.
71114
*/
72-
static inline long long probe_syscall(int msb, int lsb)
115+
static long long probe_syscall(int msb, int lsb)
73116
{
74117
register long long arg1 asm("rdi") = nullfd;
75118
register long long arg2 asm("rsi") = 0;
@@ -80,11 +123,21 @@ static inline long long probe_syscall(int msb, int lsb)
80123
long long nr = ((long long)msb << 32) | (unsigned int)lsb;
81124
long long ret;
82125

126+
/*
127+
* We pass in an extra copy of the extended system call number
128+
* in %rbx, so we can examine it from the ptrace handler without
129+
* worrying about it being possibly modified. This is to test
130+
* the validity of struct user regs.orig_rax a.k.a.
131+
* struct pt_regs.orig_ax.
132+
*/
133+
sh->probing_syscall = true;
83134
asm volatile("syscall"
84135
: "=a" (ret)
85-
: "a" (nr), "r" (arg1), "r" (arg2), "r" (arg3),
136+
: "a" (nr), "b" (nr),
137+
"r" (arg1), "r" (arg2), "r" (arg3),
86138
"r" (arg4), "r" (arg5), "r" (arg6)
87139
: "rcx", "r11", "memory", "cc");
140+
sh->probing_syscall = false;
88141

89142
return ret;
90143
}
@@ -119,9 +172,9 @@ static unsigned int _check_for(int msb, int start, int end, long long expect,
119172
{
120173
unsigned int err = 0;
121174

122-
indent++;
175+
sh->indent++;
123176
if (start != end)
124-
indent++;
177+
sh->indent++;
125178

126179
for (int nr = start; nr <= end; nr++) {
127180
long long ret = probe_syscall(msb, nr);
@@ -135,20 +188,19 @@ static unsigned int _check_for(int msb, int start, int end, long long expect,
135188
}
136189

137190
if (start != end)
138-
indent--;
191+
sh->indent--;
139192

140193
if (err) {
141-
nerr += err;
142194
if (start != end)
143195
fail("%s had %u failure%s\n",
144-
syscall_str(msb, start, end),
145-
err, err == 1 ? "s" : "");
196+
syscall_str(msb, start, end),
197+
err, err == 1 ? "s" : "");
146198
} else {
147199
ok("%s returned %s as expected\n",
148200
syscall_str(msb, start, end), expect_str);
149201
}
150202

151-
indent--;
203+
sh->indent--;
152204

153205
return err;
154206
}
@@ -175,28 +227,29 @@ static bool test_x32(void)
175227
{
176228
long long ret;
177229
pid_t mypid = getpid();
178-
bool with_x32;
179230

180231
run("Checking for x32 by calling x32 getpid()\n");
181232
ret = probe_syscall(0, SYS_GETPID | X32_BIT);
182233

183-
indent++;
234+
sh->indent++;
184235
if (ret == mypid) {
185236
info("x32 is supported\n");
186237
with_x32 = true;
187238
} else if (ret == -ENOSYS) {
188239
info("x32 is not supported\n");
189240
with_x32 = false;
190241
} else {
191-
fail("x32 getpid() returned %lld, but it should have returned either %lld or -ENOSYS\n", ret, mypid);
242+
fail("x32 getpid() returned %lld, but it should have returned either %lld or -ENOSYS\n", ret, (long long)mypid);
192243
with_x32 = false;
193244
}
194-
indent--;
245+
sh->indent--;
195246
return with_x32;
196247
}
197248

198249
static void test_syscalls_common(int msb)
199250
{
251+
enum ptrace_pass pass = sh->ptrace_pass;
252+
200253
run("Checking some common syscalls as 64 bit\n");
201254
check_zero(msb, SYS_READ);
202255
check_zero(msb, SYS_WRITE);
@@ -206,7 +259,11 @@ static void test_syscalls_common(int msb)
206259
check_zero(msb, X64_WRITEV);
207260

208261
run("Checking out of range system calls\n");
209-
check_for(msb, -64, -1, -ENOSYS);
262+
check_for(msb, -64, -2, -ENOSYS);
263+
if (pass >= PTP_FUZZRET)
264+
check_for(msb, -1, -1, MODIFIED_BY_PTRACE);
265+
else
266+
check_for(msb, -1, -1, -ENOSYS);
210267
check_for(msb, X32_BIT-64, X32_BIT-1, -ENOSYS);
211268
check_for(msb, -64-X32_BIT, -1-X32_BIT, -ENOSYS);
212269
check_for(msb, INT_MAX-64, INT_MAX-1, -ENOSYS);
@@ -249,7 +306,8 @@ static void test_syscall_numbering(void)
249306
0, 1, -1, X32_BIT-1, X32_BIT, X32_BIT-1, -X32_BIT, INT_MAX,
250307
INT_MIN, INT_MIN+1
251308
};
252-
bool with_x32 = test_x32();
309+
310+
sh->indent++;
253311

254312
/*
255313
* The MSB is supposed to be ignored, so we loop over a few
@@ -260,20 +318,127 @@ static void test_syscall_numbering(void)
260318
run("Checking system calls with msb = %d (0x%x)\n",
261319
msb, msb);
262320

263-
indent++;
321+
sh->indent++;
264322

265323
test_syscalls_common(msb);
266324
if (with_x32)
267325
test_syscalls_with_x32(msb);
268326
else
269327
test_syscalls_without_x32(msb);
270328

271-
indent--;
329+
sh->indent--;
330+
}
331+
332+
sh->indent--;
333+
}
334+
335+
static void syscall_numbering_tracee(void)
336+
{
337+
enum ptrace_pass pass;
338+
339+
if (ptrace(PTRACE_TRACEME, 0, 0, 0)) {
340+
crit("Failed to request tracing\n");
341+
return;
342+
}
343+
raise(SIGSTOP);
344+
345+
for (sh->ptrace_pass = pass = PTP_NOTHING; pass < PTP_DONE;
346+
sh->ptrace_pass = ++pass) {
347+
run("Running tests under ptrace: %s\n", ptrace_pass_name[pass]);
348+
test_syscall_numbering();
349+
}
350+
}
351+
352+
static void mess_with_syscall(pid_t testpid, enum ptrace_pass pass)
353+
{
354+
struct user_regs_struct regs;
355+
356+
sh->probing_syscall = false; /* Do this on entry only */
357+
358+
/* For these, don't even getregs */
359+
if (pass == PTP_NOTHING || pass == PTP_DONE)
360+
return;
361+
362+
ptrace(PTRACE_GETREGS, testpid, NULL, &regs);
363+
364+
if (regs.orig_rax != regs.rbx) {
365+
fail("orig_rax %#llx doesn't match syscall number %#llx\n",
366+
(unsigned long long)regs.orig_rax,
367+
(unsigned long long)regs.rbx);
368+
}
369+
370+
switch (pass) {
371+
case PTP_GETREGS:
372+
/* Just read, no writeback */
373+
return;
374+
case PTP_WRITEBACK:
375+
/* Write back the same register state verbatim */
376+
break;
377+
case PTP_FUZZRET:
378+
regs.rax = MODIFIED_BY_PTRACE;
379+
break;
380+
case PTP_FUZZHIGH:
381+
regs.rax = MODIFIED_BY_PTRACE;
382+
regs.orig_rax = regs.orig_rax | 0xffffffff00000000ULL;
383+
break;
384+
case PTP_INTNUM:
385+
regs.rax = MODIFIED_BY_PTRACE;
386+
regs.orig_rax = (int)regs.orig_rax;
387+
break;
388+
default:
389+
crit("invalid ptrace_pass\n");
390+
break;
391+
}
392+
393+
ptrace(PTRACE_SETREGS, testpid, NULL, &regs);
394+
}
395+
396+
static void syscall_numbering_tracer(pid_t testpid)
397+
{
398+
int wstatus;
399+
400+
do {
401+
pid_t wpid = waitpid(testpid, &wstatus, 0);
402+
if (wpid < 0 && errno != EINTR)
403+
break;
404+
if (wpid != testpid)
405+
continue;
406+
if (!WIFSTOPPED(wstatus))
407+
break; /* Thread exited? */
408+
409+
if (sh->probing_syscall && WSTOPSIG(wstatus) == SIGTRAP)
410+
mess_with_syscall(testpid, sh->ptrace_pass);
411+
} while (sh->ptrace_pass != PTP_DONE &&
412+
!ptrace(PTRACE_SYSCALL, testpid, NULL, NULL));
413+
414+
ptrace(PTRACE_DETACH, testpid, NULL, NULL);
415+
416+
/* Wait for the child process to terminate */
417+
while (waitpid(testpid, &wstatus, 0) != testpid || !WIFEXITED(wstatus))
418+
/* wait some more */;
419+
}
420+
421+
static void test_traced_syscall_numbering(void)
422+
{
423+
pid_t testpid;
424+
425+
/* Launch the test thread; this thread continues as the tracer thread */
426+
testpid = fork();
427+
428+
if (testpid < 0) {
429+
crit("Unable to launch tracer process\n");
430+
} else if (testpid == 0) {
431+
syscall_numbering_tracee();
432+
_exit(0);
433+
} else {
434+
syscall_numbering_tracer(testpid);
272435
}
273436
}
274437

275438
int main(void)
276439
{
440+
unsigned int nerr;
441+
277442
/*
278443
* It is quite likely to get a segfault on a failure, so make
279444
* sure the message gets out by setting stdout to nonbuffered.
@@ -288,7 +453,24 @@ int main(void)
288453
crit("Unable to open /dev/null: %s\n", strerror(errno));
289454
}
290455

456+
/*
457+
* Set up a block of shared memory...
458+
*/
459+
sh = mmap(NULL, sysconf(_SC_PAGE_SIZE), PROT_READ|PROT_WRITE,
460+
MAP_ANONYMOUS|MAP_SHARED, 0, 0);
461+
if (sh == MAP_FAILED) {
462+
crit("Unable to allocated shared memory block: %s\n",
463+
strerror(errno));
464+
}
465+
466+
with_x32 = test_x32();
467+
468+
run("Running tests without ptrace...\n");
291469
test_syscall_numbering();
470+
471+
test_traced_syscall_numbering();
472+
473+
nerr = sh->nerr;
292474
if (!nerr) {
293475
ok("All system calls succeeded or failed as expected\n");
294476
return 0;

0 commit comments

Comments
 (0)