xref: /illumos-gate/usr/src/test/os-tests/tests/spawn/spawn-syscall.c (revision ac2250cb76bb32944fd2c8a3ba2cd3f79747748d)
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 2026 Oxide Computer Company
14  */
15 
16 /*
17  * Tests for the raw spawn(2) system call interface. These concentrate
18  * mainly on checking that the kernel fully validates the marshalled
19  * spawn_param_t/spawn_args_t structures that libc constructs.
20  * We also test that a spawned child is robust against being killed in
21  * the window between creation and exec.
22  *
23  * Functional coverage of posix_spawn(3C) itself (attributes, file actions,
24  * PATH handling, etc.) lives in libc-tests/tests/posix_spawn.
25  */
26 
27 #include <errno.h>
28 #include <signal.h>
29 #include <spawn.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <unistd.h>
34 #include <sys/syscall.h>
35 #include <sys/spawn_impl.h>
36 #include <sys/types.h>
37 #include <sys/wait.h>
38 
39 static uint_t failures = 0;
40 
41 #define	TFAIL(name, fmt, ...)	do {					\
42 	(void) fprintf(stderr, "TEST FAILED: %s: " fmt "\n",		\
43 	    (name), ##__VA_ARGS__);					\
44 	failures++;							\
45 } while (0)
46 
47 #define	TPASS(name)	(void) printf("TEST PASSED: %s\n", (name))
48 
49 /*
50  * The spawn_args_t and spawn_param_t structures end in a variable-length
51  * data[] region. These tests build small instances on the stack and reserve a
52  * fixed amount of room for that region.
53  */
54 #define	SA_DATA	16
55 #define	SP_DATA	64
56 
57 static pid_t
raw_spawn(const char * path,const void * sp,uint32_t spsize,const void * sa,uint32_t sasize)58 raw_spawn(const char *path, const void *sp, uint32_t spsize, const void *sa,
59     uint32_t sasize)
60 {
61 	return (syscall(SYS_spawn, path, sp, spsize, sa, sasize));
62 }
63 
64 /*
65  * Build a minimal valid spawn_args_t in the provided buffer:
66  * argv = { "x" }, empty environment.
67  */
68 static uint32_t
valid_args(spawn_args_t * sa)69 valid_args(spawn_args_t *sa)
70 {
71 	uint32_t sasize = sizeof (*sa) + 2;
72 
73 	(void) memset(sa, 0, sasize);
74 	sa->sa_size = sasize;
75 	sa->sa_datalen = 2;
76 	sa->sa_arg_cnt = 1;
77 	sa->sa_env_off = 2;
78 	sa->sa_data[0] = 'x';
79 	sa->sa_data[1] = '\0';
80 
81 	return (sasize);
82 }
83 
84 static const char *
errname(int err)85 errname(int err)
86 {
87 	const char *name = strerrorname_np(err);
88 
89 	return (name != NULL ? name : "?");
90 }
91 
92 static void
expect_err(const char * name,int wanted,const char * path,const void * sp,uint32_t spsize,const void * sa,uint32_t sasize)93 expect_err(const char *name, int wanted, const char *path, const void *sp,
94     uint32_t spsize, const void *sa, uint32_t sasize)
95 {
96 	pid_t pid = raw_spawn(path, sp, spsize, sa, sasize);
97 
98 	if (pid != -1) {
99 		TFAIL(name, "spawn unexpectedly succeeded (pid %d)",
100 		    (int)pid);
101 		(void) waitpid(pid, NULL, 0);
102 		return;
103 	}
104 	if (errno != wanted) {
105 		TFAIL(name, "got %s (%d), wanted %s (%d)", errname(errno),
106 		    errno, errname(wanted), wanted);
107 		return;
108 	}
109 	TPASS(name);
110 }
111 
112 /*
113  * The positive case: a well-formed minimal request - a valid argument vector,
114  * no spawn parameters and no file actions - succeeds, and the child
115  * (/usr/bin/true) exits 0.
116  */
117 static void
t_valid(void)118 t_valid(void)
119 {
120 	union {
121 		spawn_args_t sa;
122 		char pad[sizeof (spawn_args_t) + SA_DATA];
123 	} au;
124 	uint32_t sasize = valid_args(&au.sa);
125 	int status;
126 	pid_t pid;
127 
128 	pid = raw_spawn("/usr/bin/true", NULL, 0, &au.sa, sasize);
129 	if (pid == -1) {
130 		TFAIL("valid", "spawn failed: %s", strerror(errno));
131 		return;
132 	}
133 	if (waitpid(pid, &status, 0) != pid) {
134 		TFAIL("valid", "waitpid: %s", strerror(errno));
135 		return;
136 	}
137 	if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
138 		TFAIL("valid", "status %#x", status);
139 		return;
140 	}
141 	TPASS("valid, well-formed, minimal spawn request");
142 }
143 
144 static void
t_args_fuzz(void)145 t_args_fuzz(void)
146 {
147 	union {
148 		spawn_args_t sa;
149 		char pad[sizeof (spawn_args_t) + SA_DATA];
150 	} au;
151 	spawn_args_t *sa = &au.sa;
152 	uint32_t sasize;
153 
154 	expect_err("null-path", EINVAL, NULL, NULL, 0, &au.sa,
155 	    valid_args(&au.sa));
156 	expect_err("null-args", EINVAL, "/usr/bin/true", NULL, 0, NULL, 0);
157 
158 	sasize = valid_args(sa);
159 	sa->sa_size = sasize + 8;
160 	expect_err("args-size-mismatch", EINVAL, "/usr/bin/true", NULL, 0,
161 	    sa, sasize);
162 
163 	sasize = valid_args(sa);
164 	sa->sa_datalen = 1;
165 	expect_err("args-datalen-mismatch", EINVAL, "/usr/bin/true", NULL, 0,
166 	    sa, sasize);
167 
168 	sasize = valid_args(sa);
169 	sa->sa_arg_cnt = 100;
170 	expect_err("args-cnt-overrun", EINVAL, "/usr/bin/true", NULL, 0,
171 	    sa, sasize);
172 
173 	sasize = valid_args(sa);
174 	sa->sa_data[1] = 'y';	/* string no longer NUL-terminated */
175 	expect_err("args-unterminated", EINVAL, "/usr/bin/true", NULL, 0,
176 	    sa, sasize);
177 
178 	sasize = valid_args(sa);
179 	sa->sa_env_off = 100;
180 	expect_err("args-env-off", EINVAL, "/usr/bin/true", NULL, 0,
181 	    sa, sasize);
182 
183 	sasize = valid_args(sa);
184 	sa->sa_env_cnt = 7;
185 	expect_err("args-env-cnt", EINVAL, "/usr/bin/true", NULL, 0,
186 	    sa, sasize);
187 
188 	sasize = valid_args(sa);
189 	expect_err("args-e2big", E2BIG, "/usr/bin/true", NULL, 0,
190 	    sa, 0x400000);
191 
192 	/* Truncated buffer: shorter than the header itself. */
193 	sasize = valid_args(sa);
194 	expect_err("args-short", EINVAL, "/usr/bin/true", NULL, 0,
195 	    sa, sizeof (*sa) - 4);
196 }
197 
198 static void
t_param_fuzz(void)199 t_param_fuzz(void)
200 {
201 	union {
202 		spawn_args_t sa;
203 		char pad[sizeof (spawn_args_t) + SA_DATA];
204 	} au;
205 	union {
206 		spawn_param_t sp;
207 		char pad[sizeof (spawn_param_t) + SP_DATA];
208 	} pu;
209 	spawn_param_t *sp = &pu.sp;
210 	uint32_t sasize = valid_args(&au.sa);
211 	uint32_t spsize = sizeof (*sp) + SP_DATA;
212 	kfile_attr_t *kfa;
213 
214 	/* Size field disagreeing with the system call argument. */
215 	(void) memset(&pu, 0, sizeof (pu));
216 	sp->sp_size = spsize + 4;
217 	sp->sp_datalen = SP_DATA;
218 	expect_err("param-size-mismatch", EINVAL, "/usr/bin/true",
219 	    sp, spsize, &au.sa, sasize);
220 
221 	/* Attribute region out of bounds. */
222 	(void) memset(&pu, 0, sizeof (pu));
223 	sp->sp_size = spsize;
224 	sp->sp_datalen = SP_DATA;
225 	sp->sp_attr_off = 60;
226 	sp->sp_attr_len = sizeof (spawn_attr_t);
227 	expect_err("param-attr-off", EINVAL, "/usr/bin/true",
228 	    sp, spsize, &au.sa, sasize);
229 
230 	/* Attribute length that is not sizeof (spawn_attr_t). */
231 	(void) memset(&pu, 0, sizeof (pu));
232 	sp->sp_size = spsize;
233 	sp->sp_datalen = SP_DATA;
234 	sp->sp_attr_len = 8;
235 	expect_err("param-attr-len", EINVAL, "/usr/bin/true",
236 	    sp, spsize, &au.sa, sasize);
237 
238 	/* Undefined attribute flags. */
239 	(void) memset(&pu, 0, sizeof (pu));
240 	sp->sp_size = spsize;
241 	sp->sp_datalen = SP_DATA;
242 	sp->sp_attr_off = 0;
243 	sp->sp_attr_len = sizeof (spawn_attr_t);
244 	((spawn_attr_t *)&sp->sp_data[0])->sa_psflags = ~0;
245 	expect_err("param-attr-flags", EINVAL, "/usr/bin/true",
246 	    sp, spsize, &au.sa, sasize);
247 
248 	/* File action record with a zero length. */
249 	(void) memset(&pu, 0, sizeof (pu));
250 	sp->sp_size = spsize;
251 	sp->sp_datalen = SP_DATA;
252 	sp->sp_fattr_cnt = 2;
253 	expect_err("param-fattr-len", EINVAL, "/usr/bin/true",
254 	    sp, spsize, &au.sa, sasize);
255 
256 	/* File action record with a bad type. */
257 	(void) memset(&pu, 0, sizeof (pu));
258 	sp->sp_size = spsize;
259 	sp->sp_datalen = SP_DATA;
260 	sp->sp_fattr_cnt = 1;
261 	kfa = (kfile_attr_t *)&sp->sp_data[0];
262 	kfa->kfa_len = sizeof (*kfa);
263 	kfa->kfa_type = 666;
264 	expect_err("param-fattr-type", EINVAL, "/usr/bin/true",
265 	    sp, spsize, &au.sa, sasize);
266 
267 	/* FA_CHDIR with an unterminated path. */
268 	(void) memset(&pu, 0, sizeof (pu));
269 	sp->sp_size = spsize;
270 	sp->sp_datalen = SP_DATA;
271 	sp->sp_fattr_cnt = 1;
272 	kfa = (kfile_attr_t *)&sp->sp_data[0];
273 	kfa->kfa_type = FA_CHDIR;
274 	kfa->kfa_pathsize = 4;
275 	kfa->kfa_len = sizeof (*kfa) + 4;
276 	(void) memcpy(kfa->kfa_path, "/tmp", 4);	/* no NUL */
277 	expect_err("param-chdir-nul", EINVAL, "/usr/bin/true",
278 	    sp, spsize, &au.sa, sasize);
279 
280 	/* Unterminated shell and search path strings. */
281 	(void) memset(&pu, 0, sizeof (pu));
282 	sp->sp_size = spsize;
283 	sp->sp_datalen = SP_DATA;
284 	sp->sp_shell_off = 0;
285 	sp->sp_shell_len = 4;
286 	(void) memcpy(sp->sp_data, "/bin", 4);
287 	expect_err("param-shell-nul", EINVAL, "/usr/bin/true",
288 	    sp, spsize, &au.sa, sasize);
289 
290 	(void) memset(&pu, 0, sizeof (pu));
291 	sp->sp_size = spsize;
292 	sp->sp_datalen = SP_DATA;
293 	sp->sp_path_off = 62;
294 	sp->sp_path_len = 8;
295 	expect_err("param-path-off", EINVAL, "/usr/bin/true",
296 	    sp, spsize, &au.sa, sasize);
297 
298 	expect_err("param-e2big", E2BIG, "/usr/bin/true",
299 	    sp, 0x400000, &au.sa, sasize);
300 
301 	expect_err("param-short", EINVAL, "/usr/bin/true",
302 	    sp, sizeof (*sp) - 4, &au.sa, sasize);
303 }
304 
305 /*
306  * Build a spawn_param_t carrying an attribute region with
307  * POSIX_SPAWN_SETSCHEDPARAM set and a scheduling region with the given
308  * operation.
309  */
310 static kspawn_sched_t *
valid_sched(spawn_param_t * sp,uint32_t spsize,int op)311 valid_sched(spawn_param_t *sp, uint32_t spsize, int op)
312 {
313 	spawn_attr_t *spa;
314 	kspawn_sched_t *ks;
315 
316 	(void) memset(sp, 0, spsize);
317 	sp->sp_size = spsize;
318 	sp->sp_datalen = spsize - sizeof (*sp);
319 	sp->sp_attr_off = 0;
320 	sp->sp_attr_len = sizeof (spawn_attr_t);
321 	spa = (spawn_attr_t *)&sp->sp_data[0];
322 	spa->sa_psflags = POSIX_SPAWN_SETSCHEDPARAM;
323 	sp->sp_sched_off = sizeof (spawn_attr_t);
324 	sp->sp_sched_len = sizeof (kspawn_sched_t);
325 	ks = (kspawn_sched_t *)&sp->sp_data[sp->sp_sched_off];
326 	ks->ksched_op = op;
327 
328 	return (ks);
329 }
330 
331 static void
t_sched_fuzz(void)332 t_sched_fuzz(void)
333 {
334 	union {
335 		spawn_args_t sa;
336 		char pad[sizeof (spawn_args_t) + SA_DATA];
337 	} au;
338 	union {
339 		spawn_param_t sp;
340 		char pad[sizeof (spawn_param_t) + 128];
341 	} pu;
342 	spawn_param_t *sp = &pu.sp;
343 	uint32_t sasize = valid_args(&au.sa);
344 	uint32_t spsize = sizeof (*sp) + 128;
345 	kspawn_sched_t *ks;
346 
347 	/* Scheduling flag set but no scheduling region. */
348 	(void) valid_sched(sp, spsize, KSCHED_PARMS);
349 	sp->sp_sched_off = sp->sp_sched_len = 0;
350 	expect_err("sched-missing", EINVAL, "/usr/bin/true",
351 	    sp, spsize, &au.sa, sasize);
352 
353 	/* Scheduling region present without a scheduling flag. */
354 	(void) valid_sched(sp, spsize, KSCHED_PARMS);
355 	((spawn_attr_t *)&sp->sp_data[0])->sa_psflags = 0;
356 	expect_err("sched-no-flags", EINVAL, "/usr/bin/true",
357 	    sp, spsize, &au.sa, sasize);
358 
359 	/* Scheduling region with the wrong length. */
360 	(void) valid_sched(sp, spsize, KSCHED_PARMS);
361 	sp->sp_sched_len = 8;
362 	expect_err("sched-len", EINVAL, "/usr/bin/true",
363 	    sp, spsize, &au.sa, sasize);
364 
365 	/* Scheduling region out of bounds. */
366 	(void) valid_sched(sp, spsize, KSCHED_PARMS);
367 	sp->sp_sched_off = sp->sp_datalen - 4;
368 	expect_err("sched-off", EINVAL, "/usr/bin/true",
369 	    sp, spsize, &au.sa, sasize);
370 
371 	/* Invalid operation. */
372 	(void) valid_sched(sp, spsize, 0);
373 	expect_err("sched-op", EINVAL, "/usr/bin/true",
374 	    sp, spsize, &au.sa, sasize);
375 
376 	/* A priority change must be a set operation. */
377 	ks = valid_sched(sp, spsize, KSCHED_PRIO);
378 	ks->ksched_prio.pc_op = PC_GETPRIO;
379 	ks->ksched_prio.pc_cid = 1;
380 	expect_err("sched-prio-op", EINVAL, "/usr/bin/true",
381 	    sp, spsize, &au.sa, sasize);
382 
383 	/*
384 	 * Class IDs out of range. These are not caught until the child
385 	 * applies the attributes to itself, exercising the error handshake.
386 	 */
387 	ks = valid_sched(sp, spsize, KSCHED_PRIO);
388 	ks->ksched_prio.pc_op = PC_SETPRIO;
389 	ks->ksched_prio.pc_cid = 999;
390 	expect_err("sched-prio-cid", EINVAL, "/usr/bin/true",
391 	    sp, spsize, &au.sa, sasize);
392 
393 	ks = valid_sched(sp, spsize, KSCHED_PARMS);
394 	ks->ksched_parms.pc_cid = 999;
395 	expect_err("sched-parms-cid", EINVAL, "/usr/bin/true",
396 	    sp, spsize, &au.sa, sasize);
397 }
398 
399 /*
400  * A child killed immediately after spawn(2) returns must be waitable and
401  * must never leave the parent stuck. We try a few times to give us a fighting
402  * chance of catching it.
403  */
404 static void
t_kill_race(void)405 t_kill_race(void)
406 {
407 	union {
408 		spawn_args_t sa;
409 		char pad[sizeof (spawn_args_t) + SA_DATA];
410 	} au;
411 	uint32_t sasize = valid_args(&au.sa);
412 
413 	/*
414 	 * Spawn a child and immediately kill it, over and over, to exercise
415 	 * the window between creation and exec. valid_args() supplies only
416 	 * argv[0], so sleep runs with no duration. That does not matter,
417 	 * because we SIGKILL it at once - all we need is a live PID to race
418 	 * against.
419 	 */
420 	for (int i = 0; i < 100; i++) {
421 		int status;
422 		pid_t pid;
423 
424 		pid = raw_spawn("/usr/bin/sleep", NULL, 0, &au.sa, sasize);
425 		if (pid == -1) {
426 			TFAIL("kill-race", "spawn failed: %s",
427 			    strerror(errno));
428 			return;
429 		}
430 		(void) kill(pid, SIGKILL);
431 		if (waitpid(pid, &status, 0) != pid) {
432 			TFAIL("kill-race", "waitpid: %s", strerror(errno));
433 			return;
434 		}
435 		if (!WIFSIGNALED(status) &&
436 		    !(WIFEXITED(status) && WEXITSTATUS(status) != 0)) {
437 			TFAIL("kill-race", "iteration %d: status %#x",
438 			    i, status);
439 			return;
440 		}
441 	}
442 	TPASS("kill-race");
443 }
444 
445 int
main(void)446 main(void)
447 {
448 	t_valid();
449 	t_args_fuzz();
450 	t_param_fuzz();
451 	t_sched_fuzz();
452 	t_kill_race();
453 
454 	if (failures == 0) {
455 		(void) printf("All tests passed\n");
456 		return (EXIT_SUCCESS);
457 	}
458 
459 	(void) fprintf(stderr, "%u test(s) failed\n", failures);
460 	return (EXIT_FAILURE);
461 }
462