xref: /linux/tools/testing/selftests/exec/binfmt_misc_common.h (revision 7baee96f8356fbd01db1dc2641c13104413f6434)
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