The Pedigree Project 0.1
ptrace-contract-test/lifecycle.c
1#define _GNU_SOURCE
2#include <fcntl.h>
3#include <string.h>
4#include <unistd.h>
5
6#include "contract.h"
7
8static int kill_stopped(void) {
9 int failed = 0, status;
10 struct tc_child child = TC_CHILD_INIT;
11 struct user_regs_struct registers;
12 siginfo_t info;
13 CHECK(tc_spawn(&child, 1) == 0 && tc_stop(&child, SIGUSR1) == 0);
14 pid_t pid = child.pid;
15 CHECK(kill(pid, SIGKILL) == 0);
16 CHECK(tc_wait(pid, &status, 10000) == 0);
17 if (WIFEXITED(status) || WIFSIGNALED(status))
18 child.pid = -1;
19 CHECK(WIFSIGNALED(status) && WTERMSIG(status) == SIGKILL);
20 errno = 0;
21 CHECK(wait4(pid, NULL, WNOHANG, NULL) == -1 && errno == ECHILD);
22 errno = 0;
23 CHECK(ptrace(PTRACE_GETREGS, pid, (void*)0, (void*)&registers) == -1 && errno == ESRCH);
24 errno = 0;
25 CHECK(ptrace(PTRACE_GETSIGINFO, pid, (void*)0, (void*)&info) == -1 && errno == ESRCH);
26out:
27 tc_close(&child);
28 return failed;
29}
30
31static int tracee_exec(int raw_entry) {
32 int failed = 0, status;
33 struct tc_child child = TC_CHILD_INIT;
34 struct user_regs_struct old, entry, repeated;
35 struct tc_message message;
36 struct tc_entry_record actual;
37 siginfo_t info;
38 CHECK(tc_spawn(&child, 1) == 0 && tc_stop(&child, SIGUSR1) == 0);
39 CHECK(ptrace(PTRACE_GETREGS, child.pid, (void*)0, (void*)&old) == 0);
40 CHECK(tc_resume(child.pid, PTRACE_CONT, 0) == 0 && tc_receive(child.report, 'A', NULL) == 0);
41 CHECK(tc_send(&child, raw_entry ? 'J' : 'X', 0) == 0);
42 CHECK(tc_wait_stop(&child, SIGTRAP) == 0);
43 CHECK(ptrace(PTRACE_GETREGS, child.pid, (void*)0, (void*)&entry) == 0);
44 CHECK(ptrace(PTRACE_GETREGS, child.pid, (void*)0, (void*)&repeated) == 0);
45 CHECK(memcmp(&entry, &repeated, sizeof(entry)) == 0);
46 CHECK(entry.rip && entry.rsp && entry.rip != old.rip);
47 CHECK(ptrace(PTRACE_GETSIGINFO, child.pid, (void*)0, (void*)&info) == 0);
48 CHECK(info.si_signo == SIGTRAP && !info.si_errno);
49 CHECK(tc_resume(child.pid, PTRACE_CONT, 0) == 0);
50 if (raw_entry) {
51 CHECK(tc_read(child.report, &actual, sizeof(actual)) == 0);
52 CHECK(actual.magic == TC_ENTRY_MAGIC_VALUE && actual.fs_result == 0);
53 unsigned long stopped[27], resumed[27];
54 memcpy(stopped, &entry, sizeof(stopped));
55 memcpy(resumed, &actual.registers, sizeof(resumed));
56 for (size_t i = 0; i < 27; ++i) {
57 // orig_rax is ABI provenance; GS-base truth belongs to the core fixture.
58 if (i == TC_ORIG_RAX / 8 || i == TC_REG_GS_BASE / 8)
59 continue;
60 if (stopped[i] != resumed[i])
61 fprintf(stderr, "exec entry register[%zu] stop=%#lx resumed=%#lx\n", i, stopped[i],
62 resumed[i]);
63 CHECK(stopped[i] == resumed[i]);
64 }
65 CHECK(entry.orig_rax == SYS_execve);
66 } else {
67 CHECK(tc_receive(child.report, 'X', &message) == 0);
68 CHECK(message.pid == child.pid && message.value == TC_IMAGE_TLS);
69 }
70 CHECK(tc_wait(child.pid, &status, 10000) == 0);
71 if (WIFEXITED(status) || WIFSIGNALED(status))
72 child.pid = -1;
73 CHECK(WIFEXITED(status) && WEXITSTATUS(status) == 0);
74out:
75 tc_close(&child);
76 return failed;
77}
78
79int tc_exec_tracer(pid_t pid, int command, int report) {
80 int failed = 0;
81 struct tc_child child = {pid, command, report};
82 struct user_regs_struct registers;
83 siginfo_t info;
84 alarm(20);
85 CHECK(ptrace(PTRACE_GETREGS, pid, (void*)0, (void*)&registers) == 0);
86 CHECK(registers.rip && registers.rsp);
87 CHECK(ptrace(PTRACE_GETSIGINFO, pid, (void*)0, (void*)&info) == 0);
88 CHECK(tc_signal_info(&info, SIGUSR1, SI_TKILL, pid, getuid()) == 0);
89 CHECK(tc_resume(pid, PTRACE_CONT, 0) == 0 && tc_receive(report, 'A', NULL) == 0);
90 CHECK(tc_finish(&child) == 0);
91out:
92 tc_close(&child);
93 return failed;
94}
95
96static int tracer_exec(void) {
97 int failed = 0;
98 struct tc_child child = TC_CHILD_INIT;
99 char pid[24], command[24], report[24];
100 CHECK(tc_spawn(&child, 1) == 0 && tc_stop(&child, SIGUSR1) == 0);
101 CHECK(fcntl(child.command, F_SETFD, 0) == 0 && fcntl(child.report, F_SETFD, 0) == 0);
102 snprintf(pid, sizeof(pid), "%d", child.pid);
103 snprintf(command, sizeof(command), "%d", child.command);
104 snprintf(report, sizeof(report), "%d", child.report);
105 execl(TC_EXECUTABLE, TC_EXECUTABLE, "--exec-tracer", pid, command, report, (char*)NULL);
106 CHECK(0);
107out:
108 tc_close(&child);
109 return failed;
110}
111
112int tc_lifecycle(void) {
113 return kill_stopped() || tracee_exec(1) || tracee_exec(0) || tracer_exec();
114}