15#include <sys/syscall.h>
18#define CHECK(expression) \
20 if (!(expression)) { \
21 fprintf(stderr, "PIVOT-ROOT-CONTRACT: line=%d errno=%d\n", __LINE__, errno); \
25#define ERROR(expression, expected) \
28 CHECK((expression) == -1 && errno == (expected)); \
31_Static_assert(SYS_pivot_root == 155 && SYS_mount == 165 && SYS_umount2 == 166,
32 "Linux amd64 mount routes");
35 char base[128], newRoot[160], putOld[192], oldCwd[160], park[160], restoreTarget[192];
36 int originalCwd, oldRoot, baseFd, newFd, childFd, oldFile;
37 int command[2], response[2];
39 int created, mounted, pivoted, restored;
42static int write_marker(
int directory,
const char* name,
char value) {
43 int fd = openat(directory, name, O_CREAT | O_EXCL | O_WRONLY, 0600);
45 int good = write(fd, &value, 1) == 1;
46 CHECK(close(fd) == 0 && good);
50static int marker(
int directory,
const char* name,
char expected) {
51 int fd = openat(directory, name, O_RDONLY);
54 int good = read(fd, &value, 1) == 1 && value == expected;
55 CHECK(close(fd) == 0 && good);
59static int receive(
int fd,
char expected) {
60 struct pollfd ready = {fd, POLLIN, 0};
61 CHECK(poll(&ready, 1, 10000) == 1 && (ready.revents & POLLIN));
63 CHECK(read(fd, &value, 1) == 1 && value == expected);
67static int wait_child(pid_t child) {
69 for (
unsigned attempt = 0; attempt < 100; ++attempt) {
70 pid_t result = waitpid(child, &status, WNOHANG);
72 if (result == child) {
73 CHECK(WIFEXITED(status) && WEXITSTATUS(status) == 0);
76 const struct timespec delay = {0, 100000000};
77 CHECK(nanosleep(&delay, NULL) == 0);
80 waitpid(child, &status, 0);
84static int setup(
struct Fixture* fixture) {
85 CHECK(geteuid() == 0);
86 fixture->originalCwd = open(
".", O_PATH | O_DIRECTORY);
87 CHECK(fixture->originalCwd >= 0 && chdir(
"/") == 0);
88 fixture->oldRoot = open(
"/", O_PATH | O_DIRECTORY);
89 CHECK(fixture->oldRoot >= 0);
90 snprintf(fixture->base,
sizeof(fixture->base),
"/tmp/pivot-contract-%ld", (
long)getpid());
91 snprintf(fixture->newRoot,
sizeof(fixture->newRoot),
"%s/new", fixture->base);
92 snprintf(fixture->putOld,
sizeof(fixture->putOld),
"%s/old", fixture->newRoot);
93 snprintf(fixture->oldCwd,
sizeof(fixture->oldCwd),
"%s/cwd", fixture->base);
94 snprintf(fixture->park,
sizeof(fixture->park),
"%s/park", fixture->base);
95 snprintf(fixture->restoreTarget,
sizeof(fixture->restoreTarget),
"/old%s", fixture->park);
96 CHECK(mkdir(fixture->base, 0700) == 0);
98 fixture->baseFd = open(fixture->base, O_PATH | O_DIRECTORY);
99 CHECK(fixture->baseFd >= 0 && write_marker(fixture->baseFd,
"original",
'O'));
100 CHECK(mkdirat(fixture->baseFd,
"cwd", 0700) == 0 && mkdirat(fixture->baseFd,
"new", 0700) == 0 &&
101 mkdirat(fixture->baseFd,
"park", 0700) == 0);
102 int cwd = openat(fixture->baseFd,
"cwd", O_PATH | O_DIRECTORY);
103 CHECK(cwd >= 0 && write_marker(cwd,
"old-marker",
'S') && close(cwd) == 0);
104 fixture->oldFile = openat(fixture->baseFd,
"original", O_RDONLY);
105 CHECK(fixture->oldFile >= 0);
106 CHECK(mount(
"none", fixture->newRoot,
"tmpfs", 0, NULL) == 0);
107 fixture->mounted = 1;
108 fixture->newFd = open(fixture->newRoot, O_PATH | O_DIRECTORY);
109 CHECK(fixture->newFd >= 0);
110 const char* directories[] = {
"old",
"child",
"proc",
"dev",
"mapped"};
111 for (
size_t i = 0; i <
sizeof(directories) /
sizeof(directories[0]); ++i)
112 CHECK(mkdirat(fixture->newFd, directories[i], 0755) == 0);
113 CHECK(write_marker(fixture->newFd,
"new-marker",
'N'));
114 CHECK(symlinkat(
"/new-marker", fixture->newFd,
"absolute-link") == 0 &&
115 symlinkat(
"../new-marker", fixture->newFd,
"relative-link") == 0);
116 for (
unsigned i = 0; i <= 40; ++i) {
117 char name[32], target[32];
118 snprintf(name,
sizeof(name),
"chain-%u", i);
120 snprintf(target,
sizeof(target),
"new-marker");
122 snprintf(target,
sizeof(target),
"chain-%u", i + 1);
123 CHECK(symlinkat(target, fixture->newFd, name) == 0);
125 CHECK(symlinkat(
"child/", fixture->newFd,
"directory-target") == 0 &&
126 symlinkat(
"directory-target", fixture->newFd,
"directory-chain") == 0);
128 snprintf(path,
sizeof(path),
"%s/proc", fixture->newRoot);
129 CHECK(mount(
"none", path,
"proc", 0, NULL) == 0);
130 snprintf(path,
sizeof(path),
"%s/child", fixture->newRoot);
131 CHECK(mount(
"none", path,
"tmpfs", 0, NULL) == 0);
132 fixture->childFd = open(path, O_PATH | O_DIRECTORY);
133 CHECK(fixture->childFd >= 0 && write_marker(fixture->childFd,
"child-marker",
'C'));
137static int unprivileged(
const struct Fixture* fixture) {
138 CHECK(setgid(42424) == 0 && setuid(42424) == 0);
139 ERROR(syscall(SYS_pivot_root, fixture->newRoot, fixture->putOld), EPERM);
143static int confined(
const struct Fixture* fixture) {
144 CHECK(chroot(fixture->newRoot) == 0 && chdir(
"/") == 0);
145 CHECK(marker(AT_FDCWD,
"/new-marker",
'N') && marker(AT_FDCWD,
"/../../new-marker",
'N'));
146 ERROR(open(
"/dev/tty", O_RDWR | O_NOCTTY), ENOENT);
147 ERROR(open(fixture->base, O_PATH | O_DIRECTORY), ENOENT);
151static int before_pivot(
struct Fixture* fixture) {
152 char file[192],
byte = 0;
153 snprintf(file,
sizeof(file),
"%s/new-marker", fixture->newRoot);
154 ERROR(syscall(SYS_pivot_root, (
const char*)1, fixture->putOld), EFAULT);
155 ERROR(syscall(SYS_pivot_root, fixture->newRoot, (
const char*)1), EFAULT);
156 ERROR(syscall(SYS_pivot_root, file, fixture->putOld), ENOTDIR);
157 ERROR(syscall(SYS_pivot_root, fixture->oldCwd, fixture->putOld), EINVAL);
158 ERROR(syscall(SYS_pivot_root, fixture->newRoot, fixture->base), EINVAL);
159 ERROR(syscall(SYS_pivot_root, fixture->newRoot, fixture->newRoot), EOPNOTSUPP);
160 ERROR(mount(
"none", fixture->newRoot,
"tmpfs", 0, NULL), EOPNOTSUPP);
161 ERROR(mount(
"none", fixture->park,
"tmpfs", MS_RDONLY, NULL), EOPNOTSUPP);
162 ERROR(mount(
"none", fixture->park,
"tmpfs", 0,
"size=4096"), EOPNOTSUPP);
163 ERROR(syscall(SYS_umount2, fixture->newRoot, 0x40000000), EOPNOTSUPP);
164 ERROR(read(fixture->newFd, &
byte, 1), EBADF);
165 ERROR(linkat(fixture->newFd,
"old", fixture->newFd,
"directory-link", 0), EPERM);
166 pid_t child = fork();
169 _exit(unprivileged(fixture) ? 0 : 1);
170 CHECK(wait_child(child));
174 _exit(confined(fixture) ? 0 : 1);
175 CHECK(wait_child(child));
177 CHECK(getcwd(cwd,
sizeof(cwd)) && !strcmp(cwd,
"/") && marker(fixture->baseFd,
"original",
'O'));
181static int observe_pivot(
const struct Fixture* fixture) {
182 close(fixture->command[1]);
183 close(fixture->response[0]);
184 CHECK(chdir(fixture->oldCwd) == 0 && write(fixture->response[1],
"R", 1) == 1);
185 CHECK(receive(fixture->command[0],
'P'));
186 char cwd[PATH_MAX], expected[192];
187 snprintf(expected,
sizeof(expected),
"/old%s", fixture->oldCwd);
188 CHECK(getcwd(cwd,
sizeof(cwd)) && !strcmp(cwd, expected));
189 CHECK(marker(AT_FDCWD,
"old-marker",
'S') && marker(AT_FDCWD,
"/new-marker",
'N'));
190 CHECK(write(fixture->response[1],
"G", 1) == 1);
194static int symlink_paths(
const struct Fixture* fixture) {
195 CHECK(marker(AT_FDCWD,
"/chain-10",
'N') && marker(AT_FDCWD,
"/chain-1",
'N'));
196 ERROR(open(
"/chain-0", O_RDONLY), ELOOP);
197 int fd = open(
"/chain-0", O_PATH | O_NOFOLLOW);
198 struct stat attributes;
199 CHECK(fd >= 0 && fstat(fd, &attributes) == 0 && S_ISLNK(attributes.st_mode));
200 CHECK(close(fd) == 0);
201 CHECK(marker(AT_FDCWD,
"/directory-chain/child-marker",
'C'));
202 fd = open(
"/directory-chain/", O_PATH | O_DIRECTORY);
203 CHECK(fd >= 0 && close(fd) == 0);
204 ERROR(open(
"/chain-10/", O_PATH), ENOTDIR);
206 snprintf(magic,
sizeof(magic),
"/proc/self/fd/%d/chain-10", fixture->newFd);
207 CHECK(marker(AT_FDCWD, magic,
'N'));
211static int mount_report(
void) {
212 char contents[32768];
213 int fd = open(
"/proc/mounts", O_RDONLY);
216 while (used <
sizeof(contents) - 1) {
217 ssize_t count = read(fd, contents + used,
sizeof(contents) - 1 - used);
223 CHECK(close(fd) == 0 && used <
sizeof(contents) - 1);
227 for (
char* line = strtok_r(contents,
"\n", &state); line; line = strtok_r(NULL,
"\n", &state)) {
228 char source[256], path[512], type[64];
229 CHECK(sscanf(line,
"%255s %511s %63s", source, path, type) == 3);
230 if (!strcmp(path,
"/"))
232 if (!strcmp(path,
"/old"))
234 if (!strcmp(path,
"/child"))
236 if (!strcmp(path,
"/proc"))
243static int mapped_attachment(
void) {
244 CHECK(mount(
"none",
"/mapped",
"tmpfs", 0, NULL) == 0);
245 const long page = sysconf(_SC_PAGESIZE);
247 int fd = open(
"/mapped/data", O_CREAT | O_EXCL | O_RDWR, 0600);
248 CHECK(fd >= 0 && ftruncate(fd, page) == 0);
249 char* mapping = mmap(NULL, page, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
250 CHECK(mapping != MAP_FAILED && close(fd) == 0);
252 ERROR(umount2(
"/mapped", 0), EBUSY);
253 int command[2], response[2];
254 CHECK(pipe(command) == 0 && pipe(response) == 0);
255 pid_t child = fork();
260 const int good = mapping[0] ==
'M' && write(response[1],
"R", 1) == 1 &&
261 receive(command[0],
'X') && mapping[0] ==
'M';
267 CHECK(receive(response[0],
'R'));
268 CHECK(munmap(mapping, page) == 0);
269 ERROR(umount2(
"/mapped", 0), EBUSY);
270 CHECK(write(command[1],
"X", 1) == 1 && wait_child(child));
271 CHECK(close(command[1]) == 0 && close(response[0]) == 0);
272 CHECK(umount2(
"/mapped", 0) == 0);
273 ERROR(open(
"/mapped/data", O_RDONLY), ENOENT);
277static int after_pivot(
struct Fixture* fixture) {
278 CHECK(write(fixture->command[1],
"P", 1) == 1 && receive(fixture->response[0],
'G'));
279 CHECK(wait_child(fixture->observer));
280 fixture->observer = -1;
281 char cwd[PATH_MAX], value = 0;
282 CHECK(getcwd(cwd,
sizeof(cwd)) && !strcmp(cwd,
"/"));
283 CHECK(marker(AT_FDCWD,
"/new-marker",
'N') && marker(AT_FDCWD,
"/../../new-marker",
'N') &&
284 marker(AT_FDCWD,
"/absolute-link",
'N') && marker(AT_FDCWD,
"/relative-link",
'N'));
285 CHECK(pread(fixture->oldFile, &value, 1, 0) == 1 && value ==
'O');
286 CHECK(marker(fixture->baseFd,
"original",
'O') && marker(fixture->newFd,
"../new-marker",
'N'));
287 CHECK(fchdir(fixture->oldRoot) == 0 && getcwd(cwd,
sizeof(cwd)) && !strcmp(cwd,
"/old"));
288 CHECK(chdir(
"/") == 0);
289 ERROR(open(fixture->base, O_PATH | O_DIRECTORY), ENOENT);
290 int ns = open(
"/proc/self/ns/uts", O_RDONLY);
291 CHECK(ns >= 0 && close(ns) == 0);
292 ssize_t length = readlink(
"/proc/self/ns/uts", cwd,
sizeof(cwd));
293 CHECK(length > 5 && !memcmp(cwd,
"uts:[", 5));
294 CHECK(mount_report());
295 CHECK(symlink_paths(fixture));
296 CHECK(mapped_attachment());
297 ERROR(umount2(
"/old", 0), EBUSY);
298 ERROR(umount2(
"/child", 0), EBUSY);
299 CHECK(syscall(SYS_umount2,
"/child", MNT_DETACH) == 0);
300 ERROR(open(
"/child/child-marker", O_RDONLY), ENOENT);
301 CHECK(marker(fixture->childFd,
"child-marker",
'C'));
302 CHECK(fchdir(fixture->childFd) == 0 && marker(AT_FDCWD,
"child-marker",
'C'));
303 ERROR(getcwd(cwd,
sizeof(cwd)) ? 0 : -1, ENOENT);
304 CHECK(chdir(
"/") == 0);
305 CHECK(syscall(SYS_mount,
"none",
"/child",
"ramfs", 0, NULL) == 0);
306 int replacement = open(
"/child", O_PATH | O_DIRECTORY);
307 CHECK(replacement >= 0 && write_marker(replacement,
"child-marker",
'R'));
308 CHECK(marker(replacement,
"child-marker",
'R') && marker(fixture->childFd,
"child-marker",
'C'));
309 CHECK(close(replacement) == 0);
313static int restore(
struct Fixture* fixture) {
314 if (!fixture->pivoted)
316 if (syscall(SYS_pivot_root,
"/old", fixture->restoreTarget) < 0) {
317 fprintf(stderr,
"PIVOT-ROOT-CONTRACT: restoration errno=%d\n", errno);
320 fixture->pivoted = 0;
321 fixture->restored = 1;
322 CHECK(chdir(
"/") == 0 && marker(fixture->baseFd,
"original",
'O'));
324 snprintf(path,
sizeof(path),
"%s/original", fixture->base);
325 CHECK(marker(AT_FDCWD, path,
'O'));
326 ERROR(open(
"/new-marker", O_RDONLY), ENOENT);
331 struct Fixture fixture = {.originalCwd = -1,
338 .response = {-1, -1},
341 const mode_t previousMask = umask(0);
342 setvbuf(stdout, NULL, _IOLBF, 0);
343 puts(
"PIVOT-ROOT-CONTRACT: BEGIN");
344 if (!setup(&fixture) || !before_pivot(&fixture))
346 if (pipe(fixture.command) || pipe(fixture.response))
348 fixture.observer = fork();
349 if (!fixture.observer)
350 _exit(observe_pivot(&fixture) ? 0 : 1);
351 if (fixture.observer < 0 || !receive(fixture.response[0],
'R'))
353 if (syscall(SYS_pivot_root, fixture.newRoot, fixture.putOld) < 0)
356 puts(
"PIVOT-ROOT-CONTRACT: pivoted");
357 if (!after_pivot(&fixture) || !restore(&fixture))
361 if (fixture.observer > 0) {
362 kill(fixture.observer, SIGKILL);
363 waitpid(fixture.observer, NULL, 0);
365 if (!restore(&fixture))
367 if (!fixture.pivoted && fixture.mounted &&
368 umount2(fixture.restored ? fixture.park : fixture.newRoot, MNT_DETACH) < 0)
370 if (!fixture.pivoted && fixture.originalCwd >= 0 && fchdir(fixture.originalCwd) < 0)
372 if (fixture.baseFd >= 0) {
373 unlinkat(fixture.baseFd,
"original", 0);
374 unlinkat(fixture.baseFd,
"cwd/old-marker", 0);
375 unlinkat(fixture.baseFd,
"cwd", AT_REMOVEDIR);
376 unlinkat(fixture.baseFd,
"new", AT_REMOVEDIR);
377 unlinkat(fixture.baseFd,
"park", AT_REMOVEDIR);
379 int descriptors[] = {fixture.originalCwd, fixture.oldRoot, fixture.baseFd,
380 fixture.newFd, fixture.childFd, fixture.oldFile,
381 fixture.command[0], fixture.command[1], fixture.response[0],
382 fixture.response[1]};
383 for (
size_t i = 0; i <
sizeof(descriptors) /
sizeof(descriptors[0]); ++i)
384 if (descriptors[i] >= 0)
385 close(descriptors[i]);
386 if (!fixture.pivoted && fixture.created)
389 printf(
"PIVOT-ROOT-CONTRACT: %s restored=%d\n", success ?
"PASS" :
"FAIL", fixture.restored);
390 return success ? 0 : 1;