Commit c9b26b81af9c3296685b5dbcbcd415400ab400dc
Committed by
Linus Torvalds
1 parent
27d6ec7ad6
Exists in
ti-lsk-linux-4.1.y
and in
10 other branches
syscalls: add selftest for execveat(2)
Signed-off-by: David Drysdale <drysdale@google.com> Cc: Meredydd Luff <meredydd@senatehouse.org> Cc: Shuah Khan <shuah.kh@samsung.com> Cc: "Eric W. Biederman" <ebiederm@xmission.com> Cc: Andy Lutomirski <luto@amacapital.net> Cc: Alexander Viro <viro@zeniv.linux.org.uk> Cc: Thomas Gleixner <tglx@linutronix.de> Cc: Ingo Molnar <mingo@redhat.com> Cc: "H. Peter Anvin" <hpa@zytor.com> Cc: Kees Cook <keescook@chromium.org> Cc: Arnd Bergmann <arnd@arndb.de> Cc: Rich Felker <dalias@aerifal.cx> Cc: Christoph Hellwig <hch@infradead.org> Cc: Michael Kerrisk <mtk.manpages@gmail.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Showing 4 changed files with 432 additions and 0 deletions Side-by-side Diff
tools/testing/selftests/Makefile
tools/testing/selftests/exec/.gitignore
tools/testing/selftests/exec/Makefile
1 | +CC = $(CROSS_COMPILE)gcc | |
2 | +CFLAGS = -Wall | |
3 | +BINARIES = execveat | |
4 | +DEPS = execveat.symlink execveat.denatured script subdir | |
5 | +all: $(BINARIES) $(DEPS) | |
6 | + | |
7 | +subdir: | |
8 | + mkdir -p $@ | |
9 | +script: | |
10 | + echo '#!/bin/sh' > $@ | |
11 | + echo 'exit $$*' >> $@ | |
12 | + chmod +x $@ | |
13 | +execveat.symlink: execveat | |
14 | + ln -s -f $< $@ | |
15 | +execveat.denatured: execveat | |
16 | + cp $< $@ | |
17 | + chmod -x $@ | |
18 | +%: %.c | |
19 | + $(CC) $(CFLAGS) -o $@ $^ | |
20 | + | |
21 | +run_tests: all | |
22 | + ./execveat | |
23 | + | |
24 | +clean: | |
25 | + rm -rf $(BINARIES) $(DEPS) subdir.moved execveat.moved xxxxx* |
tools/testing/selftests/exec/execveat.c
1 | +/* | |
2 | + * Copyright (c) 2014 Google, Inc. | |
3 | + * | |
4 | + * Licensed under the terms of the GNU GPL License version 2 | |
5 | + * | |
6 | + * Selftests for execveat(2). | |
7 | + */ | |
8 | + | |
9 | +#define _GNU_SOURCE /* to get O_PATH, AT_EMPTY_PATH */ | |
10 | +#include <sys/sendfile.h> | |
11 | +#include <sys/stat.h> | |
12 | +#include <sys/syscall.h> | |
13 | +#include <sys/types.h> | |
14 | +#include <sys/wait.h> | |
15 | +#include <errno.h> | |
16 | +#include <fcntl.h> | |
17 | +#include <limits.h> | |
18 | +#include <stdio.h> | |
19 | +#include <stdlib.h> | |
20 | +#include <string.h> | |
21 | +#include <unistd.h> | |
22 | + | |
23 | +static char longpath[2 * PATH_MAX] = ""; | |
24 | +static char *envp[] = { "IN_TEST=yes", NULL, NULL }; | |
25 | +static char *argv[] = { "execveat", "99", NULL }; | |
26 | + | |
27 | +static int execveat_(int fd, const char *path, char **argv, char **envp, | |
28 | + int flags) | |
29 | +{ | |
30 | +#ifdef __NR_execveat | |
31 | + return syscall(__NR_execveat, fd, path, argv, envp, flags); | |
32 | +#else | |
33 | + errno = -ENOSYS; | |
34 | + return -1; | |
35 | +#endif | |
36 | +} | |
37 | + | |
38 | +#define check_execveat_fail(fd, path, flags, errno) \ | |
39 | + _check_execveat_fail(fd, path, flags, errno, #errno) | |
40 | +static int _check_execveat_fail(int fd, const char *path, int flags, | |
41 | + int expected_errno, const char *errno_str) | |
42 | +{ | |
43 | + int rc; | |
44 | + | |
45 | + errno = 0; | |
46 | + printf("Check failure of execveat(%d, '%s', %d) with %s... ", | |
47 | + fd, path?:"(null)", flags, errno_str); | |
48 | + rc = execveat_(fd, path, argv, envp, flags); | |
49 | + | |
50 | + if (rc > 0) { | |
51 | + printf("[FAIL] (unexpected success from execveat(2))\n"); | |
52 | + return 1; | |
53 | + } | |
54 | + if (errno != expected_errno) { | |
55 | + printf("[FAIL] (expected errno %d (%s) not %d (%s)\n", | |
56 | + expected_errno, strerror(expected_errno), | |
57 | + errno, strerror(errno)); | |
58 | + return 1; | |
59 | + } | |
60 | + printf("[OK]\n"); | |
61 | + return 0; | |
62 | +} | |
63 | + | |
64 | +static int check_execveat_invoked_rc(int fd, const char *path, int flags, | |
65 | + int expected_rc) | |
66 | +{ | |
67 | + int status; | |
68 | + int rc; | |
69 | + pid_t child; | |
70 | + int pathlen = path ? strlen(path) : 0; | |
71 | + | |
72 | + if (pathlen > 40) | |
73 | + printf("Check success of execveat(%d, '%.20s...%s', %d)... ", | |
74 | + fd, path, (path + pathlen - 20), flags); | |
75 | + else | |
76 | + printf("Check success of execveat(%d, '%s', %d)... ", | |
77 | + fd, path?:"(null)", flags); | |
78 | + child = fork(); | |
79 | + if (child < 0) { | |
80 | + printf("[FAIL] (fork() failed)\n"); | |
81 | + return 1; | |
82 | + } | |
83 | + if (child == 0) { | |
84 | + /* Child: do execveat(). */ | |
85 | + rc = execveat_(fd, path, argv, envp, flags); | |
86 | + printf("[FAIL]: execveat() failed, rc=%d errno=%d (%s)\n", | |
87 | + rc, errno, strerror(errno)); | |
88 | + exit(1); /* should not reach here */ | |
89 | + } | |
90 | + /* Parent: wait for & check child's exit status. */ | |
91 | + rc = waitpid(child, &status, 0); | |
92 | + if (rc != child) { | |
93 | + printf("[FAIL] (waitpid(%d,...) returned %d)\n", child, rc); | |
94 | + return 1; | |
95 | + } | |
96 | + if (!WIFEXITED(status)) { | |
97 | + printf("[FAIL] (child %d did not exit cleanly, status=%08x)\n", | |
98 | + child, status); | |
99 | + return 1; | |
100 | + } | |
101 | + if (WEXITSTATUS(status) != expected_rc) { | |
102 | + printf("[FAIL] (child %d exited with %d not %d)\n", | |
103 | + child, WEXITSTATUS(status), expected_rc); | |
104 | + return 1; | |
105 | + } | |
106 | + printf("[OK]\n"); | |
107 | + return 0; | |
108 | +} | |
109 | + | |
110 | +static int check_execveat(int fd, const char *path, int flags) | |
111 | +{ | |
112 | + return check_execveat_invoked_rc(fd, path, flags, 99); | |
113 | +} | |
114 | + | |
115 | +static char *concat(const char *left, const char *right) | |
116 | +{ | |
117 | + char *result = malloc(strlen(left) + strlen(right) + 1); | |
118 | + | |
119 | + strcpy(result, left); | |
120 | + strcat(result, right); | |
121 | + return result; | |
122 | +} | |
123 | + | |
124 | +static int open_or_die(const char *filename, int flags) | |
125 | +{ | |
126 | + int fd = open(filename, flags); | |
127 | + | |
128 | + if (fd < 0) { | |
129 | + printf("Failed to open '%s'; " | |
130 | + "check prerequisites are available\n", filename); | |
131 | + exit(1); | |
132 | + } | |
133 | + return fd; | |
134 | +} | |
135 | + | |
136 | +static void exe_cp(const char *src, const char *dest) | |
137 | +{ | |
138 | + int in_fd = open_or_die(src, O_RDONLY); | |
139 | + int out_fd = open(dest, O_RDWR|O_CREAT|O_TRUNC, 0755); | |
140 | + struct stat info; | |
141 | + | |
142 | + fstat(in_fd, &info); | |
143 | + sendfile(out_fd, in_fd, NULL, info.st_size); | |
144 | + close(in_fd); | |
145 | + close(out_fd); | |
146 | +} | |
147 | + | |
148 | +#define XX_DIR_LEN 200 | |
149 | +static int check_execveat_pathmax(int dot_dfd, const char *src, int is_script) | |
150 | +{ | |
151 | + int fail = 0; | |
152 | + int ii, count, len; | |
153 | + char longname[XX_DIR_LEN + 1]; | |
154 | + int fd; | |
155 | + | |
156 | + if (*longpath == '\0') { | |
157 | + /* Create a filename close to PATH_MAX in length */ | |
158 | + memset(longname, 'x', XX_DIR_LEN - 1); | |
159 | + longname[XX_DIR_LEN - 1] = '/'; | |
160 | + longname[XX_DIR_LEN] = '\0'; | |
161 | + count = (PATH_MAX - 3) / XX_DIR_LEN; | |
162 | + for (ii = 0; ii < count; ii++) { | |
163 | + strcat(longpath, longname); | |
164 | + mkdir(longpath, 0755); | |
165 | + } | |
166 | + len = (PATH_MAX - 3) - (count * XX_DIR_LEN); | |
167 | + if (len <= 0) | |
168 | + len = 1; | |
169 | + memset(longname, 'y', len); | |
170 | + longname[len] = '\0'; | |
171 | + strcat(longpath, longname); | |
172 | + } | |
173 | + exe_cp(src, longpath); | |
174 | + | |
175 | + /* | |
176 | + * Execute as a pre-opened file descriptor, which works whether this is | |
177 | + * a script or not (because the interpreter sees a filename like | |
178 | + * "/dev/fd/20"). | |
179 | + */ | |
180 | + fd = open(longpath, O_RDONLY); | |
181 | + if (fd > 0) { | |
182 | + printf("Invoke copy of '%s' via filename of length %lu:\n", | |
183 | + src, strlen(longpath)); | |
184 | + fail += check_execveat(fd, "", AT_EMPTY_PATH); | |
185 | + } else { | |
186 | + printf("Failed to open length %lu filename, errno=%d (%s)\n", | |
187 | + strlen(longpath), errno, strerror(errno)); | |
188 | + fail++; | |
189 | + } | |
190 | + | |
191 | + /* | |
192 | + * Execute as a long pathname relative to ".". If this is a script, | |
193 | + * the interpreter will launch but fail to open the script because its | |
194 | + * name ("/dev/fd/5/xxx....") is bigger than PATH_MAX. | |
195 | + */ | |
196 | + if (is_script) | |
197 | + fail += check_execveat_invoked_rc(dot_dfd, longpath, 0, 127); | |
198 | + else | |
199 | + fail += check_execveat(dot_dfd, longpath, 0); | |
200 | + | |
201 | + return fail; | |
202 | +} | |
203 | + | |
204 | +static int run_tests(void) | |
205 | +{ | |
206 | + int fail = 0; | |
207 | + char *fullname = realpath("execveat", NULL); | |
208 | + char *fullname_script = realpath("script", NULL); | |
209 | + char *fullname_symlink = concat(fullname, ".symlink"); | |
210 | + int subdir_dfd = open_or_die("subdir", O_DIRECTORY|O_RDONLY); | |
211 | + int subdir_dfd_ephemeral = open_or_die("subdir.ephemeral", | |
212 | + O_DIRECTORY|O_RDONLY); | |
213 | + int dot_dfd = open_or_die(".", O_DIRECTORY|O_RDONLY); | |
214 | + int dot_dfd_path = open_or_die(".", O_DIRECTORY|O_RDONLY|O_PATH); | |
215 | + int dot_dfd_cloexec = open_or_die(".", O_DIRECTORY|O_RDONLY|O_CLOEXEC); | |
216 | + int fd = open_or_die("execveat", O_RDONLY); | |
217 | + int fd_path = open_or_die("execveat", O_RDONLY|O_PATH); | |
218 | + int fd_symlink = open_or_die("execveat.symlink", O_RDONLY); | |
219 | + int fd_denatured = open_or_die("execveat.denatured", O_RDONLY); | |
220 | + int fd_denatured_path = open_or_die("execveat.denatured", | |
221 | + O_RDONLY|O_PATH); | |
222 | + int fd_script = open_or_die("script", O_RDONLY); | |
223 | + int fd_ephemeral = open_or_die("execveat.ephemeral", O_RDONLY); | |
224 | + int fd_ephemeral_path = open_or_die("execveat.path.ephemeral", | |
225 | + O_RDONLY|O_PATH); | |
226 | + int fd_script_ephemeral = open_or_die("script.ephemeral", O_RDONLY); | |
227 | + int fd_cloexec = open_or_die("execveat", O_RDONLY|O_CLOEXEC); | |
228 | + int fd_script_cloexec = open_or_die("script", O_RDONLY|O_CLOEXEC); | |
229 | + | |
230 | + /* Change file position to confirm it doesn't affect anything */ | |
231 | + lseek(fd, 10, SEEK_SET); | |
232 | + | |
233 | + /* Normal executable file: */ | |
234 | + /* dfd + path */ | |
235 | + fail += check_execveat(subdir_dfd, "../execveat", 0); | |
236 | + fail += check_execveat(dot_dfd, "execveat", 0); | |
237 | + fail += check_execveat(dot_dfd_path, "execveat", 0); | |
238 | + /* absolute path */ | |
239 | + fail += check_execveat(AT_FDCWD, fullname, 0); | |
240 | + /* absolute path with nonsense dfd */ | |
241 | + fail += check_execveat(99, fullname, 0); | |
242 | + /* fd + no path */ | |
243 | + fail += check_execveat(fd, "", AT_EMPTY_PATH); | |
244 | + /* O_CLOEXEC fd + no path */ | |
245 | + fail += check_execveat(fd_cloexec, "", AT_EMPTY_PATH); | |
246 | + /* O_PATH fd */ | |
247 | + fail += check_execveat(fd_path, "", AT_EMPTY_PATH); | |
248 | + | |
249 | + /* Mess with executable file that's already open: */ | |
250 | + /* fd + no path to a file that's been renamed */ | |
251 | + rename("execveat.ephemeral", "execveat.moved"); | |
252 | + fail += check_execveat(fd_ephemeral, "", AT_EMPTY_PATH); | |
253 | + /* fd + no path to a file that's been deleted */ | |
254 | + unlink("execveat.moved"); /* remove the file now fd open */ | |
255 | + fail += check_execveat(fd_ephemeral, "", AT_EMPTY_PATH); | |
256 | + | |
257 | + /* Mess with executable file that's already open with O_PATH */ | |
258 | + /* fd + no path to a file that's been deleted */ | |
259 | + unlink("execveat.path.ephemeral"); | |
260 | + fail += check_execveat(fd_ephemeral_path, "", AT_EMPTY_PATH); | |
261 | + | |
262 | + /* Invalid argument failures */ | |
263 | + fail += check_execveat_fail(fd, "", 0, ENOENT); | |
264 | + fail += check_execveat_fail(fd, NULL, AT_EMPTY_PATH, EFAULT); | |
265 | + | |
266 | + /* Symlink to executable file: */ | |
267 | + /* dfd + path */ | |
268 | + fail += check_execveat(dot_dfd, "execveat.symlink", 0); | |
269 | + fail += check_execveat(dot_dfd_path, "execveat.symlink", 0); | |
270 | + /* absolute path */ | |
271 | + fail += check_execveat(AT_FDCWD, fullname_symlink, 0); | |
272 | + /* fd + no path, even with AT_SYMLINK_NOFOLLOW (already followed) */ | |
273 | + fail += check_execveat(fd_symlink, "", AT_EMPTY_PATH); | |
274 | + fail += check_execveat(fd_symlink, "", | |
275 | + AT_EMPTY_PATH|AT_SYMLINK_NOFOLLOW); | |
276 | + | |
277 | + /* Symlink fails when AT_SYMLINK_NOFOLLOW set: */ | |
278 | + /* dfd + path */ | |
279 | + fail += check_execveat_fail(dot_dfd, "execveat.symlink", | |
280 | + AT_SYMLINK_NOFOLLOW, ELOOP); | |
281 | + fail += check_execveat_fail(dot_dfd_path, "execveat.symlink", | |
282 | + AT_SYMLINK_NOFOLLOW, ELOOP); | |
283 | + /* absolute path */ | |
284 | + fail += check_execveat_fail(AT_FDCWD, fullname_symlink, | |
285 | + AT_SYMLINK_NOFOLLOW, ELOOP); | |
286 | + | |
287 | + /* Shell script wrapping executable file: */ | |
288 | + /* dfd + path */ | |
289 | + fail += check_execveat(subdir_dfd, "../script", 0); | |
290 | + fail += check_execveat(dot_dfd, "script", 0); | |
291 | + fail += check_execveat(dot_dfd_path, "script", 0); | |
292 | + /* absolute path */ | |
293 | + fail += check_execveat(AT_FDCWD, fullname_script, 0); | |
294 | + /* fd + no path */ | |
295 | + fail += check_execveat(fd_script, "", AT_EMPTY_PATH); | |
296 | + fail += check_execveat(fd_script, "", | |
297 | + AT_EMPTY_PATH|AT_SYMLINK_NOFOLLOW); | |
298 | + /* O_CLOEXEC fd fails for a script (as script file inaccessible) */ | |
299 | + fail += check_execveat_fail(fd_script_cloexec, "", AT_EMPTY_PATH, | |
300 | + ENOENT); | |
301 | + fail += check_execveat_fail(dot_dfd_cloexec, "script", 0, ENOENT); | |
302 | + | |
303 | + /* Mess with script file that's already open: */ | |
304 | + /* fd + no path to a file that's been renamed */ | |
305 | + rename("script.ephemeral", "script.moved"); | |
306 | + fail += check_execveat(fd_script_ephemeral, "", AT_EMPTY_PATH); | |
307 | + /* fd + no path to a file that's been deleted */ | |
308 | + unlink("script.moved"); /* remove the file while fd open */ | |
309 | + fail += check_execveat(fd_script_ephemeral, "", AT_EMPTY_PATH); | |
310 | + | |
311 | + /* Rename a subdirectory in the path: */ | |
312 | + rename("subdir.ephemeral", "subdir.moved"); | |
313 | + fail += check_execveat(subdir_dfd_ephemeral, "../script", 0); | |
314 | + fail += check_execveat(subdir_dfd_ephemeral, "script", 0); | |
315 | + /* Remove the subdir and its contents */ | |
316 | + unlink("subdir.moved/script"); | |
317 | + unlink("subdir.moved"); | |
318 | + /* Shell loads via deleted subdir OK because name starts with .. */ | |
319 | + fail += check_execveat(subdir_dfd_ephemeral, "../script", 0); | |
320 | + fail += check_execveat_fail(subdir_dfd_ephemeral, "script", 0, ENOENT); | |
321 | + | |
322 | + /* Flag values other than AT_SYMLINK_NOFOLLOW => EINVAL */ | |
323 | + fail += check_execveat_fail(dot_dfd, "execveat", 0xFFFF, EINVAL); | |
324 | + /* Invalid path => ENOENT */ | |
325 | + fail += check_execveat_fail(dot_dfd, "no-such-file", 0, ENOENT); | |
326 | + fail += check_execveat_fail(dot_dfd_path, "no-such-file", 0, ENOENT); | |
327 | + fail += check_execveat_fail(AT_FDCWD, "no-such-file", 0, ENOENT); | |
328 | + /* Attempt to execute directory => EACCES */ | |
329 | + fail += check_execveat_fail(dot_dfd, "", AT_EMPTY_PATH, EACCES); | |
330 | + /* Attempt to execute non-executable => EACCES */ | |
331 | + fail += check_execveat_fail(dot_dfd, "Makefile", 0, EACCES); | |
332 | + fail += check_execveat_fail(fd_denatured, "", AT_EMPTY_PATH, EACCES); | |
333 | + fail += check_execveat_fail(fd_denatured_path, "", AT_EMPTY_PATH, | |
334 | + EACCES); | |
335 | + /* Attempt to execute nonsense FD => EBADF */ | |
336 | + fail += check_execveat_fail(99, "", AT_EMPTY_PATH, EBADF); | |
337 | + fail += check_execveat_fail(99, "execveat", 0, EBADF); | |
338 | + /* Attempt to execute relative to non-directory => ENOTDIR */ | |
339 | + fail += check_execveat_fail(fd, "execveat", 0, ENOTDIR); | |
340 | + | |
341 | + fail += check_execveat_pathmax(dot_dfd, "execveat", 0); | |
342 | + fail += check_execveat_pathmax(dot_dfd, "script", 1); | |
343 | + return fail; | |
344 | +} | |
345 | + | |
346 | +static void prerequisites(void) | |
347 | +{ | |
348 | + int fd; | |
349 | + const char *script = "#!/bin/sh\nexit $*\n"; | |
350 | + | |
351 | + /* Create ephemeral copies of files */ | |
352 | + exe_cp("execveat", "execveat.ephemeral"); | |
353 | + exe_cp("execveat", "execveat.path.ephemeral"); | |
354 | + exe_cp("script", "script.ephemeral"); | |
355 | + mkdir("subdir.ephemeral", 0755); | |
356 | + | |
357 | + fd = open("subdir.ephemeral/script", O_RDWR|O_CREAT|O_TRUNC, 0755); | |
358 | + write(fd, script, strlen(script)); | |
359 | + close(fd); | |
360 | +} | |
361 | + | |
362 | +int main(int argc, char **argv) | |
363 | +{ | |
364 | + int ii; | |
365 | + int rc; | |
366 | + const char *verbose = getenv("VERBOSE"); | |
367 | + | |
368 | + if (argc >= 2) { | |
369 | + /* If we are invoked with an argument, don't run tests. */ | |
370 | + const char *in_test = getenv("IN_TEST"); | |
371 | + | |
372 | + if (verbose) { | |
373 | + printf(" invoked with:"); | |
374 | + for (ii = 0; ii < argc; ii++) | |
375 | + printf(" [%d]='%s'", ii, argv[ii]); | |
376 | + printf("\n"); | |
377 | + } | |
378 | + | |
379 | + /* Check expected environment transferred. */ | |
380 | + if (!in_test || strcmp(in_test, "yes") != 0) { | |
381 | + printf("[FAIL] (no IN_TEST=yes in env)\n"); | |
382 | + return 1; | |
383 | + } | |
384 | + | |
385 | + /* Use the final argument as an exit code. */ | |
386 | + rc = atoi(argv[argc - 1]); | |
387 | + fflush(stdout); | |
388 | + } else { | |
389 | + prerequisites(); | |
390 | + if (verbose) | |
391 | + envp[1] = "VERBOSE=1"; | |
392 | + rc = run_tests(); | |
393 | + if (rc > 0) | |
394 | + printf("%d tests failed\n", rc); | |
395 | + } | |
396 | + return rc; | |
397 | +} |