1 /* SPDX-License-Identifier: GPL-2.0 */ 2 /* Helpers shared by the binfmt_misc selftests. */ 3 #ifndef __SELFTESTS_EXEC_BINFMT_MISC_COMMON_H 4 #define __SELFTESTS_EXEC_BINFMT_MISC_COMMON_H 5 6 #include <errno.h> 7 #include <fcntl.h> 8 #include <libgen.h> 9 #include <limits.h> 10 #include <stdbool.h> 11 #include <stdio.h> 12 #include <stdlib.h> 13 #include <string.h> 14 #include <sys/mount.h> 15 #include <sys/types.h> 16 #include <sys/wait.h> 17 #include <unistd.h> 18 19 #define BINFMT_DIR "/proc/sys/fs/binfmt_misc" 20 #define BINFMT_REG BINFMT_DIR "/register" 21 22 /* comm holds 15 usable chars; a read of /proc/self/comm appends a newline. */ 23 #define TASK_COMM_LEN 16 24 25 /* The canonical payload argv: run_payload() passes it, the payloads assert it. */ 26 #define PAYLOAD_ARGV0 "payload-argv0" 27 #define PAYLOAD_ARG1 "argone" 28 #define PAYLOAD_ARG2 "argtwo" 29 30 /* Exit status run_payload() reports when the exec was refused as unhandled. */ 31 #define RUN_ENOEXEC 42 32 33 static inline int copy_file(const char *src, const char *dst) 34 { 35 char buf[4096]; 36 int in, out; 37 ssize_t n; 38 39 in = open(src, O_RDONLY); 40 if (in < 0) 41 return -1; 42 /* The tests share /tmp, so never write through a name they don't own. */ 43 unlink(dst); 44 out = open(dst, O_WRONLY | O_CREAT | O_EXCL, 0755); 45 if (out < 0) { 46 close(in); 47 return -1; 48 } 49 while ((n = read(in, buf, sizeof(buf))) > 0) { 50 if (write(out, buf, n) != n) { 51 close(in); 52 close(out); 53 return -1; 54 } 55 } 56 close(in); 57 close(out); 58 return n < 0 ? -1 : 0; 59 } 60 61 /* Write @rule to the register file, preserving the write's errno. */ 62 static inline int write_reg(const char *rule) 63 { 64 int fd, saved; 65 ssize_t n; 66 67 fd = open(BINFMT_REG, O_WRONLY); 68 if (fd < 0) 69 return -1; 70 n = write(fd, rule, strlen(rule)); 71 saved = errno; 72 close(fd); 73 errno = saved; 74 return n < 0 ? -1 : 0; 75 } 76 77 static inline void unregister(const char *name) 78 { 79 char path[PATH_MAX]; 80 int fd; 81 82 snprintf(path, sizeof(path), BINFMT_DIR "/%s", name); 83 fd = open(path, O_WRONLY); 84 if (fd >= 0) { 85 if (write(fd, "-1", 2) < 0) 86 ; /* best effort */ 87 close(fd); 88 } 89 } 90 91 /* Mount binfmt_misc unless it already is, and report whether it is usable. */ 92 static inline bool binfmt_misc_available(void) 93 { 94 if (access(BINFMT_REG, F_OK) < 0) 95 mount("binfmt_misc", BINFMT_DIR, "binfmt_misc", 0, NULL); 96 return access(BINFMT_REG, F_OK) == 0; 97 } 98 99 /* Absolute path of @name in the directory this test was built into. */ 100 static inline int artifact_path(char *out, size_t sz, const char *name) 101 { 102 char exe[PATH_MAX]; 103 ssize_t n; 104 105 n = readlink("/proc/self/exe", exe, sizeof(exe) - 1); 106 if (n < 0) 107 return -1; 108 exe[n] = '\0'; 109 if ((size_t)snprintf(out, sz, "%s/%s", dirname(exe), name) >= sz) 110 return -1; 111 return 0; 112 } 113 114 /* Probe kernel support for a registration flag with a throwaway entry. */ 115 static inline int binfmt_flag_supported(char flag) 116 { 117 char rule[64]; 118 119 snprintf(rule, sizeof(rule), ":bm_flag_probe:E::bmprobe::/bin/true:%c", 120 flag); 121 if (write_reg(rule)) 122 return -1; 123 unregister("bm_flag_probe"); 124 return 0; 125 } 126 127 /* 128 * Run @path with the canonical payload argv and return its exit status, or 129 * RUN_ENOEXEC when the exec itself was refused as unhandled. 130 */ 131 static inline int run_payload(const char *path) 132 { 133 int status; 134 pid_t pid; 135 136 pid = fork(); 137 if (pid == 0) { 138 execl(path, PAYLOAD_ARGV0, PAYLOAD_ARG1, PAYLOAD_ARG2, 139 (char *)NULL); 140 _exit(errno == ENOEXEC ? RUN_ENOEXEC : 126); 141 } 142 if (pid < 0 || waitpid(pid, &status, 0) != pid || !WIFEXITED(status)) 143 return -1; 144 return WEXITSTATUS(status); 145 } 146 147 /* Does the exe link name @path? */ 148 static inline bool exe_is(const char *path) 149 { 150 char exe[PATH_MAX], real[PATH_MAX]; 151 ssize_t n; 152 153 n = readlink("/proc/self/exe", exe, sizeof(exe) - 1); 154 if (n <= 0 || !realpath(path, real)) 155 return false; 156 exe[n] = '\0'; 157 return !strcmp(exe, real); 158 } 159 160 /* Is comm @name truncated to what a comm can hold? */ 161 static inline bool comm_is(const char *name) 162 { 163 char comm[TASK_COMM_LEN + 2], expect[TASK_COMM_LEN]; 164 ssize_t n; 165 int fd; 166 167 fd = open("/proc/self/comm", O_RDONLY); 168 if (fd < 0) 169 return false; 170 n = read(fd, comm, sizeof(comm) - 1); 171 close(fd); 172 if (n <= 0) 173 return false; 174 if (comm[n - 1] == '\n') 175 n--; 176 comm[n] = '\0'; 177 snprintf(expect, sizeof(expect), "%s", name); 178 return !strcmp(comm, expect); 179 } 180 181 /* Opening @path for writing has to fail with ETXTBSY. */ 182 static inline bool write_denied(const char *path) 183 { 184 int fd = open(path, O_WRONLY); 185 186 if (fd >= 0) { 187 close(fd); 188 return false; 189 } 190 return errno == ETXTBSY; 191 } 192 193 #endif /* __SELFTESTS_EXEC_BINFMT_MISC_COMMON_H */ 194