The Pedigree Project 0.1
session-contract-test/main.c
1/* Copyright (c) 2026, Pedigree Developers. */
2#define _GNU_SOURCE
3#include <errno.h>
4#include <pthread.h>
5#include <signal.h>
6#include <stdio.h>
7#include <stdlib.h>
8#include <string.h>
9#include <unistd.h>
10
11#include <sys/syscall.h>
12#include <sys/wait.h>
13
14#define CHECK(x) \
15 do { \
16 if (!(x)) { \
17 fprintf(stderr, "SESSION-CONTRACT: FAIL line=%d errno=%d\n", __LINE__, errno); \
18 return 1; \
19 } \
20 } while (0)
21
22static int finish(pid_t child) {
23 int status;
24 return waitpid(child, &status, 0) == child && WIFEXITED(status) && WEXITSTATUS(status) == 0;
25}
26
27static int inheritance(void) {
28 pid_t sid = getsid(0), pgid = getpgrp();
29 CHECK(sid > 0 && pgid > 0 && getsid(getpid()) == sid && getpgid(getpid()) == pgid);
30 int ready[2], release[2];
31 CHECK(pipe(ready) == 0 && pipe(release) == 0);
32 pid_t child = fork();
33 CHECK(child >= 0);
34 if (!child) {
35 close(ready[0]);
36 close(release[1]);
37 char byte = 'x';
38 if (getsid(0) != sid || getpgrp() != pgid || getpgid(getppid()) != pgid ||
39 write(ready[1], &byte, 1) != 1 || read(release[0], &byte, 1) != 1)
40 _exit(1);
41 _exit(0);
42 }
43 close(ready[1]);
44 close(release[0]);
45 char byte;
46 CHECK(read(ready[0], &byte, 1) == 1);
47 CHECK(getsid(child) == sid && getpgid(child) == pgid);
48 CHECK(setpgid(child, child) == 0 && getpgid(child) == child && getsid(child) == sid);
49 CHECK(write(release[1], &byte, 1) == 1 && finish(child));
50 close(ready[0]);
51 close(ready[1]);
52 close(release[0]);
53 close(release[1]);
54 errno = 0;
55 CHECK(getsid(-1) == -1 && errno == ESRCH);
56 errno = 0;
57 CHECK(getsid(child) == -1 && errno == ESRCH);
58 puts("SESSION-CONTRACT: PASS inheritance");
59 return 0;
60}
61
62static int independent_session(void) {
63 pid_t oldGroup = getpgrp();
64 pid_t child = fork();
65 CHECK(child >= 0);
66 if (!child) {
67 pid_t me = getpid();
68 if (setsid() != me || getsid(0) != me || getpgrp() != me)
69 _exit(1);
70 errno = 0;
71 if (setsid() != -1 || errno != EPERM)
72 _exit(2);
73 errno = 0;
74 if (setpgid(0, 0) != -1 || errno != EPERM)
75 _exit(3);
76 pid_t member = fork();
77 if (member < 0)
78 _exit(4);
79 if (!member) {
80 if (getsid(0) != me || getpgrp() != me)
81 _exit(5);
82 errno = 0;
83 if (setpgid(0, oldGroup) != -1 || errno != EPERM)
84 _exit(6);
85 if (setpgid(0, 0) || getpgrp() != getpid() || getsid(0) != me)
86 _exit(7);
87 _exit(0);
88 }
89 _exit(finish(member) ? 0 : 8);
90 }
91 CHECK(finish(child));
92 puts("SESSION-CONTRACT: PASS independent-session");
93 return 0;
94}
95
96static int leader_exit(void) {
97 int ready[2], release[2];
98 CHECK(pipe(ready) == 0 && pipe(release) == 0);
99 pid_t leader = fork();
100 CHECK(leader >= 0);
101 if (!leader) {
102 close(ready[0]);
103 close(release[1]);
104 if (setsid() != getpid())
105 _exit(1);
106 pid_t member = fork();
107 if (member < 0)
108 _exit(2);
109 if (!member) {
110 pid_t me = getpid();
111 char byte;
112 if (write(ready[1], &me, sizeof(me)) != sizeof(me) || read(release[0], &byte, 1) != 1)
113 _exit(3);
114 _exit(0);
115 }
116 _exit(0);
117 }
118 close(ready[1]);
119 close(release[0]);
120 pid_t member;
121 CHECK(read(ready[0], &member, sizeof(member)) == sizeof(member));
122 CHECK(finish(leader));
123 CHECK(getsid(member) == leader && getpgid(member) == leader);
124 char byte = 'x';
125 CHECK(write(release[1], &byte, 1) == 1);
126 close(ready[0]);
127 close(ready[1]);
128 close(release[0]);
129 close(release[1]);
130 puts("SESSION-CONTRACT: PASS leader-exit");
131 return 0;
132}
133
134static int exec_boundary(const char* program) {
135 int ready[2], release[2];
136 CHECK(pipe(ready) == 0 && pipe(release) == 0);
137 pid_t child = fork();
138 CHECK(child >= 0);
139 if (!child) {
140 close(ready[0]);
141 close(release[1]);
142 char send[24], receive[24];
143 snprintf(send, sizeof(send), "%d", ready[1]);
144 snprintf(receive, sizeof(receive), "%d", release[0]);
145 execl(program, program, "--exec-child", send, receive, (char*)0);
146 _exit(127);
147 }
148 close(ready[1]);
149 close(release[0]);
150 char byte;
151 CHECK(read(ready[0], &byte, 1) == 1);
152 errno = 0;
153 CHECK(setpgid(child, child) == -1 && errno == EACCES);
154 CHECK(write(release[1], &byte, 1) == 1 && finish(child));
155 close(ready[0]);
156 close(ready[1]);
157 close(release[0]);
158 close(release[1]);
159 puts("SESSION-CONTRACT: PASS exec-boundary");
160 return 0;
161}
162
163static int direct_child_only(void) {
164 int ready[2], release[2];
165 CHECK(pipe(ready) == 0 && pipe(release) == 0);
166 pid_t child = fork();
167 CHECK(child >= 0);
168 if (!child) {
169 close(ready[0]);
170 close(release[1]);
171 pid_t grandchild = fork();
172 if (grandchild < 0)
173 _exit(1);
174 if (!grandchild) {
175 pid_t me = getpid();
176 char byte;
177 if (write(ready[1], &me, sizeof(me)) != sizeof(me) || read(release[0], &byte, 1) != 1)
178 _exit(2);
179 _exit(0);
180 }
181 _exit(finish(grandchild) ? 0 : 3);
182 }
183 close(ready[1]);
184 close(release[0]);
185 pid_t grandchild;
186 CHECK(read(ready[0], &grandchild, sizeof(grandchild)) == sizeof(grandchild));
187 errno = 0;
188 CHECK(setpgid(grandchild, grandchild) == -1 && errno == ESRCH);
189 char byte = 'x';
190 CHECK(write(release[1], &byte, 1) == 1 && finish(child));
191 close(ready[0]);
192 close(ready[1]);
193 close(release[0]);
194 close(release[1]);
195 puts("SESSION-CONTRACT: PASS direct-child-only");
196 return 0;
197}
198
199#ifndef __APPLE__
200static void* thread_identity(void* unused) {
201 (void)unused;
202 const pid_t tid = syscall(SYS_gettid);
203 if (tid == getpid() || getsid(tid) != getsid(0) || getpgid(tid) != getpgrp())
204 return (void*)1;
205 errno = 0;
206 return setpgid(tid, 0) == -1 && errno == EINVAL ? NULL : (void*)2;
207}
208#endif
209
210static int task_identity(void) {
211#ifdef __APPLE__
212 puts("SESSION-CONTRACT: SKIP Linux task IDs on native host");
213#else
214 pthread_t thread;
215 void* result;
216 CHECK(pthread_create(&thread, NULL, thread_identity, NULL) == 0);
217 CHECK(pthread_join(thread, &result) == 0 && result == NULL);
218 puts("SESSION-CONTRACT: PASS task-identity");
219#endif
220 return 0;
221}
222
223int main(int argc, char** argv) {
224 setvbuf(stdout, NULL, _IONBF, 0);
225 alarm(45);
226 if (argc == 4 && !strcmp(argv[1], "--exec-child")) {
227 char byte = 'x';
228 return write(atoi(argv[2]), &byte, 1) != 1 || read(atoi(argv[3]), &byte, 1) != 1;
229 }
230 const int portable = argc == 2 && !strcmp(argv[1], "--portable-host");
231 if (portable)
232 puts("SESSION-CONTRACT: SKIP Linux direct-child-only rule on native host");
233 if (inheritance() || independent_session() || leader_exit() || exec_boundary(argv[0]) ||
234 (!portable && direct_child_only()) || task_identity())
235 return 1;
236 puts("SESSION-CONTRACT: END PASS");
237 return 0;
238}