execveat.c (13628B)
1// SPDX-License-Identifier: GPL-2.0-only 2/* 3 * Copyright (c) 2014 Google, Inc. 4 * 5 * Selftests for execveat(2). 6 */ 7 8#ifndef _GNU_SOURCE 9#define _GNU_SOURCE /* to get O_PATH, AT_EMPTY_PATH */ 10#endif 11#include <sys/sendfile.h> 12#include <sys/stat.h> 13#include <sys/syscall.h> 14#include <sys/types.h> 15#include <sys/wait.h> 16#include <errno.h> 17#include <fcntl.h> 18#include <limits.h> 19#include <stdio.h> 20#include <stdlib.h> 21#include <string.h> 22#include <unistd.h> 23 24#include "../kselftest.h" 25 26static char longpath[2 * PATH_MAX] = ""; 27static char *envp[] = { "IN_TEST=yes", NULL, NULL }; 28static char *argv[] = { "execveat", "99", NULL }; 29 30static int execveat_(int fd, const char *path, char **argv, char **envp, 31 int flags) 32{ 33#ifdef __NR_execveat 34 return syscall(__NR_execveat, fd, path, argv, envp, flags); 35#else 36 errno = ENOSYS; 37 return -1; 38#endif 39} 40 41#define check_execveat_fail(fd, path, flags, errno) \ 42 _check_execveat_fail(fd, path, flags, errno, #errno) 43static int _check_execveat_fail(int fd, const char *path, int flags, 44 int expected_errno, const char *errno_str) 45{ 46 int rc; 47 48 errno = 0; 49 printf("Check failure of execveat(%d, '%s', %d) with %s... ", 50 fd, path?:"(null)", flags, errno_str); 51 rc = execveat_(fd, path, argv, envp, flags); 52 53 if (rc > 0) { 54 printf("[FAIL] (unexpected success from execveat(2))\n"); 55 return 1; 56 } 57 if (errno != expected_errno) { 58 printf("[FAIL] (expected errno %d (%s) not %d (%s)\n", 59 expected_errno, strerror(expected_errno), 60 errno, strerror(errno)); 61 return 1; 62 } 63 printf("[OK]\n"); 64 return 0; 65} 66 67static int check_execveat_invoked_rc(int fd, const char *path, int flags, 68 int expected_rc, int expected_rc2) 69{ 70 int status; 71 int rc; 72 pid_t child; 73 int pathlen = path ? strlen(path) : 0; 74 75 if (pathlen > 40) 76 printf("Check success of execveat(%d, '%.20s...%s', %d)... ", 77 fd, path, (path + pathlen - 20), flags); 78 else 79 printf("Check success of execveat(%d, '%s', %d)... ", 80 fd, path?:"(null)", flags); 81 child = fork(); 82 if (child < 0) { 83 printf("[FAIL] (fork() failed)\n"); 84 return 1; 85 } 86 if (child == 0) { 87 /* Child: do execveat(). */ 88 rc = execveat_(fd, path, argv, envp, flags); 89 printf("[FAIL]: execveat() failed, rc=%d errno=%d (%s)\n", 90 rc, errno, strerror(errno)); 91 exit(1); /* should not reach here */ 92 } 93 /* Parent: wait for & check child's exit status. */ 94 rc = waitpid(child, &status, 0); 95 if (rc != child) { 96 printf("[FAIL] (waitpid(%d,...) returned %d)\n", child, rc); 97 return 1; 98 } 99 if (!WIFEXITED(status)) { 100 printf("[FAIL] (child %d did not exit cleanly, status=%08x)\n", 101 child, status); 102 return 1; 103 } 104 if ((WEXITSTATUS(status) != expected_rc) && 105 (WEXITSTATUS(status) != expected_rc2)) { 106 printf("[FAIL] (child %d exited with %d not %d nor %d)\n", 107 child, WEXITSTATUS(status), expected_rc, expected_rc2); 108 return 1; 109 } 110 printf("[OK]\n"); 111 return 0; 112} 113 114static int check_execveat(int fd, const char *path, int flags) 115{ 116 return check_execveat_invoked_rc(fd, path, flags, 99, 99); 117} 118 119static char *concat(const char *left, const char *right) 120{ 121 char *result = malloc(strlen(left) + strlen(right) + 1); 122 123 strcpy(result, left); 124 strcat(result, right); 125 return result; 126} 127 128static int open_or_die(const char *filename, int flags) 129{ 130 int fd = open(filename, flags); 131 132 if (fd < 0) { 133 printf("Failed to open '%s'; " 134 "check prerequisites are available\n", filename); 135 exit(1); 136 } 137 return fd; 138} 139 140static void exe_cp(const char *src, const char *dest) 141{ 142 int in_fd = open_or_die(src, O_RDONLY); 143 int out_fd = open(dest, O_RDWR|O_CREAT|O_TRUNC, 0755); 144 struct stat info; 145 146 fstat(in_fd, &info); 147 sendfile(out_fd, in_fd, NULL, info.st_size); 148 close(in_fd); 149 close(out_fd); 150} 151 152#define XX_DIR_LEN 200 153static int check_execveat_pathmax(int root_dfd, const char *src, int is_script) 154{ 155 int fail = 0; 156 int ii, count, len; 157 char longname[XX_DIR_LEN + 1]; 158 int fd; 159 160 if (*longpath == '\0') { 161 /* Create a filename close to PATH_MAX in length */ 162 char *cwd = getcwd(NULL, 0); 163 164 if (!cwd) { 165 printf("Failed to getcwd(), errno=%d (%s)\n", 166 errno, strerror(errno)); 167 return 2; 168 } 169 strcpy(longpath, cwd); 170 strcat(longpath, "/"); 171 memset(longname, 'x', XX_DIR_LEN - 1); 172 longname[XX_DIR_LEN - 1] = '/'; 173 longname[XX_DIR_LEN] = '\0'; 174 count = (PATH_MAX - 3 - strlen(cwd)) / XX_DIR_LEN; 175 for (ii = 0; ii < count; ii++) { 176 strcat(longpath, longname); 177 mkdir(longpath, 0755); 178 } 179 len = (PATH_MAX - 3 - strlen(cwd)) - (count * XX_DIR_LEN); 180 if (len <= 0) 181 len = 1; 182 memset(longname, 'y', len); 183 longname[len] = '\0'; 184 strcat(longpath, longname); 185 free(cwd); 186 } 187 exe_cp(src, longpath); 188 189 /* 190 * Execute as a pre-opened file descriptor, which works whether this is 191 * a script or not (because the interpreter sees a filename like 192 * "/dev/fd/20"). 193 */ 194 fd = open(longpath, O_RDONLY); 195 if (fd > 0) { 196 printf("Invoke copy of '%s' via filename of length %zu:\n", 197 src, strlen(longpath)); 198 fail += check_execveat(fd, "", AT_EMPTY_PATH); 199 } else { 200 printf("Failed to open length %zu filename, errno=%d (%s)\n", 201 strlen(longpath), errno, strerror(errno)); 202 fail++; 203 } 204 205 /* 206 * Execute as a long pathname relative to "/". If this is a script, 207 * the interpreter will launch but fail to open the script because its 208 * name ("/dev/fd/5/xxx....") is bigger than PATH_MAX. 209 * 210 * The failure code is usually 127 (POSIX: "If a command is not found, 211 * the exit status shall be 127."), but some systems give 126 (POSIX: 212 * "If the command name is found, but it is not an executable utility, 213 * the exit status shall be 126."), so allow either. 214 */ 215 if (is_script) 216 fail += check_execveat_invoked_rc(root_dfd, longpath + 1, 0, 217 127, 126); 218 else 219 fail += check_execveat(root_dfd, longpath + 1, 0); 220 221 return fail; 222} 223 224static int run_tests(void) 225{ 226 int fail = 0; 227 char *fullname = realpath("execveat", NULL); 228 char *fullname_script = realpath("script", NULL); 229 char *fullname_symlink = concat(fullname, ".symlink"); 230 int subdir_dfd = open_or_die("subdir", O_DIRECTORY|O_RDONLY); 231 int subdir_dfd_ephemeral = open_or_die("subdir.ephemeral", 232 O_DIRECTORY|O_RDONLY); 233 int dot_dfd = open_or_die(".", O_DIRECTORY|O_RDONLY); 234 int root_dfd = open_or_die("/", O_DIRECTORY|O_RDONLY); 235 int dot_dfd_path = open_or_die(".", O_DIRECTORY|O_RDONLY|O_PATH); 236 int dot_dfd_cloexec = open_or_die(".", O_DIRECTORY|O_RDONLY|O_CLOEXEC); 237 int fd = open_or_die("execveat", O_RDONLY); 238 int fd_path = open_or_die("execveat", O_RDONLY|O_PATH); 239 int fd_symlink = open_or_die("execveat.symlink", O_RDONLY); 240 int fd_denatured = open_or_die("execveat.denatured", O_RDONLY); 241 int fd_denatured_path = open_or_die("execveat.denatured", 242 O_RDONLY|O_PATH); 243 int fd_script = open_or_die("script", O_RDONLY); 244 int fd_ephemeral = open_or_die("execveat.ephemeral", O_RDONLY); 245 int fd_ephemeral_path = open_or_die("execveat.path.ephemeral", 246 O_RDONLY|O_PATH); 247 int fd_script_ephemeral = open_or_die("script.ephemeral", O_RDONLY); 248 int fd_cloexec = open_or_die("execveat", O_RDONLY|O_CLOEXEC); 249 int fd_script_cloexec = open_or_die("script", O_RDONLY|O_CLOEXEC); 250 251 /* Check if we have execveat at all, and bail early if not */ 252 errno = 0; 253 execveat_(-1, NULL, NULL, NULL, 0); 254 if (errno == ENOSYS) { 255 ksft_exit_skip( 256 "ENOSYS calling execveat - no kernel support?\n"); 257 } 258 259 /* Change file position to confirm it doesn't affect anything */ 260 lseek(fd, 10, SEEK_SET); 261 262 /* Normal executable file: */ 263 /* dfd + path */ 264 fail += check_execveat(subdir_dfd, "../execveat", 0); 265 fail += check_execveat(dot_dfd, "execveat", 0); 266 fail += check_execveat(dot_dfd_path, "execveat", 0); 267 /* absolute path */ 268 fail += check_execveat(AT_FDCWD, fullname, 0); 269 /* absolute path with nonsense dfd */ 270 fail += check_execveat(99, fullname, 0); 271 /* fd + no path */ 272 fail += check_execveat(fd, "", AT_EMPTY_PATH); 273 /* O_CLOEXEC fd + no path */ 274 fail += check_execveat(fd_cloexec, "", AT_EMPTY_PATH); 275 /* O_PATH fd */ 276 fail += check_execveat(fd_path, "", AT_EMPTY_PATH); 277 278 /* Mess with executable file that's already open: */ 279 /* fd + no path to a file that's been renamed */ 280 rename("execveat.ephemeral", "execveat.moved"); 281 fail += check_execveat(fd_ephemeral, "", AT_EMPTY_PATH); 282 /* fd + no path to a file that's been deleted */ 283 unlink("execveat.moved"); /* remove the file now fd open */ 284 fail += check_execveat(fd_ephemeral, "", AT_EMPTY_PATH); 285 286 /* Mess with executable file that's already open with O_PATH */ 287 /* fd + no path to a file that's been deleted */ 288 unlink("execveat.path.ephemeral"); 289 fail += check_execveat(fd_ephemeral_path, "", AT_EMPTY_PATH); 290 291 /* Invalid argument failures */ 292 fail += check_execveat_fail(fd, "", 0, ENOENT); 293 fail += check_execveat_fail(fd, NULL, AT_EMPTY_PATH, EFAULT); 294 295 /* Symlink to executable file: */ 296 /* dfd + path */ 297 fail += check_execveat(dot_dfd, "execveat.symlink", 0); 298 fail += check_execveat(dot_dfd_path, "execveat.symlink", 0); 299 /* absolute path */ 300 fail += check_execveat(AT_FDCWD, fullname_symlink, 0); 301 /* fd + no path, even with AT_SYMLINK_NOFOLLOW (already followed) */ 302 fail += check_execveat(fd_symlink, "", AT_EMPTY_PATH); 303 fail += check_execveat(fd_symlink, "", 304 AT_EMPTY_PATH|AT_SYMLINK_NOFOLLOW); 305 306 /* Symlink fails when AT_SYMLINK_NOFOLLOW set: */ 307 /* dfd + path */ 308 fail += check_execveat_fail(dot_dfd, "execveat.symlink", 309 AT_SYMLINK_NOFOLLOW, ELOOP); 310 fail += check_execveat_fail(dot_dfd_path, "execveat.symlink", 311 AT_SYMLINK_NOFOLLOW, ELOOP); 312 /* absolute path */ 313 fail += check_execveat_fail(AT_FDCWD, fullname_symlink, 314 AT_SYMLINK_NOFOLLOW, ELOOP); 315 316 /* Non-regular file failure */ 317 fail += check_execveat_fail(dot_dfd, "pipe", 0, EACCES); 318 unlink("pipe"); 319 320 /* Shell script wrapping executable file: */ 321 /* dfd + path */ 322 fail += check_execveat(subdir_dfd, "../script", 0); 323 fail += check_execveat(dot_dfd, "script", 0); 324 fail += check_execveat(dot_dfd_path, "script", 0); 325 /* absolute path */ 326 fail += check_execveat(AT_FDCWD, fullname_script, 0); 327 /* fd + no path */ 328 fail += check_execveat(fd_script, "", AT_EMPTY_PATH); 329 fail += check_execveat(fd_script, "", 330 AT_EMPTY_PATH|AT_SYMLINK_NOFOLLOW); 331 /* O_CLOEXEC fd fails for a script (as script file inaccessible) */ 332 fail += check_execveat_fail(fd_script_cloexec, "", AT_EMPTY_PATH, 333 ENOENT); 334 fail += check_execveat_fail(dot_dfd_cloexec, "script", 0, ENOENT); 335 336 /* Mess with script file that's already open: */ 337 /* fd + no path to a file that's been renamed */ 338 rename("script.ephemeral", "script.moved"); 339 fail += check_execveat(fd_script_ephemeral, "", AT_EMPTY_PATH); 340 /* fd + no path to a file that's been deleted */ 341 unlink("script.moved"); /* remove the file while fd open */ 342 fail += check_execveat(fd_script_ephemeral, "", AT_EMPTY_PATH); 343 344 /* Rename a subdirectory in the path: */ 345 rename("subdir.ephemeral", "subdir.moved"); 346 fail += check_execveat(subdir_dfd_ephemeral, "../script", 0); 347 fail += check_execveat(subdir_dfd_ephemeral, "script", 0); 348 /* Remove the subdir and its contents */ 349 unlink("subdir.moved/script"); 350 unlink("subdir.moved"); 351 /* Shell loads via deleted subdir OK because name starts with .. */ 352 fail += check_execveat(subdir_dfd_ephemeral, "../script", 0); 353 fail += check_execveat_fail(subdir_dfd_ephemeral, "script", 0, ENOENT); 354 355 /* Flag values other than AT_SYMLINK_NOFOLLOW => EINVAL */ 356 fail += check_execveat_fail(dot_dfd, "execveat", 0xFFFF, EINVAL); 357 /* Invalid path => ENOENT */ 358 fail += check_execveat_fail(dot_dfd, "no-such-file", 0, ENOENT); 359 fail += check_execveat_fail(dot_dfd_path, "no-such-file", 0, ENOENT); 360 fail += check_execveat_fail(AT_FDCWD, "no-such-file", 0, ENOENT); 361 /* Attempt to execute directory => EACCES */ 362 fail += check_execveat_fail(dot_dfd, "", AT_EMPTY_PATH, EACCES); 363 /* Attempt to execute non-executable => EACCES */ 364 fail += check_execveat_fail(dot_dfd, "Makefile", 0, EACCES); 365 fail += check_execveat_fail(fd_denatured, "", AT_EMPTY_PATH, EACCES); 366 fail += check_execveat_fail(fd_denatured_path, "", AT_EMPTY_PATH, 367 EACCES); 368 /* Attempt to execute nonsense FD => EBADF */ 369 fail += check_execveat_fail(99, "", AT_EMPTY_PATH, EBADF); 370 fail += check_execveat_fail(99, "execveat", 0, EBADF); 371 /* Attempt to execute relative to non-directory => ENOTDIR */ 372 fail += check_execveat_fail(fd, "execveat", 0, ENOTDIR); 373 374 fail += check_execveat_pathmax(root_dfd, "execveat", 0); 375 fail += check_execveat_pathmax(root_dfd, "script", 1); 376 return fail; 377} 378 379static void prerequisites(void) 380{ 381 int fd; 382 const char *script = "#!/bin/sh\nexit $*\n"; 383 384 /* Create ephemeral copies of files */ 385 exe_cp("execveat", "execveat.ephemeral"); 386 exe_cp("execveat", "execveat.path.ephemeral"); 387 exe_cp("script", "script.ephemeral"); 388 mkdir("subdir.ephemeral", 0755); 389 390 fd = open("subdir.ephemeral/script", O_RDWR|O_CREAT|O_TRUNC, 0755); 391 write(fd, script, strlen(script)); 392 close(fd); 393 394 mkfifo("pipe", 0755); 395} 396 397int main(int argc, char **argv) 398{ 399 int ii; 400 int rc; 401 const char *verbose = getenv("VERBOSE"); 402 403 if (argc >= 2) { 404 /* If we are invoked with an argument, don't run tests. */ 405 const char *in_test = getenv("IN_TEST"); 406 407 if (verbose) { 408 printf(" invoked with:"); 409 for (ii = 0; ii < argc; ii++) 410 printf(" [%d]='%s'", ii, argv[ii]); 411 printf("\n"); 412 } 413 414 /* Check expected environment transferred. */ 415 if (!in_test || strcmp(in_test, "yes") != 0) { 416 printf("[FAIL] (no IN_TEST=yes in env)\n"); 417 return 1; 418 } 419 420 /* Use the final argument as an exit code. */ 421 rc = atoi(argv[argc - 1]); 422 fflush(stdout); 423 } else { 424 prerequisites(); 425 if (verbose) 426 envp[1] = "VERBOSE=1"; 427 rc = run_tests(); 428 if (rc > 0) 429 printf("%d tests failed\n", rc); 430 } 431 return rc; 432}