The Pedigree Project 0.1
execveat-contract-test/main.c
1/* Copyright (c) 2026, Pedigree Developers. */
2#define _GNU_SOURCE
3#include <dirent.h>
4#include <errno.h>
5#include <fcntl.h>
6#include <limits.h>
7#include <stdio.h>
8#include <stdlib.h>
9#include <string.h>
10#include <unistd.h>
11
12#include <sys/auxv.h>
13#include <sys/stat.h>
14#include <sys/syscall.h>
15#include <sys/wait.h>
16
17#define CHECK(expression) \
18 do { \
19 if (!(expression)) { \
20 fprintf(stderr, "EXECVEAT-CONTRACT: FAIL line=%d errno=%d\n", __LINE__, errno); \
21 return 1; \
22 } \
23 } while (0)
24
25extern char** environ;
26static char self[PATH_MAX];
27
28static int invoke(int fd, const char* path, int flags, char** arguments) {
29 return syscall(SYS_execveat, fd, path, arguments, environ, flags);
30}
31
32static int run(int fd, const char* path, int flags, int script, int closefd) {
33 char descriptor[32], expected[PATH_MAX + 64];
34 snprintf(descriptor, sizeof(descriptor), "%d", closefd);
35 if (path[0] == '/' || fd == AT_FDCWD)
36 snprintf(expected, sizeof(expected), "%s", path);
37 else
38 snprintf(expected, sizeof(expected), "/dev/fd/%d%s%s", fd, *path ? "/" : "", path);
39 char* arguments[] = {"chosen-argv-zero", script ? expected : "--elf-child", descriptor,
40 script ? NULL : expected, NULL};
41 pid_t child = fork();
42 CHECK(child >= 0);
43 if (!child) {
44 invoke(fd, path, flags, arguments);
45 fprintf(stderr, "EXECVEAT-CONTRACT: exec failed errno=%d path=%s\n", errno, expected);
46 _exit(80);
47 }
48 int status;
49 CHECK(waitpid(child, &status, 0) == child);
50 CHECK(WIFEXITED(status) && WEXITSTATUS(status) == 0);
51 return 0;
52}
53
54static int copy_image(const char* path) {
55 int source = open(self, O_RDONLY), target = open(path, O_CREAT | O_EXCL | O_WRONLY, 0700);
56 CHECK(source >= 0 && target >= 0);
57 char buffer[8192];
58 ssize_t count;
59 while ((count = read(source, buffer, sizeof(buffer))) > 0) {
60 ssize_t written = 0;
61 while (written < count) {
62 ssize_t n = write(target, buffer + written, count - written);
63 CHECK(n > 0);
64 written += n;
65 }
66 }
67 CHECK(count == 0 && close(source) == 0 && close(target) == 0);
68 return 0;
69}
70
71static int descriptor_paths(const char* image) {
72 int fd = open(image, O_RDONLY);
73 CHECK(fd >= 0);
74 char proc[64], dev[64], name[32];
75 snprintf(proc, sizeof(proc), "/proc/self/fd/%d", fd);
76 snprintf(dev, sizeof(dev), "/dev/fd/%d", fd);
77 snprintf(name, sizeof(name), "%d", fd);
78 struct stat backing, link, alias;
79 CHECK(fstat(fd, &backing) == 0 && lstat(proc, &link) == 0 && S_ISLNK(link.st_mode));
80 CHECK(stat(proc, &alias) == 0 && alias.st_ino == backing.st_ino &&
81 alias.st_dev == backing.st_dev);
82 CHECK(stat(dev, &alias) == 0 && alias.st_ino == backing.st_ino && alias.st_dev == backing.st_dev);
83 int linkfd = open(proc, O_PATH | O_NOFOLLOW);
84 CHECK(linkfd >= 0 && fstat(linkfd, &link) == 0 && S_ISLNK(link.st_mode));
85 CHECK(open(proc, O_RDONLY | O_NOFOLLOW) == -1 && errno == ELOOP);
86 char byte;
87 CHECK(read(linkfd, &byte, 1) == -1 && errno == EBADF);
88 CHECK(fcntl(linkfd, F_SETFL, O_RDONLY) == -1 && errno == EBADF);
89 CHECK(fcntl(linkfd, F_GETFL) & O_PATH);
90 char target[PATH_MAX];
91 CHECK(readlinkat(linkfd, "", target, sizeof(target)) > 0);
92 CHECK(syscall(SYS_readlink, proc, target, 0) == -1 && errno == EINVAL);
93 CHECK(readlink(proc, target, 0) == 0);
94 DIR* directory = opendir("/proc/self/fd");
95 CHECK(directory);
96 int found = 0;
97 struct dirent* entry;
98 while ((entry = readdir(directory)))
99 found |= !strcmp(entry->d_name, name);
100 CHECK(found && closedir(directory) == 0);
101 CHECK(close(fd) == 0);
102 CHECK(lstat(proc, &link) == -1 && errno == ENOENT);
103 int replacement = open(image, O_RDONLY);
104 CHECK(replacement >= 0);
105 if (replacement != fd) {
106 CHECK(dup2(replacement, fd) == fd && close(replacement) == 0);
107 }
108 CHECK(stat(proc, &alias) == 0 && alias.st_ino == backing.st_ino);
109 CHECK(close(fd) == 0 && close(linkfd) == 0);
110 return 0;
111}
112
113static int contracts(void) {
114 char directory[] = "/tmp/execveat-contract-XXXXXX";
115 CHECK(mkdtemp(directory));
116 char image[PATH_MAX], script[PATH_MAX], link[PATH_MAX], moved[PATH_MAX];
117 snprintf(image, sizeof(image), "%s/image", directory);
118 snprintf(script, sizeof(script), "%s/script", directory);
119 snprintf(link, sizeof(link), "%s/link", directory);
120 snprintf(moved, sizeof(moved), "%s-renamed", directory);
121 CHECK(copy_image(image) == 0 && descriptor_paths(image) == 0);
122 int dirfd = open(directory, O_PATH | O_DIRECTORY);
123 int executable = open(image, O_PATH | O_CLOEXEC);
124 CHECK(dirfd >= 0 && executable >= 0);
125 CHECK(run(-1, image, 0, 0, -1) == 0);
126 CHECK(run(dirfd, "image", 0, 0, -1) == 0);
127 CHECK(unlink(image) == 0);
128 CHECK(run(executable, "", AT_EMPTY_PATH, 0, executable) == 0);
129 char* arguments[] = {"failure", NULL};
130 CHECK(invoke(executable, "", 0, arguments) == -1 && errno == ENOENT);
131 CHECK(invoke(-1, "", AT_EMPTY_PATH, arguments) == -1 && errno == EBADF);
132 CHECK(invoke(executable, "child", 0, arguments) == -1 && errno == ENOTDIR);
133 CHECK(invoke(dirfd, "", AT_EMPTY_PATH, arguments) == -1 && errno == EACCES);
134 CHECK(invoke(dirfd, "script", 0x40000000, arguments) == -1 && errno == EINVAL);
135 CHECK(invoke(dirfd, (void*)1, 0, arguments) == -1 && errno == EFAULT);
136 CHECK(close(executable) == 0);
137
138 int output = open(script, O_CREAT | O_EXCL | O_WRONLY, 0700);
139 CHECK(output >= 0);
140 char contents[PATH_MAX + 64];
141 int length = snprintf(contents, sizeof(contents), "#!%s --script-child\nscript-payload\n", self);
142 CHECK(write(output, contents, length) == length && close(output) == 0);
143 CHECK(symlink("script", link) == 0);
144 CHECK(invoke(dirfd, "link", AT_SYMLINK_NOFOLLOW, arguments) == -1 && errno == ELOOP);
145 int symlinkfd = open(link, O_PATH | O_NOFOLLOW);
146 CHECK(symlinkfd >= 0);
147 CHECK(invoke(symlinkfd, "", AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW, arguments) == -1 &&
148 errno == ELOOP);
149 CHECK(close(symlinkfd) == 0);
150 CHECK(run(dirfd, "link", 0, 1, -1) == 0);
151 int source = open(script, O_RDONLY | O_CLOEXEC);
152 CHECK(source >= 0);
153 CHECK(invoke(source, "", AT_EMPTY_PATH, arguments) == -1 && errno == ENOENT);
154 CHECK(fcntl(source, F_GETFD) & FD_CLOEXEC);
155 CHECK(fcntl(source, F_SETFD, 0) == 0);
156 CHECK(rename(directory, moved) == 0);
157 CHECK(run(dirfd, "script", 0, 1, -1) == 0);
158 CHECK(fcntl(dirfd, F_SETFD, FD_CLOEXEC) == 0);
159 CHECK(invoke(dirfd, "script", 0, arguments) == -1 && errno == ENOENT);
160 CHECK(fcntl(dirfd, F_SETFD, 0) == 0);
161 CHECK(unlinkat(dirfd, "script", 0) == 0);
162 CHECK(run(source, "", AT_EMPTY_PATH, 1, -1) == 0);
163 CHECK(unlinkat(dirfd, "link", 0) == 0);
164 CHECK(close(source) == 0 && close(dirfd) == 0 && rmdir(moved) == 0);
165 return 0;
166}
167
168static int executable_upgrade(void) {
169 char directory[] = "/tmp/exec-upgrade-XXXXXX";
170 CHECK(mkdtemp(directory));
171 char image[PATH_MAX], replacement[PATH_MAX];
172 snprintf(image, sizeof(image), "%s/image", directory);
173 snprintf(replacement, sizeof(replacement), "%s/replacement", directory);
174 CHECK(copy_image(image) == 0);
175 int ready[2], resume[2];
176 CHECK(pipe(ready) == 0 && pipe(resume) == 0);
177 pid_t child = fork();
178 CHECK(child >= 0);
179 if (!child) {
180 close(ready[0]);
181 close(resume[1]);
182 char ready_fd[32], resume_fd[32];
183 snprintf(ready_fd, sizeof(ready_fd), "%d", ready[1]);
184 snprintf(resume_fd, sizeof(resume_fd), "%d", resume[0]);
185 execl(image, image, "--upgrade-child", ready_fd, resume_fd, NULL);
186 _exit(80);
187 }
188 close(ready[1]);
189 close(resume[0]);
190 char byte;
191 CHECK(read(ready[0], &byte, 1) == 1);
192 int old = open(image, O_RDONLY);
193 struct stat before, after;
194 CHECK(old >= 0 && fstat(old, &before) == 0);
195 int staged = open(replacement, O_CREAT | O_EXCL | O_WRONLY, 0700);
196 static const char script[] = "#!/bin/sh\nexit 23\n";
197 CHECK(staged >= 0 && write(staged, script, sizeof(script) - 1) == sizeof(script) - 1);
198 CHECK(close(staged) == 0 && rename(replacement, image) == 0);
199 CHECK(stat(image, &after) == 0 && before.st_ino != after.st_ino);
200 char magic[4];
201 CHECK(read(old, magic, sizeof(magic)) == sizeof(magic) && !memcmp(magic, "\177ELF", 4));
202 CHECK(close(old) == 0 && write(resume[1], "x", 1) == 1);
203 int status;
204 CHECK(waitpid(child, &status, 0) == child && WIFEXITED(status) && WEXITSTATUS(status) == 42);
205 close(ready[0]);
206 close(resume[1]);
207 child = fork();
208 CHECK(child >= 0);
209 if (!child) {
210 execl(image, image, NULL);
211 _exit(80);
212 }
213 CHECK(waitpid(child, &status, 0) == child && WIFEXITED(status) && WEXITSTATUS(status) == 23);
214 CHECK(unlink(image) == 0 && rmdir(directory) == 0);
215 puts("EXECUTABLE-UPGRADE: PASS");
216 return 0;
217}
218
219int main(int argc, char** argv) {
220 setvbuf(stdout, NULL, _IONBF, 0);
221 alarm(30);
222 if (argc == 4 && !strcmp(argv[1], "--upgrade-child")) {
223 int ready = atoi(argv[2]), resume = atoi(argv[3]);
224 char byte;
225 CHECK(write(ready, "x", 1) == 1 && read(resume, &byte, 1) == 1);
226 return 42;
227 }
228 if (argc >= 2 && !strcmp(argv[1], "--elf-child")) {
229 CHECK(argc == 4);
230 int fd = atoi(argv[2]);
231 if (fd >= 0)
232 CHECK(fcntl(fd, F_GETFD) == -1 && errno == EBADF);
233 CHECK(!strcmp((const char*)getauxval(AT_EXECFN), argv[3]));
234 return 0;
235 }
236 if (argc >= 2 && !strcmp(argv[1], "--script-child")) {
237 CHECK(argc == 5 && !strcmp(argv[2], argv[3]));
238 int fd = open(argv[2], O_RDONLY);
239 CHECK(fd >= 0);
240 char contents[PATH_MAX + 64];
241 ssize_t count = read(fd, contents, sizeof(contents) - 1);
242 CHECK(count > 0);
243 contents[count] = 0;
244 CHECK(strstr(contents, "\nscript-payload\n") && close(fd) == 0);
245 return 0;
246 }
247 CHECK(realpath(argv[0], self));
248 CHECK(contracts() == 0);
249 CHECK(executable_upgrade() == 0);
250 puts("EXECVEAT-CONTRACT: PASS");
251 return 0;
252}