The Pedigree Project 0.1
prctl.c
1/*
2 * Copyright (c) 2026, Pedigree Developers
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted.
6 */
7
8#define _GNU_SOURCE
9#include <errno.h>
10#include <poll.h>
11#include <pthread.h>
12#include <signal.h>
13#include <stdint.h>
14#include <stdio.h>
15#include <stdlib.h>
16#include <string.h>
17#include <unistd.h>
18
19#include <sys/fsuid.h>
20#include <sys/prctl.h>
21#include <sys/wait.h>
22
23extern void fail(void) __attribute__((noreturn));
24
25static int death_ready[2], death_go[2], death_report[2];
26static int death_mode;
27struct death_notice {
28 int signal;
29 pid_t sender;
30};
31
32static void death_handler(int signal, siginfo_t* info, void* context) {
33 (void)context;
34 struct death_notice notice = {signal, info->si_pid};
35 (void)write(death_report[1], &notice, sizeof(notice));
36 _exit(0);
37}
38
39static void read_bounded(int fd, void* data, size_t size) {
40 struct pollfd pollfd = {fd, POLLIN, 0};
41 if (poll(&pollfd, 1, 5000) != 1 || read(fd, data, size) != (ssize_t)size) {
42 fail();
43 }
44}
45
46static void* death_creator(void* unused) {
47 (void)unused;
48 int setting = -1;
49 if (prctl(PR_GET_PDEATHSIG, &setting, 0UL, 0UL, 0UL) || setting) {
50 fail();
51 }
52 pid_t child = fork();
53 if (child < 0) {
54 fail();
55 }
56 if (!child) {
57 pid_t parent = getppid();
58 struct sigaction action = {.sa_sigaction = death_handler, .sa_flags = SA_SIGINFO};
59 sigemptyset(&action.sa_mask);
60 if (sigaction(SIGUSR1, &action, NULL)) {
61 fail();
62 }
63 if (death_mode != 2 && prctl(PR_SET_PDEATHSIG, SIGUSR1, 0UL, 0UL, 0UL)) {
64 fail();
65 }
66 if (death_mode == 1 && prctl(PR_SET_PDEATHSIG, 0UL, 0UL, 0UL, 0UL)) {
67 fail();
68 }
69 if (write(death_ready[1], "r", 1) != 1) {
70 fail();
71 }
72 if (!death_mode) {
73 for (;;) {
74 pause();
75 }
76 }
77 for (int i = 0; getppid() == parent && i < 1000; ++i) {
78 usleep(1000);
79 }
80 if (getppid() == parent ||
81 (death_mode == 2 && prctl(PR_SET_PDEATHSIG, SIGUSR1, 0UL, 0UL, 0UL))) {
82 fail();
83 }
84 usleep(100000);
85 struct death_notice notice = {0, parent};
86 if (write(death_report[1], &notice, sizeof(notice)) != sizeof(notice)) {
87 fail();
88 }
89 _exit(0);
90 }
91 char go;
92 read_bounded(death_go[0], &go, 1);
93 return (void*)(intptr_t)child;
94}
95
96static void test_creator_exit(int threaded, int mode) {
97 death_mode = mode;
98 if (pipe(death_ready) || pipe(death_go) || pipe(death_report)) {
99 fail();
100 }
101 pthread_t thread;
102 pid_t parent = getpid();
103 if (threaded) {
104 if (pthread_create(&thread, NULL, death_creator, NULL)) {
105 fail();
106 }
107 } else {
108 parent = fork();
109 if (parent < 0) {
110 fail();
111 }
112 if (!parent) {
113 death_creator(NULL);
114 _exit(0);
115 }
116 }
117 char ready;
118 read_bounded(death_ready[0], &ready, 1);
119 if (write(death_go[1], "g", 1) != 1) {
120 fail();
121 }
122 int status;
123 if (threaded) {
124 void* child;
125 if (pthread_join(thread, &child)) {
126 fail();
127 }
128 if (waitpid((pid_t)(intptr_t)child, &status, 0) < 0 || status) {
129 fail();
130 }
131 } else if (waitpid(parent, &status, 0) != parent || status) {
132 fail();
133 }
134 struct death_notice notice;
135 read_bounded(death_report[0], &notice, sizeof(notice));
136 if (notice.signal != (mode ? 0 : SIGUSR1) || notice.sender != parent) {
137 fail();
138 }
139 for (int i = 0; i < 2; ++i) {
140 close(death_ready[i]);
141 close(death_go[i]);
142 close(death_report[i]);
143 }
144}
145
146int test_prctl_exec(void) {
147 int signal = 0;
148 return prctl(PR_GET_PDEATHSIG, &signal, 0UL, 0UL, 0UL) || signal != SIGUSR1;
149}
150
151static void test_parent_death(void) {
152 int signal = -1;
153 if (prctl(PR_SET_PDEATHSIG, SIGUSR1, 0UL, 0UL, 0UL) ||
154 prctl(PR_GET_PDEATHSIG, &signal, 0UL, 0UL, 0UL) || signal != SIGUSR1) {
155 fail();
156 }
157 errno = 0;
158 if (prctl(PR_SET_PDEATHSIG, 65UL, 0UL, 0UL, 0UL) != -1 || errno != EINVAL) {
159 fail();
160 }
161 errno = 0;
162 if (prctl(PR_SET_PDEATHSIG, -1UL, 0UL, 0UL, 0UL) != -1 || errno != EINVAL) {
163 fail();
164 }
165 errno = 0;
166 if (prctl(PR_GET_PDEATHSIG, (void*)-1, 0UL, 0UL, 0UL) != -1 || errno != EFAULT) {
167 fail();
168 }
169 pid_t child = fork();
170 if (!child) {
171 if (prctl(PR_GET_PDEATHSIG, &signal, 0UL, 0UL, 0UL) || signal) {
172 _exit(1);
173 }
174 if (prctl(PR_SET_PDEATHSIG, SIGUSR1, 0UL, 0UL, 0UL)) {
175 _exit(2);
176 }
177 execl("/proc/self/exe", "testsuite", "--prctl-exec", NULL);
178 _exit(3);
179 }
180 int status;
181 if (child < 0 || waitpid(child, &status, 0) != child || status) {
182 fail();
183 }
184 // A new pthread has its own cleared setting while the main task stays armed.
185 test_creator_exit(1, 0);
186 if (prctl(PR_GET_PDEATHSIG, &signal, 0UL, 0UL, 0UL) || signal != SIGUSR1) {
187 fail();
188 }
189 child = fork();
190 if (!child) {
191 if (getuid() == 0) {
192 if (prctl(PR_SET_PDEATHSIG, SIGUSR1, 0UL, 0UL, 0UL)) {
193 _exit(4);
194 }
195 (void)setfsuid(65534);
196 if (prctl(PR_GET_PDEATHSIG, &signal, 0UL, 0UL, 0UL) || signal) {
197 _exit(5);
198 }
199 (void)setfsuid(0);
200 if (prctl(PR_SET_PDEATHSIG, SIGUSR1, 0UL, 0UL, 0UL) || setresuid(0, 65534, 0) ||
201 prctl(PR_GET_PDEATHSIG, &signal, 0UL, 0UL, 0UL) || signal) {
202 _exit(6);
203 }
204 }
205 _exit(0);
206 }
207 if (child < 0 || waitpid(child, &status, 0) != child || status) {
208 fail();
209 }
210 if (prctl(PR_SET_PDEATHSIG, 0UL, 0UL, 0UL, 0UL) ||
211 prctl(PR_GET_PDEATHSIG, &signal, 0UL, 0UL, 0UL) || signal) {
212 fail();
213 }
214 test_creator_exit(0, 0);
215 test_creator_exit(0, 1);
216 test_creator_exit(0, 2);
217 puts("PDEATH-CONTRACT PASS");
218}
219
220void test_prctl(void) {
221 puts("Testing prctl(2) thread names... ");
222 fflush(stdout);
223
224 char original[16] = {0};
225 char returned[16];
226 if (prctl(PR_GET_NAME, original, 0UL, 0UL, 0UL))
227 fail();
228
229 if (prctl(PR_SET_NAME, "0123456789abcdef-long", 0UL, 0UL, 0UL))
230 fail();
231 memset(returned, 0xA5, sizeof(returned));
232 if (prctl(PR_GET_NAME, returned, 0UL, 0UL, 0UL) || memcmp(returned, "0123456789abcde", 15) ||
233 returned[15])
234 fail();
235
236 errno = 0;
237 if (prctl(-1, 0UL, 0UL, 0UL, 0UL) != -1 || errno != EINVAL)
238 fail();
239 errno = 0;
240 if (prctl(PR_GET_NAME, (char*)-1, 0UL, 0UL, 0UL) != -1 || errno != EFAULT)
241 fail();
242 errno = 0;
243 if (prctl(PR_SET_NAME, (char*)-1, 0UL, 0UL, 0UL) != -1 || errno != EFAULT)
244 fail();
245
246 if (prctl(PR_SET_NAME, original, 0UL, 0UL, 0UL))
247 fail();
248 test_parent_death();
249 puts("OK\n");
250 fflush(stdout);
251}