| /* SPDX-License-Identifier: GPL-2.0 */ | |
| /* Helpers shared by the binfmt_misc selftests. */ | |
| #ifndef __SELFTESTS_EXEC_BINFMT_MISC_COMMON_H | |
| #define __SELFTESTS_EXEC_BINFMT_MISC_COMMON_H | |
| #include <elf.h> | |
| #include <errno.h> | |
| #include <fcntl.h> | |
| #include <libgen.h> | |
| #include <limits.h> | |
| #include <link.h> | |
| #include <stdbool.h> | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <sys/mount.h> | |
| #include <sys/types.h> | |
| #include <sys/wait.h> | |
| #include <unistd.h> | |
| #define BINFMT_DIR "/proc/sys/fs/binfmt_misc" | |
| #define BINFMT_REG BINFMT_DIR "/register" | |
| /* comm holds 15 usable chars; a read of /proc/self/comm appends a newline. */ | |
| #define TASK_COMM_LEN 16 | |
| /* The canonical payload argv: run_payload() passes it, the payloads assert it. */ | |
| #define PAYLOAD_ARGV0 "payload-argv0" | |
| #define PAYLOAD_ARG1 "argone" | |
| #define PAYLOAD_ARG2 "argtwo" | |
| /* Marker the loader tests poke into the payload's e_ident padding. */ | |
| #define LOADER_MARKER "LDRTST" | |
| /* Exit status run_payload() reports when the exec was refused as unhandled. */ | |
| #define RUN_ENOEXEC 42 | |
| static inline int copy_file(const char *src, const char *dst) | |
| { | |
| char buf[4096]; | |
| int in, out; | |
| ssize_t n; | |
| in = open(src, O_RDONLY); | |
| if (in < 0) | |
| return -1; | |
| /* The tests share /tmp, so never write through a name they don't own. */ | |
| unlink(dst); | |
| out = open(dst, O_WRONLY | O_CREAT | O_EXCL, 0755); | |
| if (out < 0) { | |
| close(in); | |
| return -1; | |
| } | |
| while ((n = read(in, buf, sizeof(buf))) > 0) { | |
| if (write(out, buf, n) != n) { | |
| close(in); | |
| close(out); | |
| return -1; | |
| } | |
| } | |
| close(in); | |
| close(out); | |
| return n < 0 ? -1 : 0; | |
| } | |
| /* Write @rule to the register file, preserving the write's errno. */ | |
| static inline int write_reg(const char *rule) | |
| { | |
| int fd, saved; | |
| ssize_t n; | |
| fd = open(BINFMT_REG, O_WRONLY); | |
| if (fd < 0) | |
| return -1; | |
| n = write(fd, rule, strlen(rule)); | |
| saved = errno; | |
| close(fd); | |
| errno = saved; | |
| return n < 0 ? -1 : 0; | |
| } | |
| static inline void unregister(const char *name) | |
| { | |
| char path[PATH_MAX]; | |
| int fd; | |
| snprintf(path, sizeof(path), BINFMT_DIR "/%s", name); | |
| fd = open(path, O_WRONLY); | |
| if (fd >= 0) { | |
| if (write(fd, "-1", 2) < 0) | |
| ; /* best effort */ | |
| close(fd); | |
| } | |
| } | |
| /* Write @line to @entry's file, reporting the errno it was refused with. */ | |
| static inline int entry_command(const char *entry, const char *line) | |
| { | |
| char path[PATH_MAX]; | |
| int fd, retval = 0; | |
| size_t len = strlen(line); | |
| snprintf(path, sizeof(path), BINFMT_DIR "/%s", entry); | |
| fd = open(path, O_WRONLY | O_CLOEXEC); | |
| if (fd < 0) | |
| return -errno; | |
| if (write(fd, line, len) != (ssize_t)len) | |
| retval = -errno; | |
| close(fd); | |
| return retval; | |
| } | |
| /* Does @entry's file report @line? */ | |
| static inline bool entry_shows(const char *entry, const char *line) | |
| { | |
| char path[PATH_MAX], buf[PATH_MAX]; | |
| bool found = false; | |
| FILE *fp; | |
| snprintf(path, sizeof(path), BINFMT_DIR "/%s", entry); | |
| fp = fopen(path, "r"); | |
| if (!fp) | |
| return false; | |
| while (fgets(buf, sizeof(buf), fp)) { | |
| buf[strcspn(buf, "\n")] = '\0'; | |
| if (!strcmp(buf, line)) { | |
| found = true; | |
| break; | |
| } | |
| } | |
| fclose(fp); | |
| return found; | |
| } | |
| /* Mount binfmt_misc unless it already is, and report whether it is usable. */ | |
| static inline bool binfmt_misc_available(void) | |
| { | |
| if (access(BINFMT_REG, F_OK) < 0) | |
| mount("binfmt_misc", BINFMT_DIR, "binfmt_misc", 0, NULL); | |
| return access(BINFMT_REG, F_OK) == 0; | |
| } | |
| /* Absolute path of @name in the directory this test was built into. */ | |
| static inline int artifact_path(char *out, size_t sz, const char *name) | |
| { | |
| char exe[PATH_MAX]; | |
| ssize_t n; | |
| n = readlink("/proc/self/exe", exe, sizeof(exe) - 1); | |
| if (n < 0) | |
| return -1; | |
| exe[n] = '\0'; | |
| if ((size_t)snprintf(out, sz, "%s/%s", dirname(exe), name) >= sz) | |
| return -1; | |
| return 0; | |
| } | |
| /* Probe kernel support for a registration flag with a throwaway entry. */ | |
| static inline bool binfmt_flag_supported(char flag) | |
| { | |
| char rule[64]; | |
| snprintf(rule, sizeof(rule), ":bm_flag_probe:E::bmprobe::/bin/true:%c", | |
| flag); | |
| if (write_reg(rule)) | |
| return false; | |
| unregister("bm_flag_probe"); | |
| return true; | |
| } | |
| /* | |
| * Run @path with the canonical payload argv and return its exit status, or | |
| * RUN_ENOEXEC when the exec itself was refused as unhandled. | |
| */ | |
| static inline int run_payload(const char *path) | |
| { | |
| int status; | |
| pid_t pid; | |
| pid = fork(); | |
| if (pid == 0) { | |
| execl(path, PAYLOAD_ARGV0, PAYLOAD_ARG1, PAYLOAD_ARG2, | |
| (char *)NULL); | |
| _exit(errno == ENOEXEC ? RUN_ENOEXEC : 126); | |
| } | |
| if (pid < 0 || waitpid(pid, &status, 0) != pid || !WIFEXITED(status)) | |
| return -1; | |
| return WEXITSTATUS(status); | |
| } | |
| /* Does the exe link name @path? */ | |
| static inline bool exe_is(const char *path) | |
| { | |
| char exe[PATH_MAX], real[PATH_MAX]; | |
| ssize_t n; | |
| n = readlink("/proc/self/exe", exe, sizeof(exe) - 1); | |
| if (n <= 0 || !realpath(path, real)) | |
| return false; | |
| exe[n] = '\0'; | |
| return !strcmp(exe, real); | |
| } | |
| /* Is comm @name truncated to what a comm can hold? */ | |
| static inline bool comm_is(const char *name) | |
| { | |
| char comm[TASK_COMM_LEN + 2], expect[TASK_COMM_LEN]; | |
| ssize_t n; | |
| int fd; | |
| fd = open("/proc/self/comm", O_RDONLY); | |
| if (fd < 0) | |
| return false; | |
| n = read(fd, comm, sizeof(comm) - 1); | |
| close(fd); | |
| if (n <= 0) | |
| return false; | |
| if (comm[n - 1] == '\n') | |
| n--; | |
| comm[n] = '\0'; | |
| snprintf(expect, sizeof(expect), "%s", name); | |
| return !strcmp(comm, expect); | |
| } | |
| /* Opening @path for writing has to fail with ETXTBSY. */ | |
| static inline bool write_denied(const char *path) | |
| { | |
| int fd = open(path, O_WRONLY); | |
| if (fd >= 0) { | |
| close(fd); | |
| return false; | |
| } | |
| return errno == ETXTBSY; | |
| } | |
| static inline int patch_file(const char *path, off_t off, const void *data, size_t len) | |
| { | |
| ssize_t n; | |
| int fd; | |
| fd = open(path, O_WRONLY); | |
| if (fd < 0) | |
| return -1; | |
| n = pwrite(fd, data, len, off); | |
| close(fd); | |
| return n == (ssize_t)len ? 0 : -1; | |
| } | |
| /* start_code and end_code are the 26th and 27th fields of /proc/pid/stat. */ | |
| static inline int stat_codes(pid_t pid, unsigned long *start_code, | |
| unsigned long *end_code) | |
| { | |
| char buf[4096], path[64], *p; | |
| ssize_t n; | |
| int fd, i; | |
| snprintf(path, sizeof(path), "/proc/%d/stat", pid); | |
| fd = open(path, O_RDONLY); | |
| if (fd < 0) | |
| return -1; | |
| n = read(fd, buf, sizeof(buf) - 1); | |
| close(fd); | |
| if (n <= 0) | |
| return -1; | |
| buf[n] = '\0'; | |
| /* Skip "pid (comm)", then start_code is the 24th field after it. */ | |
| p = strrchr(buf, ')'); | |
| if (!p) | |
| return -1; | |
| p++; | |
| for (i = 0; i < 23; i++) { | |
| p = strchr(p + 1, ' '); | |
| if (!p) | |
| return -1; | |
| } | |
| if (sscanf(p, " %lu %lu", start_code, end_code) != 2) | |
| return -1; | |
| return 0; | |
| } | |
| /* Find the system loader through our own PT_INTERP. */ | |
| static inline int find_loader(char *out, size_t sz) | |
| { | |
| ElfW(Ehdr) eh; | |
| ElfW(Phdr) ph; | |
| int fd, i, ret = -1; | |
| fd = open("/proc/self/exe", O_RDONLY); | |
| if (fd < 0) | |
| return -1; | |
| if (pread(fd, &eh, sizeof(eh), 0) != sizeof(eh)) | |
| goto out; | |
| for (i = 0; i < eh.e_phnum; i++) { | |
| if (pread(fd, &ph, sizeof(ph), | |
| eh.e_phoff + i * eh.e_phentsize) != sizeof(ph)) | |
| goto out; | |
| if (ph.p_type != PT_INTERP) | |
| continue; | |
| if (!ph.p_filesz || ph.p_filesz > sz) | |
| goto out; | |
| if (pread(fd, out, ph.p_filesz, ph.p_offset) != | |
| (ssize_t)ph.p_filesz) | |
| goto out; | |
| out[ph.p_filesz - 1] = '\0'; | |
| ret = 0; | |
| break; | |
| } | |
| out: | |
| close(fd); | |
| return ret; | |
| } | |
| #endif /* __SELFTESTS_EXEC_BINFMT_MISC_COMMON_H */ |