14#include <sys/syscall.h>
17#define CHECK(expression) \
19 if (!(expression)) { \
20 fprintf(stderr, "EXECVEAT-CONTRACT: FAIL line=%d errno=%d\n", __LINE__, errno); \
26static char self[PATH_MAX];
28static int invoke(
int fd,
const char* path,
int flags,
char** arguments) {
29 return syscall(SYS_execveat, fd, path, arguments, environ, flags);
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);
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};
44 invoke(fd, path, flags, arguments);
45 fprintf(stderr,
"EXECVEAT-CONTRACT: exec failed errno=%d path=%s\n", errno, expected);
49 CHECK(waitpid(child, &status, 0) == child);
50 CHECK(WIFEXITED(status) && WEXITSTATUS(status) == 0);
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);
59 while ((count = read(source, buffer,
sizeof(buffer))) > 0) {
61 while (written < count) {
62 ssize_t n = write(target, buffer + written, count - written);
67 CHECK(count == 0 && close(source) == 0 && close(target) == 0);
71static int descriptor_paths(
const char* image) {
72 int fd = open(image, O_RDONLY);
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);
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");
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);
108 CHECK(stat(proc, &alias) == 0 && alias.st_ino == backing.st_ino);
109 CHECK(close(fd) == 0 && close(linkfd) == 0);
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);
138 int output = open(script, O_CREAT | O_EXCL | O_WRONLY, 0700);
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 &&
149 CHECK(close(symlinkfd) == 0);
150 CHECK(run(dirfd,
"link", 0, 1, -1) == 0);
151 int source = open(script, O_RDONLY | O_CLOEXEC);
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);
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();
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);
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);
201 CHECK(read(old, magic,
sizeof(magic)) ==
sizeof(magic) && !memcmp(magic,
"\177ELF", 4));
202 CHECK(close(old) == 0 && write(resume[1],
"x", 1) == 1);
204 CHECK(waitpid(child, &status, 0) == child && WIFEXITED(status) && WEXITSTATUS(status) == 42);
210 execl(image, image, NULL);
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");
219int main(
int argc,
char** argv) {
220 setvbuf(stdout, NULL, _IONBF, 0);
222 if (argc == 4 && !strcmp(argv[1],
"--upgrade-child")) {
223 int ready = atoi(argv[2]), resume = atoi(argv[3]);
225 CHECK(write(ready,
"x", 1) == 1 && read(resume, &
byte, 1) == 1);
228 if (argc >= 2 && !strcmp(argv[1],
"--elf-child")) {
230 int fd = atoi(argv[2]);
232 CHECK(fcntl(fd, F_GETFD) == -1 && errno == EBADF);
233 CHECK(!strcmp((
const char*)getauxval(AT_EXECFN), argv[3]));
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);
240 char contents[PATH_MAX + 64];
241 ssize_t count = read(fd, contents,
sizeof(contents) - 1);
244 CHECK(strstr(contents,
"\nscript-payload\n") && close(fd) == 0);
247 CHECK(realpath(argv[0], self));
248 CHECK(contracts() == 0);
249 CHECK(executable_upgrade() == 0);
250 puts(
"EXECVEAT-CONTRACT: PASS");