xref: /freebsd/lib/libc/gen/posix_spawn.c (revision 96190b4fef3b4a0cc3ca0606b0c4e3e69a5e6717)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2008 Ed Schouten <ed@FreeBSD.org>
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26  * SUCH DAMAGE.
27  */
28 
29 #include "namespace.h"
30 #include <sys/param.h>
31 #include <sys/procctl.h>
32 #include <sys/queue.h>
33 #include <sys/wait.h>
34 
35 #include <errno.h>
36 #include <fcntl.h>
37 #include <sched.h>
38 #include <spawn.h>
39 #include <signal.h>
40 #include <stdlib.h>
41 #include <string.h>
42 #include <unistd.h>
43 #include "un-namespace.h"
44 #include "libc_private.h"
45 
46 struct __posix_spawnattr {
47 	short			sa_flags;
48 	pid_t			sa_pgroup;
49 	struct sched_param	sa_schedparam;
50 	int			sa_schedpolicy;
51 	sigset_t		sa_sigdefault;
52 	sigset_t		sa_sigmask;
53 };
54 
55 struct __posix_spawn_file_actions {
56 	STAILQ_HEAD(, __posix_spawn_file_actions_entry) fa_list;
57 };
58 
59 typedef struct __posix_spawn_file_actions_entry {
60 	STAILQ_ENTRY(__posix_spawn_file_actions_entry) fae_list;
61 	enum {
62 		FAE_OPEN,
63 		FAE_DUP2,
64 		FAE_CLOSE,
65 		FAE_CHDIR,
66 		FAE_FCHDIR,
67 		FAE_CLOSEFROM,
68 	} fae_action;
69 
70 	int fae_fildes;
71 	union {
72 		struct {
73 			char *path;
74 #define fae_path	fae_data.open.path
75 			int oflag;
76 #define fae_oflag	fae_data.open.oflag
77 			mode_t mode;
78 #define fae_mode	fae_data.open.mode
79 		} open;
80 		struct {
81 			int newfildes;
82 #define fae_newfildes	fae_data.dup2.newfildes
83 		} dup2;
84 	} fae_data;
85 } posix_spawn_file_actions_entry_t;
86 
87 /*
88  * Spawn routines
89  */
90 
91 static int
92 process_spawnattr(const posix_spawnattr_t sa)
93 {
94 	struct sigaction sigact = { .sa_flags = 0, .sa_handler = SIG_DFL };
95 	int aslr, i;
96 
97 	/*
98 	 * POSIX doesn't really describe in which order everything
99 	 * should be set. We'll just set them in the order in which they
100 	 * are mentioned.
101 	 */
102 
103 	/* Set process group */
104 	if (sa->sa_flags & POSIX_SPAWN_SETPGROUP) {
105 		if (setpgid(0, sa->sa_pgroup) != 0)
106 			return (errno);
107 	}
108 
109 	/* Set scheduler policy */
110 	if (sa->sa_flags & POSIX_SPAWN_SETSCHEDULER) {
111 		if (sched_setscheduler(0, sa->sa_schedpolicy,
112 		    &sa->sa_schedparam) != 0)
113 			return (errno);
114 	} else if (sa->sa_flags & POSIX_SPAWN_SETSCHEDPARAM) {
115 		if (sched_setparam(0, &sa->sa_schedparam) != 0)
116 			return (errno);
117 	}
118 
119 	/* Reset user ID's */
120 	if (sa->sa_flags & POSIX_SPAWN_RESETIDS) {
121 		if (setegid(getgid()) != 0)
122 			return (errno);
123 		if (seteuid(getuid()) != 0)
124 			return (errno);
125 	}
126 
127 	/*
128 	 * Set signal masks/defaults.
129 	 * Use unwrapped syscall, libthr is in undefined state after vfork().
130 	 */
131 	if (sa->sa_flags & POSIX_SPAWN_SETSIGMASK) {
132 		__sys_sigprocmask(SIG_SETMASK, &sa->sa_sigmask, NULL);
133 	}
134 
135 	if (sa->sa_flags & POSIX_SPAWN_SETSIGDEF) {
136 		for (i = 1; i <= _SIG_MAXSIG; i++) {
137 			if (sigismember(&sa->sa_sigdefault, i))
138 				if (__sys_sigaction(i, &sigact, NULL) != 0)
139 					return (errno);
140 		}
141 	}
142 
143 	/* Disable ASLR. */
144 	if ((sa->sa_flags & POSIX_SPAWN_DISABLE_ASLR_NP) != 0) {
145 		aslr = PROC_ASLR_FORCE_DISABLE;
146 		if (procctl(P_PID, 0, PROC_ASLR_CTL, &aslr) != 0)
147 			return (errno);
148 	}
149 
150 	return (0);
151 }
152 
153 static int
154 process_file_actions_entry(posix_spawn_file_actions_entry_t *fae)
155 {
156 	int fd, saved_errno;
157 
158 	switch (fae->fae_action) {
159 	case FAE_OPEN:
160 		/* Perform an open(), make it use the right fd */
161 		fd = _open(fae->fae_path, fae->fae_oflag, fae->fae_mode);
162 		if (fd < 0)
163 			return (errno);
164 		if (fd != fae->fae_fildes) {
165 			if (_dup2(fd, fae->fae_fildes) == -1) {
166 				saved_errno = errno;
167 				(void)_close(fd);
168 				return (saved_errno);
169 			}
170 			if (_close(fd) != 0) {
171 				if (errno == EBADF)
172 					return (EBADF);
173 			}
174 		}
175 		if (_fcntl(fae->fae_fildes, F_SETFD, 0) == -1)
176 			return (errno);
177 		break;
178 	case FAE_DUP2:
179 		/* Perform a dup2() */
180 		if (_dup2(fae->fae_fildes, fae->fae_newfildes) == -1)
181 			return (errno);
182 		if (_fcntl(fae->fae_newfildes, F_SETFD, 0) == -1)
183 			return (errno);
184 		break;
185 	case FAE_CLOSE:
186 		/* Perform a close(), do not fail if already closed */
187 		(void)_close(fae->fae_fildes);
188 		break;
189 	case FAE_CHDIR:
190 		if (chdir(fae->fae_path) != 0)
191 			return (errno);
192 		break;
193 	case FAE_FCHDIR:
194 		if (fchdir(fae->fae_fildes) != 0)
195 			return (errno);
196 		break;
197 	case FAE_CLOSEFROM:
198 		closefrom(fae->fae_fildes);
199 		break;
200 	}
201 	return (0);
202 }
203 
204 static int
205 process_file_actions(const posix_spawn_file_actions_t fa)
206 {
207 	posix_spawn_file_actions_entry_t *fae;
208 	int error;
209 
210 	/* Replay all file descriptor modifications */
211 	STAILQ_FOREACH(fae, &fa->fa_list, fae_list) {
212 		error = process_file_actions_entry(fae);
213 		if (error)
214 			return (error);
215 	}
216 	return (0);
217 }
218 
219 struct posix_spawn_args {
220 	const char *path;
221 	const posix_spawn_file_actions_t *fa;
222 	const posix_spawnattr_t *sa;
223 	char * const * argv;
224 	char * const * envp;
225 	int use_env_path;
226 	volatile int error;
227 };
228 
229 #define	PSPAWN_STACK_ALIGNMENT	16
230 #define	PSPAWN_STACK_ALIGNBYTES	(PSPAWN_STACK_ALIGNMENT - 1)
231 #define	PSPAWN_STACK_ALIGN(sz) \
232 	(((sz) + PSPAWN_STACK_ALIGNBYTES) & ~PSPAWN_STACK_ALIGNBYTES)
233 
234 #if defined(__i386__) || defined(__amd64__)
235 /*
236  * Below we'll assume that _RFORK_THREAD_STACK_SIZE is appropriately aligned for
237  * the posix_spawn() case where we do not end up calling execvpe and won't ever
238  * try to allocate space on the stack for argv[].
239  */
240 #define	_RFORK_THREAD_STACK_SIZE	4096
241 _Static_assert((_RFORK_THREAD_STACK_SIZE % PSPAWN_STACK_ALIGNMENT) == 0,
242     "Inappropriate stack size alignment");
243 #endif
244 
245 static int
246 _posix_spawn_thr(void *data)
247 {
248 	struct posix_spawn_args *psa;
249 	char * const *envp;
250 
251 	psa = data;
252 	if (psa->sa != NULL) {
253 		psa->error = process_spawnattr(*psa->sa);
254 		if (psa->error)
255 			_exit(127);
256 	}
257 	if (psa->fa != NULL) {
258 		psa->error = process_file_actions(*psa->fa);
259 		if (psa->error)
260 			_exit(127);
261 	}
262 	envp = psa->envp != NULL ? psa->envp : environ;
263 	if (psa->use_env_path)
264 		execvpe(psa->path, psa->argv, envp);
265 	else
266 		_execve(psa->path, psa->argv, envp);
267 	psa->error = errno;
268 
269 	/* This is called in such a way that it must not exit. */
270 	_exit(127);
271 }
272 
273 static int
274 do_posix_spawn(pid_t *pid, const char *path,
275     const posix_spawn_file_actions_t *fa,
276     const posix_spawnattr_t *sa,
277     char * const argv[], char * const envp[], int use_env_path)
278 {
279 	struct posix_spawn_args psa;
280 	pid_t p;
281 #ifdef _RFORK_THREAD_STACK_SIZE
282 	char *stack;
283 	size_t cnt, stacksz;
284 
285 	stacksz = _RFORK_THREAD_STACK_SIZE;
286 	if (use_env_path) {
287 		/*
288 		 * We need to make sure we have enough room on the stack for the
289 		 * potential alloca() in execvPe if it gets kicked back an
290 		 * ENOEXEC from execve(2), plus the original buffer we gave
291 		 * ourselves; this protects us in the event that the caller
292 		 * intentionally or inadvertently supplies enough arguments to
293 		 * make us blow past the stack we've allocated from it.
294 		 */
295 		for (cnt = 0; argv[cnt] != NULL; ++cnt)
296 			;
297 		stacksz += MAX(3, cnt + 2) * sizeof(char *);
298 		stacksz = PSPAWN_STACK_ALIGN(stacksz);
299 	}
300 
301 	/*
302 	 * aligned_alloc is not safe to use here, because we can't guarantee
303 	 * that aligned_alloc and free will be provided by the same
304 	 * implementation.  We've actively hit at least one application that
305 	 * will provide its own malloc/free but not aligned_alloc leading to
306 	 * a free by the wrong allocator.
307 	 */
308 	stack = malloc(stacksz);
309 	if (stack == NULL)
310 		return (ENOMEM);
311 	stacksz = (((uintptr_t)stack + stacksz) & ~PSPAWN_STACK_ALIGNBYTES) -
312 	    (uintptr_t)stack;
313 #endif
314 	psa.path = path;
315 	psa.fa = fa;
316 	psa.sa = sa;
317 	psa.argv = argv;
318 	psa.envp = envp;
319 	psa.use_env_path = use_env_path;
320 	psa.error = 0;
321 
322 	/*
323 	 * Passing RFSPAWN to rfork(2) gives us effectively a vfork that drops
324 	 * non-ignored signal handlers.  We'll fall back to the slightly less
325 	 * ideal vfork(2) if we get an EINVAL from rfork -- this should only
326 	 * happen with newer libc on older kernel that doesn't accept
327 	 * RFSPAWN.
328 	 *
329 	 * Combination of vfork() (or its equivalent rfork() form) and
330 	 * a special property of the libthr rtld locks ensure that
331 	 * rtld is operational in the child.  In particular, libthr
332 	 * rtld locks do not store owner' tid into the lock word.
333 	 */
334 #ifdef _RFORK_THREAD_STACK_SIZE
335 	/*
336 	 * x86 stores the return address on the stack, so rfork(2) cannot work
337 	 * as-is because the child would clobber the return address of the
338 	 * parent.  Because of this, we must use rfork_thread instead while
339 	 * almost every other arch stores the return address in a register.
340 	 */
341 	p = rfork_thread(RFSPAWN, stack + stacksz, _posix_spawn_thr, &psa);
342 	free(stack);
343 #else
344 	p = rfork(RFSPAWN);
345 	if (p == 0)
346 		/* _posix_spawn_thr does not return */
347 		_posix_spawn_thr(&psa);
348 #endif
349 	/*
350 	 * The above block should leave us in a state where we've either
351 	 * succeeded and we're ready to process the results, or we need to
352 	 * fallback to vfork() if the kernel didn't like RFSPAWN.
353 	 */
354 
355 	if (p == -1 && errno == EINVAL) {
356 		p = vfork();
357 		if (p == 0)
358 			/* _posix_spawn_thr does not return */
359 			_posix_spawn_thr(&psa);
360 	}
361 	if (p == -1)
362 		return (errno);
363 	if (psa.error != 0)
364 		/* Failed; ready to reap */
365 		_waitpid(p, NULL, WNOHANG);
366 	else if (pid != NULL)
367 		/* exec succeeded */
368 		*pid = p;
369 	return (psa.error);
370 }
371 
372 int
373 posix_spawn(pid_t *pid, const char *path,
374     const posix_spawn_file_actions_t *fa,
375     const posix_spawnattr_t *sa,
376     char * const argv[], char * const envp[])
377 {
378 	return (do_posix_spawn(pid, path, fa, sa, argv, envp, 0));
379 }
380 
381 int
382 posix_spawnp(pid_t *pid, const char *path,
383     const posix_spawn_file_actions_t *fa,
384     const posix_spawnattr_t *sa,
385     char * const argv[], char * const envp[])
386 {
387 	return (do_posix_spawn(pid, path, fa, sa, argv, envp, 1));
388 }
389 
390 /*
391  * File descriptor actions
392  */
393 
394 int
395 posix_spawn_file_actions_init(posix_spawn_file_actions_t *ret)
396 {
397 	posix_spawn_file_actions_t fa;
398 
399 	fa = malloc(sizeof(struct __posix_spawn_file_actions));
400 	if (fa == NULL)
401 		return (-1);
402 
403 	STAILQ_INIT(&fa->fa_list);
404 	*ret = fa;
405 	return (0);
406 }
407 
408 int
409 posix_spawn_file_actions_destroy(posix_spawn_file_actions_t *fa)
410 {
411 	posix_spawn_file_actions_entry_t *fae;
412 
413 	while ((fae = STAILQ_FIRST(&(*fa)->fa_list)) != NULL) {
414 		/* Remove file action entry from the queue */
415 		STAILQ_REMOVE_HEAD(&(*fa)->fa_list, fae_list);
416 
417 		/* Deallocate file action entry */
418 		if (fae->fae_action == FAE_OPEN ||
419 		    fae->fae_action == FAE_CHDIR)
420 			free(fae->fae_path);
421 		free(fae);
422 	}
423 
424 	free(*fa);
425 	return (0);
426 }
427 
428 int
429 posix_spawn_file_actions_addopen(posix_spawn_file_actions_t * __restrict fa,
430     int fildes, const char * __restrict path, int oflag, mode_t mode)
431 {
432 	posix_spawn_file_actions_entry_t *fae;
433 	int error;
434 
435 	if (fildes < 0)
436 		return (EBADF);
437 
438 	/* Allocate object */
439 	fae = malloc(sizeof(posix_spawn_file_actions_entry_t));
440 	if (fae == NULL)
441 		return (errno);
442 
443 	/* Set values and store in queue */
444 	fae->fae_action = FAE_OPEN;
445 	fae->fae_path = strdup(path);
446 	if (fae->fae_path == NULL) {
447 		error = errno;
448 		free(fae);
449 		return (error);
450 	}
451 	fae->fae_fildes = fildes;
452 	fae->fae_oflag = oflag;
453 	fae->fae_mode = mode;
454 
455 	STAILQ_INSERT_TAIL(&(*fa)->fa_list, fae, fae_list);
456 	return (0);
457 }
458 
459 int
460 posix_spawn_file_actions_adddup2(posix_spawn_file_actions_t *fa,
461     int fildes, int newfildes)
462 {
463 	posix_spawn_file_actions_entry_t *fae;
464 
465 	if (fildes < 0 || newfildes < 0)
466 		return (EBADF);
467 
468 	/* Allocate object */
469 	fae = malloc(sizeof(posix_spawn_file_actions_entry_t));
470 	if (fae == NULL)
471 		return (errno);
472 
473 	/* Set values and store in queue */
474 	fae->fae_action = FAE_DUP2;
475 	fae->fae_fildes = fildes;
476 	fae->fae_newfildes = newfildes;
477 
478 	STAILQ_INSERT_TAIL(&(*fa)->fa_list, fae, fae_list);
479 	return (0);
480 }
481 
482 int
483 posix_spawn_file_actions_addclose(posix_spawn_file_actions_t *fa,
484     int fildes)
485 {
486 	posix_spawn_file_actions_entry_t *fae;
487 
488 	if (fildes < 0)
489 		return (EBADF);
490 
491 	/* Allocate object */
492 	fae = malloc(sizeof(posix_spawn_file_actions_entry_t));
493 	if (fae == NULL)
494 		return (errno);
495 
496 	/* Set values and store in queue */
497 	fae->fae_action = FAE_CLOSE;
498 	fae->fae_fildes = fildes;
499 
500 	STAILQ_INSERT_TAIL(&(*fa)->fa_list, fae, fae_list);
501 	return (0);
502 }
503 
504 int
505 posix_spawn_file_actions_addchdir_np(posix_spawn_file_actions_t *
506     __restrict fa, const char *__restrict path)
507 {
508 	posix_spawn_file_actions_entry_t *fae;
509 	int error;
510 
511 	fae = malloc(sizeof(posix_spawn_file_actions_entry_t));
512 	if (fae == NULL)
513 		return (errno);
514 
515 	fae->fae_action = FAE_CHDIR;
516 	fae->fae_path = strdup(path);
517 	if (fae->fae_path == NULL) {
518 		error = errno;
519 		free(fae);
520 		return (error);
521 	}
522 
523 	STAILQ_INSERT_TAIL(&(*fa)->fa_list, fae, fae_list);
524 	return (0);
525 }
526 
527 int
528 posix_spawn_file_actions_addfchdir_np(posix_spawn_file_actions_t *__restrict fa,
529     int fildes)
530 {
531 	posix_spawn_file_actions_entry_t *fae;
532 
533 	if (fildes < 0)
534 		return (EBADF);
535 
536 	/* Allocate object */
537 	fae = malloc(sizeof(posix_spawn_file_actions_entry_t));
538 	if (fae == NULL)
539 		return (errno);
540 
541 	fae->fae_action = FAE_FCHDIR;
542 	fae->fae_fildes = fildes;
543 
544 	STAILQ_INSERT_TAIL(&(*fa)->fa_list, fae, fae_list);
545 	return (0);
546 }
547 
548 int
549 posix_spawn_file_actions_addclosefrom_np (posix_spawn_file_actions_t *
550     __restrict fa, int from)
551 {
552 	posix_spawn_file_actions_entry_t *fae;
553 
554 	if (from < 0)
555 		return (EBADF);
556 
557 	/* Allocate object */
558 	fae = malloc(sizeof(posix_spawn_file_actions_entry_t));
559 	if (fae == NULL)
560 		return (errno);
561 
562 	fae->fae_action = FAE_CLOSEFROM;
563 	fae->fae_fildes = from;
564 
565 	STAILQ_INSERT_TAIL(&(*fa)->fa_list, fae, fae_list);
566 	return (0);
567 }
568 
569 /*
570  * Spawn attributes
571  */
572 
573 int
574 posix_spawnattr_init(posix_spawnattr_t *ret)
575 {
576 	posix_spawnattr_t sa;
577 
578 	sa = calloc(1, sizeof(struct __posix_spawnattr));
579 	if (sa == NULL)
580 		return (errno);
581 
582 	/* Set defaults as specified by POSIX, cleared above */
583 	*ret = sa;
584 	return (0);
585 }
586 
587 int
588 posix_spawnattr_destroy(posix_spawnattr_t *sa)
589 {
590 	free(*sa);
591 	return (0);
592 }
593 
594 int
595 posix_spawnattr_getflags(const posix_spawnattr_t * __restrict sa,
596     short * __restrict flags)
597 {
598 	*flags = (*sa)->sa_flags;
599 	return (0);
600 }
601 
602 int
603 posix_spawnattr_getpgroup(const posix_spawnattr_t * __restrict sa,
604     pid_t * __restrict pgroup)
605 {
606 	*pgroup = (*sa)->sa_pgroup;
607 	return (0);
608 }
609 
610 int
611 posix_spawnattr_getschedparam(const posix_spawnattr_t * __restrict sa,
612     struct sched_param * __restrict schedparam)
613 {
614 	*schedparam = (*sa)->sa_schedparam;
615 	return (0);
616 }
617 
618 int
619 posix_spawnattr_getschedpolicy(const posix_spawnattr_t * __restrict sa,
620     int * __restrict schedpolicy)
621 {
622 	*schedpolicy = (*sa)->sa_schedpolicy;
623 	return (0);
624 }
625 
626 int
627 posix_spawnattr_getsigdefault(const posix_spawnattr_t * __restrict sa,
628     sigset_t * __restrict sigdefault)
629 {
630 	*sigdefault = (*sa)->sa_sigdefault;
631 	return (0);
632 }
633 
634 int
635 posix_spawnattr_getsigmask(const posix_spawnattr_t * __restrict sa,
636     sigset_t * __restrict sigmask)
637 {
638 	*sigmask = (*sa)->sa_sigmask;
639 	return (0);
640 }
641 
642 int
643 posix_spawnattr_setflags(posix_spawnattr_t *sa, short flags)
644 {
645 	if ((flags & ~(POSIX_SPAWN_RESETIDS | POSIX_SPAWN_SETPGROUP |
646 	    POSIX_SPAWN_SETSCHEDPARAM | POSIX_SPAWN_SETSCHEDULER |
647 	    POSIX_SPAWN_SETSIGDEF | POSIX_SPAWN_SETSIGMASK |
648 	    POSIX_SPAWN_DISABLE_ASLR_NP)) != 0)
649 		return (EINVAL);
650 	(*sa)->sa_flags = flags;
651 	return (0);
652 }
653 
654 int
655 posix_spawnattr_setpgroup(posix_spawnattr_t *sa, pid_t pgroup)
656 {
657 	(*sa)->sa_pgroup = pgroup;
658 	return (0);
659 }
660 
661 int
662 posix_spawnattr_setschedparam(posix_spawnattr_t * __restrict sa,
663     const struct sched_param * __restrict schedparam)
664 {
665 	(*sa)->sa_schedparam = *schedparam;
666 	return (0);
667 }
668 
669 int
670 posix_spawnattr_setschedpolicy(posix_spawnattr_t *sa, int schedpolicy)
671 {
672 	(*sa)->sa_schedpolicy = schedpolicy;
673 	return (0);
674 }
675 
676 int
677 posix_spawnattr_setsigdefault(posix_spawnattr_t * __restrict sa,
678     const sigset_t * __restrict sigdefault)
679 {
680 	(*sa)->sa_sigdefault = *sigdefault;
681 	return (0);
682 }
683 
684 int
685 posix_spawnattr_setsigmask(posix_spawnattr_t * __restrict sa,
686     const sigset_t * __restrict sigmask)
687 {
688 	(*sa)->sa_sigmask = *sigmask;
689 	return (0);
690 }
691