The Pedigree Project 0.1
metadata-contract-test/main.c
1#define _GNU_SOURCE
2#include <errno.h>
3#include <fcntl.h>
4#include <grp.h>
5#include <limits.h>
6#include <stdint.h>
7#include <stdio.h>
8#include <stdlib.h>
9#include <string.h>
10#include <time.h>
11#include <unistd.h>
12
13#include <sys/mman.h>
14#include <sys/stat.h>
15#include <sys/syscall.h>
16#include <sys/sysmacros.h>
17#include <sys/wait.h>
18
19#define CHECK(expression) \
20 do { \
21 if (!(expression)) { \
22 fprintf(stderr, "METADATA-CONTRACT: line=%d errno=%d\n", __LINE__, errno); \
23 return 0; \
24 } \
25 } while (0)
26#define ERROR(expression, expected) \
27 do { \
28 errno = 0; \
29 CHECK((expression) == -1 && errno == (expected)); \
30 } while (0)
31
32_Static_assert(SYS_truncate == 76 && SYS_lchown == 94 && SYS_mknodat == 259 &&
33 SYS_fchmodat == 268 && SYS_utimensat == 280 && SYS_statx == 332 &&
34 SYS_fchmodat2 == 452,
35 "Linux amd64 metadata routes");
36_Static_assert(sizeof(struct statx) == 256, "statx output ABI");
37
38static int make_file(int directory, const char* name, mode_t mode) {
39 int fd = openat(directory, name, O_CREAT | O_EXCL | O_RDWR, mode);
40 if (fd < 0)
41 return 0;
42 return close(fd) == 0;
43}
44
45static int truncate_cases(int directory, const char* absolute) {
46 unsigned char written[8192], readback[8192];
47 struct stat st;
48 int fd = openat(directory, "data", O_RDWR);
49 CHECK(fd >= 0);
50 memset(written, 0xa7, sizeof(written));
51 CHECK(write(fd, written, sizeof(written)) == sizeof(written));
52 CHECK(lseek(fd, 5000, SEEK_SET) == 5000);
53 CHECK(truncate(absolute, 4111) == 0);
54 CHECK(fstat(fd, &st) == 0 && st.st_size == 4111 && lseek(fd, 0, SEEK_CUR) == 5000);
55 CHECK(syscall(SYS_truncate, absolute, (off_t)sizeof(written)) == 0);
56 CHECK(pread(fd, readback, sizeof(readback), 0) == sizeof(readback));
57 for (size_t i = 0; i < sizeof(readback); ++i)
58 CHECK(readback[i] == (i < 4111 ? 0xa7 : 0));
59 CHECK(lseek(fd, 0, SEEK_CUR) == 5000);
60 ERROR(truncate(absolute, -1), EINVAL);
61 ERROR(syscall(SYS_truncate, (const char*)1, (off_t)1), EFAULT);
62 char absent[PATH_MAX];
63 snprintf(absent, sizeof(absent), "%s-missing", absolute);
64 ERROR(truncate(absent, 0), ENOENT);
65 CHECK(close(fd) == 0);
66 return 1;
67}
68
69static int mode_cases(int directory, int object, int link, const char* absolute) {
70 struct stat st;
71 CHECK(syscall(SYS_fchmodat, directory, "data", 0620, 0x40000000) == 0);
72 CHECK(fstat(object, &st) == 0 && (st.st_mode & 07777) == 0620);
73 CHECK(syscall(SYS_fchmodat2, directory, "data", 0640, 0) == 0);
74 CHECK(fchmodat(directory, "data", 0604, AT_SYMLINK_NOFOLLOW) == 0);
75 CHECK(fstat(object, &st) == 0 && (st.st_mode & 07777) == 0604);
76 CHECK(syscall(SYS_fchmodat2, object, "", 0600, AT_EMPTY_PATH) == 0);
77 CHECK(syscall(SYS_fchmodat2, directory, "link", 0660, 0) == 0);
78 CHECK(fstat(object, &st) == 0 && (st.st_mode & 07777) == 0660);
79 CHECK(syscall(SYS_fchmodat2, -123, absolute, 0640, 0) == 0);
80 ERROR(syscall(SYS_fchmodat2, directory, "data", 0700, 0x40000000), EINVAL);
81 ERROR(syscall(SYS_fchmodat2, -123, "data", 0700, 0), EBADF);
82 ERROR(syscall(SYS_fchmodat2, object, "data", 0700, 0), ENOTDIR);
83 ERROR(syscall(SYS_fchmodat2, object, "", 0700, 0), ENOENT);
84 ERROR(syscall(SYS_fchmodat2, object, (const char*)1, 0700, 0), EFAULT);
85 ERROR(fchmodat(directory, "link", 0600, AT_SYMLINK_NOFOLLOW), EOPNOTSUPP);
86 ERROR(syscall(SYS_fchmodat2, link, "", 0600, AT_EMPTY_PATH), EOPNOTSUPP);
87 ERROR(syscall(SYS_fchmodat2, object, "", S_ISUID | 0700, AT_EMPTY_PATH), EOPNOTSUPP);
88 ERROR(syscall(SYS_fchmodat2, object, "", S_ISGID | 0700, AT_EMPTY_PATH), EOPNOTSUPP);
89 CHECK(fstat(object, &st) == 0 && (st.st_mode & 07777) == 0640);
90 return 1;
91}
92
93static int timestamp_cases(int directory, int object, int link, const char* absolute) {
94 struct timespec times[2] = {{1700000000, 123456789}, {1700000300, 987654321}};
95 struct stat st, originalLink;
96 CHECK(utimensat(directory, "data", times, 0) == 0);
97 CHECK(fstat(object, &st) == 0 && st.st_atime == times[0].tv_sec &&
98 st.st_mtime == times[1].tv_sec && st.st_atim.tv_nsec == 0 && st.st_mtim.tv_nsec == 0);
99 times[0] = (struct timespec){-1, UTIME_OMIT};
100 times[1] = (struct timespec){1700000400, 0};
101 CHECK(syscall(SYS_utimensat, object, "", times, AT_EMPTY_PATH) == 0);
102 CHECK(fstat(object, &st) == 0 && st.st_atime == 1700000000 && st.st_mtime == 1700000400);
103 times[0].tv_nsec = 1000000000;
104 ERROR(utimensat(directory, "data", times, 0), EINVAL);
105 times[0].tv_nsec = -1;
106 ERROR(syscall(SYS_utimensat, directory, "data", times, 0), EINVAL);
107 times[0] = (struct timespec){-1, 0};
108 ERROR(utimensat(directory, "data", times, 0), EOVERFLOW);
109 CHECK(fstat(object, &st) == 0 && st.st_atime == 1700000000 && st.st_mtime == 1700000400);
110 times[0].tv_nsec = times[1].tv_nsec = UTIME_OMIT;
111 CHECK(syscall(SYS_utimensat, -123, (const char*)1, times, 0x40000000) == 0);
112 times[0] = (struct timespec){1700000500, 0};
113 times[1] = (struct timespec){1700000600, 0};
114 CHECK(utimensat(-123, absolute, times, 0) == 0);
115 ERROR(futimens(object, times), EBADF);
116 int ordinary = openat(directory, "data", O_RDONLY);
117 CHECK(ordinary >= 0 && futimens(ordinary, times) == 0 && close(ordinary) == 0);
118 CHECK(fstatat(directory, "link", &originalLink, AT_SYMLINK_NOFOLLOW) == 0);
119 times[0].tv_nsec = UTIME_OMIT;
120 times[1].tv_sec = 1700000700;
121 CHECK(utimensat(directory, "link", times, AT_SYMLINK_NOFOLLOW) == 0);
122 CHECK(fstat(link, &st) == 0 && st.st_mtime == 1700000700 && st.st_atime == originalLink.st_atime);
123 CHECK(fstat(object, &st) == 0 && st.st_mtime == 1700000600);
124 times[0] = (struct timespec){1700000800, 0};
125 times[1].tv_nsec = UTIME_OMIT;
126 CHECK(syscall(SYS_utimensat, link, "", times, AT_EMPTY_PATH) == 0);
127 CHECK(fstat(link, &st) == 0 && st.st_atime == 1700000800 && st.st_mtime == 1700000700);
128 times[0].tv_nsec = times[1].tv_nsec = UTIME_NOW;
129 const time_t before = time(NULL);
130 CHECK(utimensat(directory, "data", times, 0) == 0);
131 const time_t after = time(NULL);
132 CHECK(fstat(object, &st) == 0 && st.st_atime >= before && st.st_atime <= after &&
133 st.st_mtime >= before && st.st_mtime <= after);
134 ERROR(syscall(SYS_utimensat, directory, "data", (const void*)1, 0), EFAULT);
135 ERROR(syscall(SYS_utimensat, directory, "data", NULL, 0x40000000), EINVAL);
136 return 1;
137}
138
139static int ownership_cases(int directory, int object, const char* linkPath) {
140 struct stat st;
141 CHECK(lchown(linkPath, 34567, 34568) == 0);
142 CHECK(fstatat(directory, "link", &st, AT_SYMLINK_NOFOLLOW) == 0 && S_ISLNK(st.st_mode) &&
143 st.st_uid == 34567 && st.st_gid == 34568);
144 CHECK(fstat(object, &st) == 0 && st.st_uid == 0 && st.st_gid == 0);
145 CHECK(syscall(SYS_lchown, linkPath, (uid_t)-1, (gid_t)0) == 0);
146 CHECK(fstatat(directory, "link", &st, AT_SYMLINK_NOFOLLOW) == 0 && st.st_uid == 34567 &&
147 st.st_gid == 0);
148 const struct stat original = st;
149 for (unsigned attempt = 0; time(NULL) <= original.st_ctime && attempt < 4; ++attempt) {
150 const struct timespec delay = {1, 0};
151 CHECK(nanosleep(&delay, NULL) == 0);
152 }
153 CHECK(time(NULL) > original.st_ctime);
154 CHECK(lchown(linkPath, (uid_t)-1, (gid_t)-1) == 0);
155 CHECK(fstatat(directory, "link", &st, AT_SYMLINK_NOFOLLOW) == 0 &&
156 st.st_ctime > original.st_ctime && st.st_uid == original.st_uid &&
157 st.st_gid == original.st_gid && st.st_atime == original.st_atime &&
158 st.st_mtime == original.st_mtime);
159 ERROR(syscall(SYS_lchown, (const char*)1, (uid_t)0, (gid_t)0), EFAULT);
160 return 1;
161}
162
163static int statx_cases(int directory, int object, int link, const char* absolute) {
164 struct {
165 struct statx value;
166 unsigned char tail[16];
167 } output;
168 struct stat st;
169 CHECK(fstat(object, &st) == 0);
170 memset(&output, 0xa5, sizeof(output));
171 CHECK(syscall(SYS_statx, directory, "data", 0, STATX_ALL | STATX_MNT_ID, &output.value) == 0);
172 struct statx* sx = &output.value;
173 CHECK((sx->stx_mask & STATX_BASIC_STATS) == STATX_BASIC_STATS);
174 CHECK(!(sx->stx_mask & (STATX_BTIME | STATX_MNT_ID_UNIQUE | STATX_DIOALIGN | STATX_SUBVOL |
175 STATX_WRITE_ATOMIC)));
176 CHECK(sx->stx_mode == st.st_mode && sx->stx_ino == st.st_ino && sx->stx_size == st.st_size &&
177 sx->stx_blocks == st.st_blocks && sx->stx_nlink == st.st_nlink &&
178 sx->stx_uid == st.st_uid && sx->stx_gid == st.st_gid);
179 CHECK(sx->stx_dev_major == major(st.st_dev) && sx->stx_dev_minor == minor(st.st_dev) &&
180 sx->stx_rdev_major == 0 && sx->stx_rdev_minor == 0);
181 CHECK(sx->stx_attributes == 0 && sx->stx_attributes_mask == 0);
182 CHECK(sx->stx_btime.tv_sec == 0 && sx->stx_btime.tv_nsec == 0);
183 CHECK(sx->stx_atime.tv_sec == st.st_atime && sx->stx_mtime.tv_sec == st.st_mtime &&
184 sx->stx_ctime.tv_sec == st.st_ctime);
185 CHECK(sx->stx_atime.tv_nsec == 0 && sx->stx_mtime.tv_nsec == 0 && sx->stx_ctime.tv_nsec == 0);
186 for (size_t i = 152; i < sizeof(*sx); ++i)
187 CHECK(((const unsigned char*)sx)[i] == 0);
188 for (size_t i = 0; i < sizeof(output.tail); ++i)
189 CHECK(output.tail[i] == 0xa5);
190 CHECK(statx(-123, absolute, AT_STATX_DONT_SYNC, 0, sx) == 0 && S_ISREG(sx->stx_mode));
191 CHECK(statx(object, "", AT_EMPTY_PATH, STATX_BASIC_STATS, sx) == 0 && sx->stx_ino == st.st_ino);
192 CHECK(syscall(SYS_statx, object, NULL, AT_EMPTY_PATH, STATX_BASIC_STATS, sx) == 0 &&
193 sx->stx_ino == st.st_ino);
194 CHECK(syscall(SYS_statx, AT_FDCWD, NULL, AT_EMPTY_PATH, STATX_BASIC_STATS, sx) == 0 &&
195 S_ISDIR(sx->stx_mode));
196 CHECK(statx(link, "", AT_EMPTY_PATH, STATX_TYPE, sx) == 0 && S_ISLNK(sx->stx_mode));
197 CHECK(statx(directory, "link", 0, STATX_TYPE, sx) == 0 && S_ISREG(sx->stx_mode));
198 CHECK(statx(directory, "link", AT_SYMLINK_NOFOLLOW, STATX_TYPE, sx) == 0 &&
199 S_ISLNK(sx->stx_mode));
200 CHECK(statx(directory, "null", 0, STATX_TYPE, sx) == 0 && S_ISREG(sx->stx_mode));
201 ERROR(syscall(SYS_statx, directory, "data", 0, 0x80000000U, sx), EINVAL);
202 ERROR(syscall(SYS_statx, directory, "data", 0x6000, 0, sx), EINVAL);
203 ERROR(syscall(SYS_statx, directory, "data", 0x40000000, 0, sx), EINVAL);
204 ERROR(syscall(SYS_statx, -123, "data", 0, 0, sx), EBADF);
205 ERROR(syscall(SYS_statx, object, "data", 0, 0, sx), ENOTDIR);
206 ERROR(syscall(SYS_statx, object, "", 0, 0, sx), ENOENT);
207 ERROR(syscall(SYS_statx, directory, NULL, 0, 0, sx), EFAULT);
208 ERROR(syscall(SYS_statx, directory, "data", 0, 0, (void*)1), EFAULT);
209 const long page = sysconf(_SC_PAGESIZE);
210 void* boundary = mmap(NULL, 2 * page, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
211 CHECK(boundary != MAP_FAILED && page >= 256);
212 CHECK(mprotect((char*)boundary + page, page, PROT_NONE) == 0);
213 CHECK(syscall(SYS_statx, directory, "data", 0, 0, (char*)boundary + page - 256) == 0);
214 ERROR(syscall(SYS_statx, directory, "data", 0, 0, (char*)boundary + page - 255), EFAULT);
215 CHECK(munmap(boundary, 2 * page) == 0);
216 return 1;
217}
218
219static int mknod_cases(int directory, int object, const char* absolute) {
220 struct stat st;
221 mode_t previous = umask(0027);
222 CHECK(mknodat(directory, "regular", S_IFREG | 0666, makedev(1, 3)) == 0);
223 CHECK(syscall(SYS_mknodat, directory, "plain", 0666, 0) == 0);
224 CHECK(mkfifoat(directory, "fifo", 0666) == 0);
225 CHECK(syscall(SYS_mknodat, directory, "raw-fifo", S_IFIFO | 0666, 0) == 0);
226 umask(previous);
227 CHECK(fstatat(directory, "regular", &st, 0) == 0 && S_ISREG(st.st_mode) &&
228 (st.st_mode & 0777) == 0640 && st.st_rdev == 0 && st.st_size == 0);
229 CHECK(fstatat(directory, "fifo", &st, 0) == 0 && S_ISFIFO(st.st_mode) &&
230 (st.st_mode & 0777) == 0640);
231 int fd = openat(directory, "fifo", O_RDWR | O_NONBLOCK);
232 char value = 0;
233 CHECK(fd >= 0 && write(fd, "q", 1) == 1 && read(fd, &value, 1) == 1 && value == 'q');
234 CHECK(close(fd) == 0);
235 CHECK(mknodat(-123, absolute, S_IFREG | 0600, 0) == 0);
236 CHECK(mkdirat(directory, "subdir", 0700) == 0);
237 int retained = openat(directory, "subdir", O_PATH | O_DIRECTORY);
238 CHECK(retained >= 0);
239 CHECK(renameat(directory, "subdir", directory, "moved") == 0);
240 CHECK(mknodat(retained, "inside", S_IFREG | 0600, 0) == 0);
241 CHECK(fstatat(directory, "moved/inside", &st, 0) == 0 && S_ISREG(st.st_mode));
242 CHECK(unlinkat(retained, "inside", 0) == 0 && close(retained) == 0);
243 CHECK(unlinkat(directory, "moved", AT_REMOVEDIR) == 0);
244 ERROR(mknodat(directory, "regular", S_IFREG | 0600, 0), EEXIST);
245 ERROR(mknodat(directory, "link", S_IFREG | 0600, 0), EEXIST);
246 ERROR(mknodat(-123, "new", S_IFREG | 0600, 0), EBADF);
247 ERROR(mknodat(object, "new", S_IFREG | 0600, 0), ENOTDIR);
248 ERROR(mknodat(directory, "", S_IFREG | 0600, 0), ENOENT);
249 ERROR(mknodat(directory, ".", S_IFIFO | 0600, 0), EEXIST);
250 ERROR(mknodat(directory, "trailing/", S_IFREG | 0600, 0), ENOENT);
251 ERROR(mknodat(directory, "bad-directory", S_IFDIR | 0700, 0), EPERM);
252 ERROR(mknodat(directory, "device", S_IFCHR | 0600, makedev(1, 3)), EOPNOTSUPP);
253 ERROR(mknodat(directory, "setid", S_IFREG | S_ISUID | 0700, 0), EOPNOTSUPP);
254 char longName[257];
255 memset(longName, 'x', 256);
256 longName[256] = 0;
257 ERROR(mknodat(directory, longName, S_IFIFO | 0600, 0), ENAMETOOLONG);
258 ERROR(syscall(SYS_mknodat, directory, (const char*)1, S_IFREG | 0600, 0), EFAULT);
259 return 1;
260}
261
262static int denied_cases(int directory, const char* absolute, const char* linkPath) {
263 struct timespec explicit[2] = {{1700000000, 0}, {1700000000, 0}};
264 CHECK(setgroups(0, NULL) == 0 && setgid(42424) == 0 && setuid(42424) == 0);
265 ERROR(truncate(absolute, 0), EACCES);
266 ERROR(syscall(SYS_fchmodat2, directory, "data", 0777, 0), EPERM);
267 ERROR(lchown(linkPath, 42424, (gid_t)-1), EPERM);
268 ERROR(utimensat(directory, "data", explicit, 0), EPERM);
269 ERROR(utimensat(directory, "data", NULL, 0), EACCES);
270 CHECK(utimensat(directory, "writable", NULL, 0) == 0);
271 ERROR(utimensat(directory, "writable", explicit, 0), EPERM);
272 ERROR(mknodat(directory, "denied", S_IFIFO | 0600, 0), EACCES);
273 struct statx st;
274 CHECK(statx(directory, "data", 0, STATX_BASIC_STATS, &st) == 0);
275 return 1;
276}
277
278int main(int argc, char** argv) {
279 const char* base = argc > 1 ? argv[1] : "/tmp";
280 char directoryPath[PATH_MAX], absolute[PATH_MAX], linkPath[PATH_MAX], created[PATH_MAX];
281 snprintf(directoryPath, sizeof(directoryPath), "%s/metadata-contract-%ld", base, (long)getpid());
282 snprintf(absolute, sizeof(absolute), "%s/data", directoryPath);
283 snprintf(linkPath, sizeof(linkPath), "%s/link", directoryPath);
284 snprintf(created, sizeof(created), "%s/absolute", directoryPath);
285 int directory = -1, object = -1, link = -1, success = 0, createdDirectory = 0;
286 const mode_t originalMask = umask(0);
287 if (geteuid() != 0 || mkdir(directoryPath, 0755))
288 goto done;
289 createdDirectory = 1;
290 directory = open(directoryPath, O_PATH | O_DIRECTORY);
291 if (directory < 0 || !make_file(directory, "data", 0600) || !make_file(directory, "null", 0600) ||
292 !make_file(directory, "writable", 0666) || symlinkat("data", directory, "link"))
293 goto done;
294 object = openat(directory, "data", O_PATH);
295 link = openat(directory, "link", O_PATH | O_NOFOLLOW);
296 if (object < 0 || link < 0 || !truncate_cases(directory, absolute) ||
297 !mode_cases(directory, object, link, absolute) ||
298 !timestamp_cases(directory, object, link, absolute) ||
299 !ownership_cases(directory, object, linkPath) ||
300 !statx_cases(directory, object, link, absolute) || !mknod_cases(directory, object, created))
301 goto done;
302 if (syscall(SYS_fchmodat2, object, "", 0600, AT_EMPTY_PATH))
303 goto done;
304 pid_t child = fork();
305 if (!child)
306 _exit(denied_cases(directory, absolute, linkPath) ? 0 : 1);
307 int status = 0;
308 if (child < 0 || waitpid(child, &status, 0) != child || !WIFEXITED(status) || WEXITSTATUS(status))
309 goto done;
310 success = 1;
311done:
312 if (!success)
313 fprintf(stderr, "METADATA-CONTRACT: setup or case failure errno=%d\n", errno);
314 umask(originalMask);
315 if (link >= 0)
316 close(link);
317 if (object >= 0)
318 close(object);
319 if (directory >= 0) {
320 const char* names[] = {"data", "link", "null", "writable", "regular", "plain", "fifo",
321 "raw-fifo", "absolute", "denied", "trailing", "device", "setid"};
322 for (size_t i = 0; i < sizeof(names) / sizeof(names[0]); ++i)
323 unlinkat(directory, names[i], 0);
324 unlinkat(directory, "subdir/inside", 0);
325 unlinkat(directory, "moved/inside", 0);
326 unlinkat(directory, "subdir", AT_REMOVEDIR);
327 unlinkat(directory, "moved", AT_REMOVEDIR);
328 close(directory);
329 }
330 if (createdDirectory)
331 rmdir(directoryPath);
332 printf("METADATA-CONTRACT: %s base=%s\n", success ? "PASS" : "FAIL", base);
333 return success ? 0 : 1;
334}