Path: blob/master/tools/testing/selftests/exec/execveat.c
26285 views
// SPDX-License-Identifier: GPL-2.0-only1/*2* Copyright (c) 2014 Google, Inc.3*4* Selftests for execveat(2).5*/67#ifndef _GNU_SOURCE8#define _GNU_SOURCE /* to get O_PATH, AT_EMPTY_PATH */9#endif10#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>2223#include "../kselftest.h"2425#define TESTS_EXPECTED 5426#define TEST_NAME_LEN (PATH_MAX * 4)2728#define CHECK_COMM "CHECK_COMM"2930static char longpath[2 * PATH_MAX] = "";31static char *envp[] = { "IN_TEST=yes", NULL, NULL };32static char *argv[] = { "execveat", "99", NULL };3334static int execveat_(int fd, const char *path, char **argv, char **envp,35int flags)36{37#ifdef __NR_execveat38return syscall(__NR_execveat, fd, path, argv, envp, flags);39#else40errno = ENOSYS;41return -1;42#endif43}4445#define check_execveat_fail(fd, path, flags, errno) \46_check_execveat_fail(fd, path, flags, errno, #errno)47static int _check_execveat_fail(int fd, const char *path, int flags,48int expected_errno, const char *errno_str)49{50char test_name[TEST_NAME_LEN];51int rc;5253errno = 0;54snprintf(test_name, sizeof(test_name),55"Check failure of execveat(%d, '%s', %d) with %s",56fd, path?:"(null)", flags, errno_str);57rc = execveat_(fd, path, argv, envp, flags);5859if (rc > 0) {60ksft_print_msg("unexpected success from execveat(2)\n");61ksft_test_result_fail("%s\n", test_name);62return 1;63}64if (errno != expected_errno) {65ksft_print_msg("expected errno %d (%s) not %d (%s)\n",66expected_errno, strerror(expected_errno),67errno, strerror(errno));68ksft_test_result_fail("%s\n", test_name);69return 1;70}71ksft_test_result_pass("%s\n", test_name);72return 0;73}7475static int check_execveat_invoked_rc(int fd, const char *path, int flags,76int expected_rc, int expected_rc2)77{78char test_name[TEST_NAME_LEN];79int status;80int rc;81pid_t child;82int pathlen = path ? strlen(path) : 0;8384if (pathlen > 40)85snprintf(test_name, sizeof(test_name),86"Check success of execveat(%d, '%.20s...%s', %d)... ",87fd, path, (path + pathlen - 20), flags);88else89snprintf(test_name, sizeof(test_name),90"Check success of execveat(%d, '%s', %d)... ",91fd, path?:"(null)", flags);9293child = fork();94if (child < 0) {95ksft_perror("fork() failed");96ksft_test_result_fail("%s\n", test_name);97return 1;98}99if (child == 0) {100/* Child: do execveat(). */101rc = execveat_(fd, path, argv, envp, flags);102ksft_print_msg("child execveat() failed, rc=%d errno=%d (%s)\n",103rc, errno, strerror(errno));104exit(errno);105}106/* Parent: wait for & check child's exit status. */107rc = waitpid(child, &status, 0);108if (rc != child) {109ksft_print_msg("waitpid(%d,...) returned %d\n", child, rc);110ksft_test_result_fail("%s\n", test_name);111return 1;112}113if (!WIFEXITED(status)) {114ksft_print_msg("child %d did not exit cleanly, status=%08x\n",115child, status);116ksft_test_result_fail("%s\n", test_name);117return 1;118}119if ((WEXITSTATUS(status) != expected_rc) &&120(WEXITSTATUS(status) != expected_rc2)) {121ksft_print_msg("child %d exited with %d neither %d nor %d\n",122child, WEXITSTATUS(status), expected_rc,123expected_rc2);124ksft_test_result_fail("%s\n", test_name);125return 1;126}127ksft_test_result_pass("%s\n", test_name);128return 0;129}130131static int check_execveat(int fd, const char *path, int flags)132{133return check_execveat_invoked_rc(fd, path, flags, 99, 99);134}135136static char *concat(const char *left, const char *right)137{138char *result = malloc(strlen(left) + strlen(right) + 1);139140strcpy(result, left);141strcat(result, right);142return result;143}144145static int open_or_die(const char *filename, int flags)146{147int fd = open(filename, flags);148149if (fd < 0)150ksft_exit_fail_msg("Failed to open '%s'; "151"check prerequisites are available\n", filename);152return fd;153}154155static void exe_cp(const char *src, const char *dest)156{157int in_fd = open_or_die(src, O_RDONLY);158int out_fd = open(dest, O_RDWR|O_CREAT|O_TRUNC, 0755);159struct stat info;160161fstat(in_fd, &info);162sendfile(out_fd, in_fd, NULL, info.st_size);163close(in_fd);164close(out_fd);165}166167#define XX_DIR_LEN 200168static int check_execveat_pathmax(int root_dfd, const char *src, int is_script)169{170int fail = 0;171int ii, count, len;172char longname[XX_DIR_LEN + 1];173int fd;174175if (*longpath == '\0') {176/* Create a filename close to PATH_MAX in length */177char *cwd = getcwd(NULL, 0);178179if (!cwd) {180ksft_perror("Failed to getcwd()");181return 2;182}183strcpy(longpath, cwd);184strcat(longpath, "/");185memset(longname, 'x', XX_DIR_LEN - 1);186longname[XX_DIR_LEN - 1] = '/';187longname[XX_DIR_LEN] = '\0';188count = (PATH_MAX - 3 - strlen(cwd)) / XX_DIR_LEN;189for (ii = 0; ii < count; ii++) {190strcat(longpath, longname);191mkdir(longpath, 0755);192}193len = (PATH_MAX - 3 - strlen(cwd)) - (count * XX_DIR_LEN);194if (len <= 0)195len = 1;196memset(longname, 'y', len);197longname[len] = '\0';198strcat(longpath, longname);199free(cwd);200}201exe_cp(src, longpath);202203/*204* Execute as a pre-opened file descriptor, which works whether this is205* a script or not (because the interpreter sees a filename like206* "/dev/fd/20").207*/208fd = open(longpath, O_RDONLY);209if (fd > 0) {210ksft_print_msg("Invoke copy of '%s' via filename of length %zu:\n",211src, strlen(longpath));212fail += check_execveat(fd, "", AT_EMPTY_PATH);213} else {214ksft_print_msg("Failed to open length %zu filename, errno=%d (%s)\n",215strlen(longpath), errno, strerror(errno));216fail++;217}218219/*220* Execute as a long pathname relative to "/". If this is a script,221* the interpreter will launch but fail to open the script because its222* name ("/dev/fd/5/xxx....") is bigger than PATH_MAX.223*224* The failure code is usually 127 (POSIX: "If a command is not found,225* the exit status shall be 127."), but some systems give 126 (POSIX:226* "If the command name is found, but it is not an executable utility,227* the exit status shall be 126."), so allow either.228*/229if (is_script) {230ksft_print_msg("Invoke script via root_dfd and relative filename\n");231fail += check_execveat_invoked_rc(root_dfd, longpath + 1, 0,232127, 126);233} else {234ksft_print_msg("Invoke exec via root_dfd and relative filename\n");235fail += check_execveat(root_dfd, longpath + 1, 0);236}237238return fail;239}240241static int check_execveat_comm(int fd, char *argv0, char *expected)242{243char buf[128], *old_env, *old_argv0;244int ret;245246snprintf(buf, sizeof(buf), CHECK_COMM "=%s", expected);247248old_env = envp[1];249envp[1] = buf;250251old_argv0 = argv[0];252argv[0] = argv0;253254ksft_print_msg("Check execveat(AT_EMPTY_PATH)'s comm is %s\n",255expected);256ret = check_execveat_invoked_rc(fd, "", AT_EMPTY_PATH, 0, 0);257258envp[1] = old_env;259argv[0] = old_argv0;260261return ret;262}263264static int run_tests(void)265{266int fail = 0;267char *fullname = realpath("execveat", NULL);268char *fullname_script = realpath("script", NULL);269char *fullname_symlink = concat(fullname, ".symlink");270int subdir_dfd = open_or_die("subdir", O_DIRECTORY|O_RDONLY);271int subdir_dfd_ephemeral = open_or_die("subdir.ephemeral",272O_DIRECTORY|O_RDONLY);273int dot_dfd = open_or_die(".", O_DIRECTORY|O_RDONLY);274int root_dfd = open_or_die("/", O_DIRECTORY|O_RDONLY);275int dot_dfd_path = open_or_die(".", O_DIRECTORY|O_RDONLY|O_PATH);276int dot_dfd_cloexec = open_or_die(".", O_DIRECTORY|O_RDONLY|O_CLOEXEC);277int fd = open_or_die("execveat", O_RDONLY);278int fd_path = open_or_die("execveat", O_RDONLY|O_PATH);279int fd_symlink = open_or_die("execveat.symlink", O_RDONLY);280int fd_denatured = open_or_die("execveat.denatured", O_RDONLY);281int fd_denatured_path = open_or_die("execveat.denatured",282O_RDONLY|O_PATH);283int fd_script = open_or_die("script", O_RDONLY);284int fd_ephemeral = open_or_die("execveat.ephemeral", O_RDONLY);285int fd_ephemeral_path = open_or_die("execveat.path.ephemeral",286O_RDONLY|O_PATH);287int fd_script_ephemeral = open_or_die("script.ephemeral", O_RDONLY);288int fd_cloexec = open_or_die("execveat", O_RDONLY|O_CLOEXEC);289int fd_script_cloexec = open_or_die("script", O_RDONLY|O_CLOEXEC);290291/* Check if we have execveat at all, and bail early if not */292errno = 0;293execveat_(-1, NULL, NULL, NULL, 0);294if (errno == ENOSYS) {295ksft_exit_skip(296"ENOSYS calling execveat - no kernel support?\n");297}298299/* Change file position to confirm it doesn't affect anything */300lseek(fd, 10, SEEK_SET);301302/* Normal executable file: */303/* dfd + path */304fail += check_execveat(subdir_dfd, "../execveat", 0);305fail += check_execveat(dot_dfd, "execveat", 0);306fail += check_execveat(dot_dfd_path, "execveat", 0);307/* absolute path */308fail += check_execveat(AT_FDCWD, fullname, 0);309/* absolute path with nonsense dfd */310fail += check_execveat(99, fullname, 0);311/* fd + no path */312fail += check_execveat(fd, "", AT_EMPTY_PATH);313/* O_CLOEXEC fd + no path */314fail += check_execveat(fd_cloexec, "", AT_EMPTY_PATH);315/* O_PATH fd */316fail += check_execveat(fd_path, "", AT_EMPTY_PATH);317318/* Mess with executable file that's already open: */319/* fd + no path to a file that's been renamed */320rename("execveat.ephemeral", "execveat.moved");321fail += check_execveat(fd_ephemeral, "", AT_EMPTY_PATH);322/* fd + no path to a file that's been deleted */323unlink("execveat.moved"); /* remove the file now fd open */324fail += check_execveat(fd_ephemeral, "", AT_EMPTY_PATH);325326/* Mess with executable file that's already open with O_PATH */327/* fd + no path to a file that's been deleted */328unlink("execveat.path.ephemeral");329fail += check_execveat(fd_ephemeral_path, "", AT_EMPTY_PATH);330331/* Invalid argument failures */332fail += check_execveat_fail(fd, "", 0, ENOENT);333fail += check_execveat_fail(fd, NULL, AT_EMPTY_PATH, EFAULT);334335/* Symlink to executable file: */336/* dfd + path */337fail += check_execveat(dot_dfd, "execveat.symlink", 0);338fail += check_execveat(dot_dfd_path, "execveat.symlink", 0);339/* absolute path */340fail += check_execveat(AT_FDCWD, fullname_symlink, 0);341/* fd + no path, even with AT_SYMLINK_NOFOLLOW (already followed) */342fail += check_execveat(fd_symlink, "", AT_EMPTY_PATH);343fail += check_execveat(fd_symlink, "",344AT_EMPTY_PATH|AT_SYMLINK_NOFOLLOW);345346/* Symlink fails when AT_SYMLINK_NOFOLLOW set: */347/* dfd + path */348fail += check_execveat_fail(dot_dfd, "execveat.symlink",349AT_SYMLINK_NOFOLLOW, ELOOP);350fail += check_execveat_fail(dot_dfd_path, "execveat.symlink",351AT_SYMLINK_NOFOLLOW, ELOOP);352/* absolute path */353fail += check_execveat_fail(AT_FDCWD, fullname_symlink,354AT_SYMLINK_NOFOLLOW, ELOOP);355356/* Non-regular file failure */357fail += check_execveat_fail(dot_dfd, "pipe", 0, EACCES);358unlink("pipe");359360/* Shell script wrapping executable file: */361/* dfd + path */362fail += check_execveat(subdir_dfd, "../script", 0);363fail += check_execveat(dot_dfd, "script", 0);364fail += check_execveat(dot_dfd_path, "script", 0);365/* absolute path */366fail += check_execveat(AT_FDCWD, fullname_script, 0);367/* fd + no path */368fail += check_execveat(fd_script, "", AT_EMPTY_PATH);369fail += check_execveat(fd_script, "",370AT_EMPTY_PATH|AT_SYMLINK_NOFOLLOW);371/* O_CLOEXEC fd fails for a script (as script file inaccessible) */372fail += check_execveat_fail(fd_script_cloexec, "", AT_EMPTY_PATH,373ENOENT);374fail += check_execveat_fail(dot_dfd_cloexec, "script", 0, ENOENT);375376/* Mess with script file that's already open: */377/* fd + no path to a file that's been renamed */378rename("script.ephemeral", "script.moved");379fail += check_execveat(fd_script_ephemeral, "", AT_EMPTY_PATH);380/* fd + no path to a file that's been deleted */381unlink("script.moved"); /* remove the file while fd open */382fail += check_execveat(fd_script_ephemeral, "", AT_EMPTY_PATH);383384/* Rename a subdirectory in the path: */385rename("subdir.ephemeral", "subdir.moved");386fail += check_execveat(subdir_dfd_ephemeral, "../script", 0);387fail += check_execveat(subdir_dfd_ephemeral, "script", 0);388/* Remove the subdir and its contents */389unlink("subdir.moved/script");390unlink("subdir.moved");391/* Shell loads via deleted subdir OK because name starts with .. */392fail += check_execveat(subdir_dfd_ephemeral, "../script", 0);393fail += check_execveat_fail(subdir_dfd_ephemeral, "script", 0, ENOENT);394395/* Flag values other than AT_SYMLINK_NOFOLLOW => EINVAL */396fail += check_execveat_fail(dot_dfd, "execveat", 0xFFFF, EINVAL);397/* Invalid path => ENOENT */398fail += check_execveat_fail(dot_dfd, "no-such-file", 0, ENOENT);399fail += check_execveat_fail(dot_dfd_path, "no-such-file", 0, ENOENT);400fail += check_execveat_fail(AT_FDCWD, "no-such-file", 0, ENOENT);401/* Attempt to execute directory => EACCES */402fail += check_execveat_fail(dot_dfd, "", AT_EMPTY_PATH, EACCES);403/* Attempt to execute non-executable => EACCES */404fail += check_execveat_fail(dot_dfd, "Makefile", 0, EACCES);405fail += check_execveat_fail(fd_denatured, "", AT_EMPTY_PATH, EACCES);406fail += check_execveat_fail(fd_denatured_path, "", AT_EMPTY_PATH,407EACCES);408/* Attempt to execute nonsense FD => EBADF */409fail += check_execveat_fail(99, "", AT_EMPTY_PATH, EBADF);410fail += check_execveat_fail(99, "execveat", 0, EBADF);411/* Attempt to execute relative to non-directory => ENOTDIR */412fail += check_execveat_fail(fd, "execveat", 0, ENOTDIR);413414fail += check_execveat_pathmax(root_dfd, "execveat", 0);415fail += check_execveat_pathmax(root_dfd, "script", 1);416417/* /proc/pid/comm gives filename by default */418fail += check_execveat_comm(fd, "sentinel", "execveat");419/* /proc/pid/comm gives argv[0] when invoked via link */420fail += check_execveat_comm(fd_symlink, "sentinel", "execveat");421/* /proc/pid/comm gives filename if NULL is passed */422fail += check_execveat_comm(fd, NULL, "execveat");423424return fail;425}426427static void prerequisites(void)428{429int fd;430const char *script = "#!/bin/bash\nexit $*\n";431432/* Create ephemeral copies of files */433exe_cp("execveat", "execveat.ephemeral");434exe_cp("execveat", "execveat.path.ephemeral");435exe_cp("script", "script.ephemeral");436mkdir("subdir.ephemeral", 0755);437438fd = open("subdir.ephemeral/script", O_RDWR|O_CREAT|O_TRUNC, 0755);439write(fd, script, strlen(script));440close(fd);441442mkfifo("pipe", 0755);443}444445int main(int argc, char **argv)446{447int ii;448int rc;449const char *verbose = getenv("VERBOSE");450const char *check_comm = getenv(CHECK_COMM);451452if (argc >= 2 || check_comm) {453/*454* If we are invoked with an argument, or no arguments but a455* command to check, don't run tests.456*/457const char *in_test = getenv("IN_TEST");458459if (verbose) {460ksft_print_msg("invoked with:\n");461for (ii = 0; ii < argc; ii++)462ksft_print_msg("\t[%d]='%s\n'", ii, argv[ii]);463}464465/* If the tests wanted us to check the command, do so. */466if (check_comm) {467/* TASK_COMM_LEN == 16 */468char buf[32];469int fd, ret;470471fd = open("/proc/self/comm", O_RDONLY);472if (fd < 0) {473ksft_perror("open() comm failed");474exit(1);475}476477ret = read(fd, buf, sizeof(buf));478if (ret < 0) {479ksft_perror("read() comm failed");480close(fd);481exit(1);482}483close(fd);484485// trim off the \n486buf[ret-1] = 0;487488if (strcmp(buf, check_comm)) {489ksft_print_msg("bad comm, got: %s expected: %s\n",490buf, check_comm);491exit(1);492}493494exit(0);495}496497/* Check expected environment transferred. */498if (!in_test || strcmp(in_test, "yes") != 0) {499ksft_print_msg("no IN_TEST=yes in env\n");500return 1;501}502503/* Use the final argument as an exit code. */504rc = atoi(argv[argc - 1]);505exit(rc);506} else {507ksft_print_header();508ksft_set_plan(TESTS_EXPECTED);509prerequisites();510if (verbose)511envp[1] = "VERBOSE=1";512rc = run_tests();513if (rc > 0)514printf("%d tests failed\n", rc);515ksft_finished();516}517518return rc;519}520521522