1 /*
2 * This file and its contents are supplied under the terms of the
3 * Common Development and Distribution License ("CDDL"), version 1.0.
4 * You may only use this file in accordance with the terms of version
5 * 1.0 of the CDDL.
6 *
7 * A full copy of the text of the CDDL should have accompanied this
8 * source. A copy of the CDDL is also available via the Internet at
9 * http://www.illumos.org/license/CDDL.
10 */
11
12 /*
13 * Copyright 2015 Garrett D'Amore <garrett@damore.org>
14 * Copyright 2024 Oxide Computer Company
15 */
16
17 /*
18 * This program tests that fexecve works properly.
19 */
20
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <fcntl.h>
25 #include <err.h>
26 #include <errno.h>
27 #include <unistd.h>
28 #include <note.h>
29 #include <sys/execx.h>
30 #include <sys/utsname.h>
31 #include <sys/wait.h>
32 #include "test_common.h"
33
34 int extra_debug = 0;
35
36 struct utsname un;
37
38 static void
forkit(char * msg,const char * expect,void (* postfn)(void))39 forkit(char *msg, const char *expect, void (*postfn)(void))
40 {
41 int fd;
42 FILE *f;
43 pid_t pid;
44 char *ptr = NULL, *p;
45 size_t cap = 0;
46 int wstat;
47 int rv;
48 test_t t;
49 char fname[32];
50
51 (void) strcpy(fname, "/tmp/testXXXXXX");
52 t = test_start(msg);
53
54 fd = mkstemp(fname);
55 if (fd < 0) {
56 test_failed(t, "mkstemp failed: %s", strerror(errno));
57 return;
58 }
59
60 /* don't leave it in the filesystem */
61 (void) unlink(fname);
62
63 pid = fork();
64 switch (pid) {
65 case -1:
66 test_failed(t, "fork failed: %s", strerror(errno));
67 return;
68 case 0:
69 if (dup2(fd, 1) < 0) {
70 test_failed(t, "dup2 failed: %s", strerror(errno));
71 exit(9);
72 }
73 postfn();
74 exit(0);
75 default:
76 break;
77 }
78
79 /* parent */
80 f = fdopen(fd, "r");
81 if (f == NULL) {
82 (void) close(fd);
83 test_failed(t, "fdopen failed: %s", strerror(errno));
84 (void) wait(NULL);
85 return;
86 }
87 if (waitpid(pid, &wstat, 0) < 0) {
88 test_failed(t, "wait failed: %s", strerror(errno));
89 (void) fclose(f);
90 return;
91 }
92 if (!WIFEXITED(wstat) || WEXITSTATUS(wstat) != 0) {
93 test_failed(t, "child failed: %#x", wstat);
94 (void) fclose(f);
95 return;
96 }
97 (void) lseek(fd, 0, SEEK_SET);
98 if ((rv = getline(&ptr, &cap, f)) < 1) {
99 test_failed(t, "child gave no data: %d", rv);
100 (void) fclose(f);
101 return;
102 }
103 (void) fclose(f);
104
105 if ((p = strchr(ptr, '\n')) != NULL)
106 *p = '\0';
107 if (extra_debug)
108 printf("Child output: [%s]\n", ptr);
109 if (strcmp(ptr, expect) != 0) {
110 test_failed(t, "[%s] != [%s]", ptr, expect);
111 return;
112 }
113
114 (void) free(ptr);
115 test_passed(t);
116 }
117
118 static void
case_badf(void)119 case_badf(void)
120 {
121 int fd = -1;
122 int rv;
123 char *args[] = { "uname", NULL };
124 char *env[] = { NULL };
125
126 rv = fexecve(fd, args, env);
127 if (rv != -1) {
128 (void) printf("rv is not -1\n");
129 (void) exit(0);
130 }
131 if (errno != EBADF) {
132 (void) printf("err %d(%s) != EBADF\n", errno, strerror(errno));
133 (void) exit(0);
134 }
135 (void) printf("GOOD\n");
136 (void) exit(0);
137 }
138
139 static void
case_bad_highf(void)140 case_bad_highf(void)
141 {
142 int fd = 55;
143 int rv;
144 char *args[] = { "uname", NULL };
145 char *env[] = { NULL };
146
147 closefrom(3);
148 rv = fexecve(fd, args, env);
149 if (rv != -1) {
150 (void) printf("rv is not -1\n");
151 (void) exit(0);
152 }
153 if (errno != EBADF) {
154 (void) printf("err %d(%s) != EBADF\n", errno, strerror(errno));
155 (void) exit(0);
156 }
157 (void) printf("GOOD\n");
158 (void) exit(0);
159 }
160
161 static void
case_notexec(void)162 case_notexec(void)
163 {
164 int fd;
165 int rv;
166 char *args[] = { "uname", NULL };
167 char *env[] = { NULL };
168
169 fd = open("/usr/bin/uname", O_RDONLY);
170
171 rv = fexecve(fd, args, env);
172 if (rv != -1) {
173 (void) printf("rv is not -1\n");
174 (void) exit(0);
175 }
176 (void) printf("FAILURE\n");
177 (void) exit(0);
178 }
179
180 static void
case_cloexec(void)181 case_cloexec(void)
182 {
183 int fd;
184 int rv;
185 char *args[] = { "ls", "-C", "/proc/self/fd", NULL };
186 char *env[] = { NULL };
187
188 /*
189 * We set things up so that this process has only stdin, stdout and
190 * stderr, then `ls` will open a file descriptor for the directory
191 * being listed. If we have more than that then the descriptor we're
192 * about to open has leaked through to the new process despite the
193 * O_CLOEXEC.
194 */
195 closefrom(3);
196 fd = open("/usr/bin/ls", O_RDONLY | O_EXEC | O_CLOEXEC);
197
198 rv = fexecve(fd, args, env);
199 if (rv != -1) {
200 (void) printf("rv is not -1\n");
201 (void) exit(0);
202 }
203 (void) printf("FAILURE\n");
204 (void) exit(0);
205 }
206
207 static void
case_uname(void)208 case_uname(void)
209 {
210 int fd;
211 char *args[] = { "uname", NULL };
212 char *env[] = { NULL };
213
214 fd = open("/usr/bin/uname", O_EXEC);
215 if (fd < 0) {
216 (void) printf("failed to open /usr/bin/uname: %s",
217 strerror(errno));
218 (void) exit(0);
219 }
220
221 (void) fexecve(fd, args, env);
222 (void) printf("EXEC FAILED: %s\n", strerror(errno));
223 (void) exit(0);
224 }
225
226 static void
case_uname_r(void)227 case_uname_r(void)
228 {
229 int fd;
230 char *args[] = { "uname", "-r", NULL };
231 char *env[] = { NULL };
232
233 fd = open("/usr/bin/uname", O_EXEC);
234 if (fd < 0) {
235 (void) printf("failed to open /usr/bin/uname: %s",
236 strerror(errno));
237 (void) exit(0);
238 }
239
240 (void) fexecve(fd, args, env);
241 (void) printf("EXEC FAILED: %s\n", strerror(errno));
242 (void) exit(0);
243 }
244
245 static void
case_execvex_bad_flags(void)246 case_execvex_bad_flags(void)
247 {
248 int fd = -1;
249 int rv;
250 char *args[] = { "uname", NULL };
251 char *env[] = { NULL };
252
253 rv = execvex(fd, args, env, 0x1005);
254 if (rv != -1) {
255 (void) printf("rv is not -1\n");
256 (void) exit(0);
257 }
258 if (errno != EINVAL) {
259 (void) printf("err %d(%s) != EINVAL\n", errno, strerror(errno));
260 (void) exit(0);
261 }
262 (void) printf("GOOD\n");
263 (void) exit(0);
264 }
265
266 static void
test_fexecve_badf(void)267 test_fexecve_badf(void)
268 {
269 forkit("fexecve (bad FD)", "GOOD", case_badf);
270 }
271
272 static void
test_fexecve_bad_highf(void)273 test_fexecve_bad_highf(void)
274 {
275 forkit("fexecve (bad FD)", "GOOD", case_bad_highf);
276 }
277
278 static void
test_fexecve_notexec(void)279 test_fexecve_notexec(void)
280 {
281 forkit("fexecve (not O_EXEC)", un.sysname, case_notexec);
282 }
283
284 static void
test_fexecve_cloexec(void)285 test_fexecve_cloexec(void)
286 {
287 forkit("fexecve (O_CLOEXEC)", "0 1 2 3", case_cloexec);
288 }
289
290 static void
test_fexecve_uname(void)291 test_fexecve_uname(void)
292 {
293 forkit("fexecve (uname)", un.sysname, case_uname);
294 }
295
296 static void
test_fexecve_uname_r(void)297 test_fexecve_uname_r(void)
298 {
299 forkit("fexecve (uname)", un.release, case_uname_r);
300 }
301
302 static void
test_execvex_bad_flags(void)303 test_execvex_bad_flags(void)
304 {
305 forkit("execvex (bad flags)", "GOOD", case_execvex_bad_flags);
306 }
307
308 int
main(int argc,char ** argv)309 main(int argc, char **argv)
310 {
311 int optc;
312
313 (void) uname(&un);
314
315 while ((optc = getopt(argc, argv, "dfD")) != EOF) {
316 switch (optc) {
317 case 'd':
318 test_set_debug();
319 break;
320 case 'f':
321 test_set_force();
322 break;
323 case 'D':
324 test_set_debug();
325 extra_debug++;
326 break;
327 default:
328 (void) fprintf(stderr, "Usage: %s [-dfD]\n", argv[0]);
329 exit(1);
330 }
331 }
332
333 test_fexecve_badf();
334 test_fexecve_bad_highf();
335 test_fexecve_notexec();
336 test_fexecve_cloexec();
337 test_fexecve_uname();
338 test_fexecve_uname_r();
339 test_execvex_bad_flags();
340
341 exit(0);
342 }
343