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 <elf.h> 7 #include <errno.h> 8 #include <fcntl.h> 9 #include <libgen.h> 10 #include <limits.h> 11 #include <link.h> 12 #include <stdbool.h> 13 #include <stdio.h> 14 #include <stdlib.h> 15 #include <string.h> 16 #include <sys/mount.h> 17 #include <sys/types.h> 18 #include <sys/wait.h> 19 #include <unistd.h> 20 21 #define BINFMT_DIR "/proc/sys/fs/binfmt_misc" 22 #define BINFMT_REG BINFMT_DIR "/register" 23 24 /* comm holds 15 usable chars; a read of /proc/self/comm appends a newline. */ 25 #define TASK_COMM_LEN 16 26 27 /* The canonical payload argv: run_payload() passes it, the payloads assert it. */ 28 #define PAYLOAD_ARGV0 "payload-argv0" 29 #define PAYLOAD_ARG1 "argone" 30 #define PAYLOAD_ARG2 "argtwo" 31 32 /* Marker the loader tests poke into the payload's e_ident padding. */ 33 #define LOADER_MARKER "LDRTST" 34 35 /* Exit status run_payload() reports when the exec was refused as unhandled. */ 36 #define RUN_ENOEXEC 42 37 38 static inline int copy_file(const char *src, const char *dst) 39 { 40 char buf[4096]; 41 int in, out; 42 ssize_t n; 43 44 in = open(src, O_RDONLY); 45 if (in < 0) 46 return -1; 47 /* The tests share /tmp, so never write through a name they don't own. */ 48 unlink(dst); 49 out = open(dst, O_WRONLY | O_CREAT | O_EXCL, 0755); 50 if (out < 0) { 51 close(in); 52 return -1; 53 } 54 while ((n = read(in, buf, sizeof(buf))) > 0) { 55 if (write(out, buf, n) != n) { 56 close(in); 57 close(out); 58 return -1; 59 } 60 } 61 close(in); 62 close(out); 63 return n < 0 ? -1 : 0; 64 } 65 66 /* Write @rule to the register file, preserving the write's errno. */ 67 static inline int write_reg(const char *rule) 68 { 69 int fd, saved; 70 ssize_t n; 71 72 fd = open(BINFMT_REG, O_WRONLY); 73 if (fd < 0) 74 return -1; 75 n = write(fd, rule, strlen(rule)); 76 saved = errno; 77 close(fd); 78 errno = saved; 79 return n < 0 ? -1 : 0; 80 } 81 82 static inline void unregister(const char *name) 83 { 84 char path[PATH_MAX]; 85 int fd; 86 87 snprintf(path, sizeof(path), BINFMT_DIR "/%s", name); 88 fd = open(path, O_WRONLY); 89 if (fd >= 0) { 90 if (write(fd, "-1", 2) < 0) 91 ; /* best effort */ 92 close(fd); 93 } 94 } 95 96 /* Write @line to @entry's file, reporting the errno it was refused with. */ 97 static inline int entry_command(const char *entry, const char *line) 98 { 99 char path[PATH_MAX]; 100 int fd, retval = 0; 101 size_t len = strlen(line); 102 103 snprintf(path, sizeof(path), BINFMT_DIR "/%s", entry); 104 fd = open(path, O_WRONLY | O_CLOEXEC); 105 if (fd < 0) 106 return -errno; 107 if (write(fd, line, len) != (ssize_t)len) 108 retval = -errno; 109 close(fd); 110 return retval; 111 } 112 113 /* Does @entry's file report @line? */ 114 static inline bool entry_shows(const char *entry, const char *line) 115 { 116 char path[PATH_MAX], buf[PATH_MAX]; 117 bool found = false; 118 FILE *fp; 119 120 snprintf(path, sizeof(path), BINFMT_DIR "/%s", entry); 121 fp = fopen(path, "r"); 122 if (!fp) 123 return false; 124 while (fgets(buf, sizeof(buf), fp)) { 125 buf[strcspn(buf, "\n")] = '\0'; 126 if (!strcmp(buf, line)) { 127 found = true; 128 break; 129 } 130 } 131 fclose(fp); 132 return found; 133 } 134 135 /* Mount binfmt_misc unless it already is, and report whether it is usable. */ 136 static inline bool binfmt_misc_available(void) 137 { 138 if (access(BINFMT_REG, F_OK) < 0) 139 mount("binfmt_misc", BINFMT_DIR, "binfmt_misc", 0, NULL); 140 return access(BINFMT_REG, F_OK) == 0; 141 } 142 143 /* Absolute path of @name in the directory this test was built into. */ 144 static inline int artifact_path(char *out, size_t sz, const char *name) 145 { 146 char exe[PATH_MAX]; 147 ssize_t n; 148 149 n = readlink("/proc/self/exe", exe, sizeof(exe) - 1); 150 if (n < 0) 151 return -1; 152 exe[n] = '\0'; 153 if ((size_t)snprintf(out, sz, "%s/%s", dirname(exe), name) >= sz) 154 return -1; 155 return 0; 156 } 157 158 /* Probe kernel support for a registration flag with a throwaway entry. */ 159 static inline bool binfmt_flag_supported(char flag) 160 { 161 char rule[64]; 162 163 snprintf(rule, sizeof(rule), ":bm_flag_probe:E::bmprobe::/bin/true:%c", 164 flag); 165 if (write_reg(rule)) 166 return false; 167 unregister("bm_flag_probe"); 168 return true; 169 } 170 171 /* 172 * Run @path with the canonical payload argv and return its exit status, or 173 * RUN_ENOEXEC when the exec itself was refused as unhandled. 174 */ 175 static inline int run_payload(const char *path) 176 { 177 int status; 178 pid_t pid; 179 180 pid = fork(); 181 if (pid == 0) { 182 execl(path, PAYLOAD_ARGV0, PAYLOAD_ARG1, PAYLOAD_ARG2, 183 (char *)NULL); 184 _exit(errno == ENOEXEC ? RUN_ENOEXEC : 126); 185 } 186 if (pid < 0 || waitpid(pid, &status, 0) != pid || !WIFEXITED(status)) 187 return -1; 188 return WEXITSTATUS(status); 189 } 190 191 /* Does the exe link name @path? */ 192 static inline bool exe_is(const char *path) 193 { 194 char exe[PATH_MAX], real[PATH_MAX]; 195 ssize_t n; 196 197 n = readlink("/proc/self/exe", exe, sizeof(exe) - 1); 198 if (n <= 0 || !realpath(path, real)) 199 return false; 200 exe[n] = '\0'; 201 return !strcmp(exe, real); 202 } 203 204 /* Is comm @name truncated to what a comm can hold? */ 205 static inline bool comm_is(const char *name) 206 { 207 char comm[TASK_COMM_LEN + 2], expect[TASK_COMM_LEN]; 208 ssize_t n; 209 int fd; 210 211 fd = open("/proc/self/comm", O_RDONLY); 212 if (fd < 0) 213 return false; 214 n = read(fd, comm, sizeof(comm) - 1); 215 close(fd); 216 if (n <= 0) 217 return false; 218 if (comm[n - 1] == '\n') 219 n--; 220 comm[n] = '\0'; 221 snprintf(expect, sizeof(expect), "%s", name); 222 return !strcmp(comm, expect); 223 } 224 225 /* Opening @path for writing has to fail with ETXTBSY. */ 226 static inline bool write_denied(const char *path) 227 { 228 int fd = open(path, O_WRONLY); 229 230 if (fd >= 0) { 231 close(fd); 232 return false; 233 } 234 return errno == ETXTBSY; 235 } 236 237 static inline int patch_file(const char *path, off_t off, const void *data, size_t len) 238 { 239 ssize_t n; 240 int fd; 241 242 fd = open(path, O_WRONLY); 243 if (fd < 0) 244 return -1; 245 n = pwrite(fd, data, len, off); 246 close(fd); 247 return n == (ssize_t)len ? 0 : -1; 248 } 249 250 /* start_code and end_code are the 26th and 27th fields of /proc/pid/stat. */ 251 static inline int stat_codes(pid_t pid, unsigned long *start_code, 252 unsigned long *end_code) 253 { 254 char buf[4096], path[64], *p; 255 ssize_t n; 256 int fd, i; 257 258 snprintf(path, sizeof(path), "/proc/%d/stat", pid); 259 fd = open(path, O_RDONLY); 260 if (fd < 0) 261 return -1; 262 n = read(fd, buf, sizeof(buf) - 1); 263 close(fd); 264 if (n <= 0) 265 return -1; 266 buf[n] = '\0'; 267 268 /* Skip "pid (comm)", then start_code is the 24th field after it. */ 269 p = strrchr(buf, ')'); 270 if (!p) 271 return -1; 272 p++; 273 for (i = 0; i < 23; i++) { 274 p = strchr(p + 1, ' '); 275 if (!p) 276 return -1; 277 } 278 if (sscanf(p, " %lu %lu", start_code, end_code) != 2) 279 return -1; 280 return 0; 281 } 282 283 /* Find the system loader through our own PT_INTERP. */ 284 static inline int find_loader(char *out, size_t sz) 285 { 286 ElfW(Ehdr) eh; 287 ElfW(Phdr) ph; 288 int fd, i, ret = -1; 289 290 fd = open("/proc/self/exe", O_RDONLY); 291 if (fd < 0) 292 return -1; 293 if (pread(fd, &eh, sizeof(eh), 0) != sizeof(eh)) 294 goto out; 295 for (i = 0; i < eh.e_phnum; i++) { 296 if (pread(fd, &ph, sizeof(ph), 297 eh.e_phoff + i * eh.e_phentsize) != sizeof(ph)) 298 goto out; 299 if (ph.p_type != PT_INTERP) 300 continue; 301 if (!ph.p_filesz || ph.p_filesz > sz) 302 goto out; 303 if (pread(fd, out, ph.p_filesz, ph.p_offset) != 304 (ssize_t)ph.p_filesz) 305 goto out; 306 out[ph.p_filesz - 1] = '\0'; 307 ret = 0; 308 break; 309 } 310 out: 311 close(fd); 312 return ret; 313 } 314 315 #endif /* __SELFTESTS_EXEC_BINFMT_MISC_COMMON_H */ 316