1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include <spawn.h>
18 
19 #include <errno.h>
20 #include <fcntl.h>
21 #include <gtest/gtest.h>
22 
23 #include "SignalUtils.h"
24 #include "utils.h"
25 
26 #include <android-base/file.h>
27 #include <android-base/strings.h>
28 
29 // Old versions of glibc didn't have POSIX_SPAWN_SETSID.
30 #if __GLIBC__
31 # if !defined(POSIX_SPAWN_SETSID)
32 #  define POSIX_SPAWN_SETSID 0
33 # endif
34 #else
35 #include <platform/bionic/reserved_signals.h>
36 #endif
37 
TEST(spawn,posix_spawnattr_init_posix_spawnattr_destroy)38 TEST(spawn, posix_spawnattr_init_posix_spawnattr_destroy) {
39   posix_spawnattr_t sa;
40   ASSERT_EQ(0, posix_spawnattr_init(&sa));
41   ASSERT_EQ(0, posix_spawnattr_destroy(&sa));
42 }
43 
TEST(spawn,posix_spawnattr_setflags_EINVAL)44 TEST(spawn, posix_spawnattr_setflags_EINVAL) {
45   posix_spawnattr_t sa;
46   ASSERT_EQ(0, posix_spawnattr_init(&sa));
47   ASSERT_EQ(EINVAL, posix_spawnattr_setflags(&sa, ~0));
48   ASSERT_EQ(0, posix_spawnattr_destroy(&sa));
49 }
50 
TEST(spawn,posix_spawnattr_setflags_posix_spawnattr_getflags)51 TEST(spawn, posix_spawnattr_setflags_posix_spawnattr_getflags) {
52   posix_spawnattr_t sa;
53   ASSERT_EQ(0, posix_spawnattr_init(&sa));
54 
55   ASSERT_EQ(0, posix_spawnattr_setflags(&sa, POSIX_SPAWN_RESETIDS));
56   short flags;
57   ASSERT_EQ(0, posix_spawnattr_getflags(&sa, &flags));
58   ASSERT_EQ(POSIX_SPAWN_RESETIDS, flags);
59 
60   constexpr short all_flags = POSIX_SPAWN_RESETIDS | POSIX_SPAWN_SETPGROUP | POSIX_SPAWN_SETSIGDEF |
61                               POSIX_SPAWN_SETSIGMASK | POSIX_SPAWN_SETSCHEDPARAM |
62                               POSIX_SPAWN_SETSCHEDULER | POSIX_SPAWN_USEVFORK | POSIX_SPAWN_SETSID;
63   ASSERT_EQ(0, posix_spawnattr_setflags(&sa, all_flags));
64   ASSERT_EQ(0, posix_spawnattr_getflags(&sa, &flags));
65   ASSERT_EQ(all_flags, flags);
66 
67   ASSERT_EQ(0, posix_spawnattr_destroy(&sa));
68 }
69 
TEST(spawn,posix_spawnattr_setpgroup_posix_spawnattr_getpgroup)70 TEST(spawn, posix_spawnattr_setpgroup_posix_spawnattr_getpgroup) {
71   posix_spawnattr_t sa;
72   ASSERT_EQ(0, posix_spawnattr_init(&sa));
73 
74   ASSERT_EQ(0, posix_spawnattr_setpgroup(&sa, 123));
75   pid_t g;
76   ASSERT_EQ(0, posix_spawnattr_getpgroup(&sa, &g));
77   ASSERT_EQ(123, g);
78 
79   ASSERT_EQ(0, posix_spawnattr_destroy(&sa));
80 }
81 
TEST(spawn,posix_spawnattr_setsigmask_posix_spawnattr_getsigmask)82 TEST(spawn, posix_spawnattr_setsigmask_posix_spawnattr_getsigmask) {
83   posix_spawnattr_t sa;
84   ASSERT_EQ(0, posix_spawnattr_init(&sa));
85 
86   sigset_t sigs;
87   ASSERT_EQ(0, posix_spawnattr_getsigmask(&sa, &sigs));
88   ASSERT_FALSE(sigismember(&sigs, SIGALRM));
89 
90   sigset_t just_SIGALRM;
91   sigemptyset(&just_SIGALRM);
92   sigaddset(&just_SIGALRM, SIGALRM);
93   ASSERT_EQ(0, posix_spawnattr_setsigmask(&sa, &just_SIGALRM));
94 
95   ASSERT_EQ(0, posix_spawnattr_getsigmask(&sa, &sigs));
96   ASSERT_TRUE(sigismember(&sigs, SIGALRM));
97 
98   ASSERT_EQ(0, posix_spawnattr_destroy(&sa));
99 }
100 
TEST(spawn,posix_spawnattr_setsigmask64_posix_spawnattr_getsigmask64)101 TEST(spawn, posix_spawnattr_setsigmask64_posix_spawnattr_getsigmask64) {
102   posix_spawnattr_t sa;
103   ASSERT_EQ(0, posix_spawnattr_init(&sa));
104 
105   sigset64_t sigs;
106   ASSERT_EQ(0, posix_spawnattr_getsigmask64(&sa, &sigs));
107   ASSERT_FALSE(sigismember64(&sigs, SIGRTMIN));
108 
109   sigset64_t just_SIGRTMIN;
110   sigemptyset64(&just_SIGRTMIN);
111   sigaddset64(&just_SIGRTMIN, SIGRTMIN);
112   ASSERT_EQ(0, posix_spawnattr_setsigmask64(&sa, &just_SIGRTMIN));
113 
114   ASSERT_EQ(0, posix_spawnattr_getsigmask64(&sa, &sigs));
115   ASSERT_TRUE(sigismember64(&sigs, SIGRTMIN));
116 
117   ASSERT_EQ(0, posix_spawnattr_destroy(&sa));
118 }
119 
TEST(spawn,posix_spawnattr_setsigdefault_posix_spawnattr_getsigdefault)120 TEST(spawn, posix_spawnattr_setsigdefault_posix_spawnattr_getsigdefault) {
121   posix_spawnattr_t sa;
122   ASSERT_EQ(0, posix_spawnattr_init(&sa));
123 
124   sigset_t sigs;
125   ASSERT_EQ(0, posix_spawnattr_getsigdefault(&sa, &sigs));
126   ASSERT_FALSE(sigismember(&sigs, SIGALRM));
127 
128   sigset_t just_SIGALRM;
129   sigemptyset(&just_SIGALRM);
130   sigaddset(&just_SIGALRM, SIGALRM);
131   ASSERT_EQ(0, posix_spawnattr_setsigdefault(&sa, &just_SIGALRM));
132 
133   ASSERT_EQ(0, posix_spawnattr_getsigdefault(&sa, &sigs));
134   ASSERT_TRUE(sigismember(&sigs, SIGALRM));
135 
136   ASSERT_EQ(0, posix_spawnattr_destroy(&sa));
137 }
138 
TEST(spawn,posix_spawnattr_setsigdefault64_posix_spawnattr_getsigdefault64)139 TEST(spawn, posix_spawnattr_setsigdefault64_posix_spawnattr_getsigdefault64) {
140   posix_spawnattr_t sa;
141   ASSERT_EQ(0, posix_spawnattr_init(&sa));
142 
143   sigset64_t sigs;
144   ASSERT_EQ(0, posix_spawnattr_getsigdefault64(&sa, &sigs));
145   ASSERT_FALSE(sigismember64(&sigs, SIGRTMIN));
146 
147   sigset64_t just_SIGRTMIN;
148   sigemptyset64(&just_SIGRTMIN);
149   sigaddset64(&just_SIGRTMIN, SIGRTMIN);
150   ASSERT_EQ(0, posix_spawnattr_setsigdefault64(&sa, &just_SIGRTMIN));
151 
152   ASSERT_EQ(0, posix_spawnattr_getsigdefault64(&sa, &sigs));
153   ASSERT_TRUE(sigismember64(&sigs, SIGRTMIN));
154 
155   ASSERT_EQ(0, posix_spawnattr_destroy(&sa));
156 }
157 
TEST(spawn,posix_spawnattr_setsschedparam_posix_spawnattr_getsschedparam)158 TEST(spawn, posix_spawnattr_setsschedparam_posix_spawnattr_getsschedparam) {
159   posix_spawnattr_t sa;
160   ASSERT_EQ(0, posix_spawnattr_init(&sa));
161 
162   sched_param sp;
163   ASSERT_EQ(0, posix_spawnattr_getschedparam(&sa, &sp));
164   ASSERT_EQ(0, sp.sched_priority);
165 
166   sched_param sp123 = { .sched_priority = 123 };
167   ASSERT_EQ(0, posix_spawnattr_setschedparam(&sa, &sp123));
168 
169   ASSERT_EQ(0, posix_spawnattr_getschedparam(&sa, &sp));
170   ASSERT_EQ(123, sp.sched_priority);
171 
172   ASSERT_EQ(0, posix_spawnattr_destroy(&sa));
173 }
174 
TEST(spawn,posix_spawnattr_setschedpolicy_posix_spawnattr_getschedpolicy)175 TEST(spawn, posix_spawnattr_setschedpolicy_posix_spawnattr_getschedpolicy) {
176   posix_spawnattr_t sa;
177   ASSERT_EQ(0, posix_spawnattr_init(&sa));
178 
179   int p;
180   ASSERT_EQ(0, posix_spawnattr_getschedpolicy(&sa, &p));
181   ASSERT_EQ(0, p);
182 
183   ASSERT_EQ(0, posix_spawnattr_setschedpolicy(&sa, SCHED_FIFO));
184 
185   ASSERT_EQ(0, posix_spawnattr_getschedpolicy(&sa, &p));
186   ASSERT_EQ(SCHED_FIFO, p);
187 
188   ASSERT_EQ(0, posix_spawnattr_destroy(&sa));
189 }
190 
TEST(spawn,posix_spawn)191 TEST(spawn, posix_spawn) {
192   ExecTestHelper eth;
193   eth.SetArgs({BIN_DIR "true", nullptr});
194   pid_t pid;
195   ASSERT_EQ(0, posix_spawn(&pid, eth.GetArg0(), nullptr, nullptr, eth.GetArgs(), nullptr));
196   AssertChildExited(pid, 0);
197 }
198 
TEST(spawn,posix_spawn_not_found)199 TEST(spawn, posix_spawn_not_found) {
200   ExecTestHelper eth;
201   eth.SetArgs({"true", nullptr});
202   pid_t pid;
203   ASSERT_EQ(0, posix_spawn(&pid, eth.GetArg0(), nullptr, nullptr, eth.GetArgs(), nullptr));
204   AssertChildExited(pid, 127);
205 }
206 
TEST(spawn,posix_spawnp)207 TEST(spawn, posix_spawnp) {
208   ExecTestHelper eth;
209   eth.SetArgs({"true", nullptr});
210   pid_t pid;
211   ASSERT_EQ(0, posix_spawnp(&pid, eth.GetArg0(), nullptr, nullptr, eth.GetArgs(), nullptr));
212   AssertChildExited(pid, 0);
213 }
214 
TEST(spawn,posix_spawnp_not_found)215 TEST(spawn, posix_spawnp_not_found) {
216   ExecTestHelper eth;
217   eth.SetArgs({"does-not-exist", nullptr});
218   pid_t pid;
219   ASSERT_EQ(0, posix_spawnp(&pid, eth.GetArg0(), nullptr, nullptr, eth.GetArgs(), nullptr));
220   AssertChildExited(pid, 127);
221 }
222 
TEST(spawn,posix_spawn_environment)223 TEST(spawn, posix_spawn_environment) {
224   ExecTestHelper eth;
225   eth.SetArgs({"sh", "-c", "exit $posix_spawn_environment_test", nullptr});
226   eth.SetEnv({"posix_spawn_environment_test=66", nullptr});
227   pid_t pid;
228   ASSERT_EQ(0, posix_spawnp(&pid, eth.GetArg0(), nullptr, nullptr, eth.GetArgs(), eth.GetEnv()));
229   AssertChildExited(pid, 66);
230 }
231 
TEST(spawn,posix_spawn_file_actions)232 TEST(spawn, posix_spawn_file_actions) {
233   int fds[2];
234   ASSERT_NE(-1, pipe(fds));
235 
236   posix_spawn_file_actions_t fa;
237   ASSERT_EQ(0, posix_spawn_file_actions_init(&fa));
238 
239   ASSERT_EQ(0, posix_spawn_file_actions_addclose(&fa, fds[0]));
240   ASSERT_EQ(0, posix_spawn_file_actions_adddup2(&fa, fds[1], 1));
241   ASSERT_EQ(0, posix_spawn_file_actions_addclose(&fa, fds[1]));
242   // Check that close(2) failures are ignored by closing the same fd again.
243   ASSERT_EQ(0, posix_spawn_file_actions_addclose(&fa, fds[1]));
244   ASSERT_EQ(0, posix_spawn_file_actions_addopen(&fa, 56, "/proc/version", O_RDONLY, 0));
245 
246   ExecTestHelper eth;
247   eth.SetArgs({"ls", "-l", "/proc/self/fd", nullptr});
248   pid_t pid;
249   ASSERT_EQ(0, posix_spawnp(&pid, eth.GetArg0(), &fa, nullptr, eth.GetArgs(), eth.GetEnv()));
250   ASSERT_EQ(0, posix_spawn_file_actions_destroy(&fa));
251 
252   ASSERT_EQ(0, close(fds[1]));
253   std::string content;
254   ASSERT_TRUE(android::base::ReadFdToString(fds[0], &content));
255   ASSERT_EQ(0, close(fds[0]));
256 
257   AssertChildExited(pid, 0);
258 
259   // We'll know the dup2 worked if we see any ls(1) output in our pipe.
260   // The open we can check manually...
261   bool open_to_fd_56_worked = false;
262   for (const auto& line : android::base::Split(content, "\n")) {
263     if (line.find(" 56 -> /proc/version") != std::string::npos) open_to_fd_56_worked = true;
264   }
265   ASSERT_TRUE(open_to_fd_56_worked);
266 }
267 
CatFileToString(posix_spawnattr_t * sa,const char * path,std::string * content)268 static void CatFileToString(posix_spawnattr_t* sa, const char* path, std::string* content) {
269   int fds[2];
270   ASSERT_NE(-1, pipe(fds));
271 
272   posix_spawn_file_actions_t fa;
273   ASSERT_EQ(0, posix_spawn_file_actions_init(&fa));
274   ASSERT_EQ(0, posix_spawn_file_actions_addclose(&fa, fds[0]));
275   ASSERT_EQ(0, posix_spawn_file_actions_adddup2(&fa, fds[1], 1));
276   ASSERT_EQ(0, posix_spawn_file_actions_addclose(&fa, fds[1]));
277 
278   ExecTestHelper eth;
279   eth.SetArgs({"cat", path, nullptr});
280   pid_t pid;
281   ASSERT_EQ(0, posix_spawnp(&pid, eth.GetArg0(), &fa, sa, eth.GetArgs(), nullptr));
282   ASSERT_EQ(0, posix_spawn_file_actions_destroy(&fa));
283 
284   ASSERT_EQ(0, close(fds[1]));
285   ASSERT_TRUE(android::base::ReadFdToString(fds[0], content));
286   ASSERT_EQ(0, close(fds[0]));
287   AssertChildExited(pid, 0);
288 }
289 
290 struct ProcStat {
291   pid_t pid;
292   pid_t ppid;
293   pid_t pgrp;
294   pid_t sid;
295 };
296 
GetChildStat(posix_spawnattr_t * sa,ProcStat * ps)297 static __attribute__((unused)) void GetChildStat(posix_spawnattr_t* sa, ProcStat* ps) {
298   std::string content;
299   CatFileToString(sa, "/proc/self/stat", &content);
300 
301   ASSERT_EQ(4, sscanf(content.c_str(), "%d (cat) %*c %d %d %d", &ps->pid, &ps->ppid, &ps->pgrp,
302                       &ps->sid));
303 
304   ASSERT_EQ(getpid(), ps->ppid);
305 }
306 
307 struct ProcStatus {
308   uint64_t sigblk;
309   uint64_t sigign;
310 };
311 
GetChildStatus(posix_spawnattr_t * sa,ProcStatus * ps)312 static void __attribute__((unused)) GetChildStatus(posix_spawnattr_t* sa, ProcStatus* ps) {
313   std::string content;
314   CatFileToString(sa, "/proc/self/status", &content);
315 
316   bool saw_blk = false;
317   bool saw_ign = false;
318   for (const auto& line : android::base::Split(content, "\n")) {
319     if (sscanf(line.c_str(), "SigBlk: %" SCNx64, &ps->sigblk) == 1) saw_blk = true;
320     if (sscanf(line.c_str(), "SigIgn: %" SCNx64, &ps->sigign) == 1) saw_ign = true;
321   }
322   ASSERT_TRUE(saw_blk);
323   ASSERT_TRUE(saw_ign);
324 }
325 
TEST(spawn,posix_spawn_POSIX_SPAWN_SETSID_clear)326 TEST(spawn, posix_spawn_POSIX_SPAWN_SETSID_clear) {
327   pid_t parent_sid = getsid(0);
328 
329   posix_spawnattr_t sa;
330   ASSERT_EQ(0, posix_spawnattr_init(&sa));
331   ASSERT_EQ(0, posix_spawnattr_setflags(&sa, 0));
332 
333   ProcStat ps = {};
334   GetChildStat(&sa, &ps);
335   ASSERT_EQ(parent_sid, ps.sid);
336   ASSERT_EQ(0, posix_spawnattr_destroy(&sa));
337 }
338 
TEST(spawn,posix_spawn_POSIX_SPAWN_SETSID_set)339 TEST(spawn, posix_spawn_POSIX_SPAWN_SETSID_set) {
340   pid_t parent_sid = getsid(0);
341 
342   posix_spawnattr_t sa;
343   ASSERT_EQ(0, posix_spawnattr_init(&sa));
344   ASSERT_EQ(0, posix_spawnattr_setflags(&sa, POSIX_SPAWN_SETSID));
345 
346   ProcStat ps = {};
347   GetChildStat(&sa, &ps);
348   ASSERT_NE(parent_sid, ps.sid);
349   ASSERT_EQ(0, posix_spawnattr_destroy(&sa));
350 }
351 
TEST(spawn,posix_spawn_POSIX_SPAWN_SETPGROUP_clear)352 TEST(spawn, posix_spawn_POSIX_SPAWN_SETPGROUP_clear) {
353   pid_t parent_pgrp = getpgrp();
354 
355   posix_spawnattr_t sa;
356   ASSERT_EQ(0, posix_spawnattr_init(&sa));
357   ASSERT_EQ(0, posix_spawnattr_setflags(&sa, 0));
358 
359   ProcStat ps = {};
360   GetChildStat(&sa, &ps);
361   ASSERT_EQ(parent_pgrp, ps.pgrp);
362   ASSERT_EQ(0, posix_spawnattr_destroy(&sa));
363 }
364 
TEST(spawn,posix_spawn_POSIX_SPAWN_SETPGROUP_set)365 TEST(spawn, posix_spawn_POSIX_SPAWN_SETPGROUP_set) {
366   pid_t parent_pgrp = getpgrp();
367 
368   posix_spawnattr_t sa;
369   ASSERT_EQ(0, posix_spawnattr_init(&sa));
370   ASSERT_EQ(0, posix_spawnattr_setpgroup(&sa, 0));
371   ASSERT_EQ(0, posix_spawnattr_setflags(&sa, POSIX_SPAWN_SETPGROUP));
372 
373   ProcStat ps = {};
374   GetChildStat(&sa, &ps);
375   ASSERT_NE(parent_pgrp, ps.pgrp);
376   // Setting pgid 0 means "the same as the caller's pid".
377   ASSERT_EQ(ps.pid, ps.pgrp);
378   ASSERT_EQ(0, posix_spawnattr_destroy(&sa));
379 }
380 
TEST(spawn,posix_spawn_POSIX_SPAWN_SETSIGMASK)381 TEST(spawn, posix_spawn_POSIX_SPAWN_SETSIGMASK) {
382 #if defined(__GLIBC__)
383   GTEST_SKIP() << "glibc doesn't ignore the same signals.";
384 #else
385   // Block SIGBUS in the parent...
386   sigset_t just_SIGBUS;
387   sigemptyset(&just_SIGBUS);
388   sigaddset(&just_SIGBUS, SIGBUS);
389   ASSERT_EQ(0, sigprocmask(SIG_BLOCK, &just_SIGBUS, nullptr));
390 
391   posix_spawnattr_t sa;
392   ASSERT_EQ(0, posix_spawnattr_init(&sa));
393 
394   // Ask for only SIGALRM to be blocked in the child...
395   sigset_t just_SIGALRM;
396   sigemptyset(&just_SIGALRM);
397   sigaddset(&just_SIGALRM, SIGALRM);
398   ASSERT_EQ(0, posix_spawnattr_setsigmask(&sa, &just_SIGALRM));
399   ASSERT_EQ(0, posix_spawnattr_setflags(&sa, POSIX_SPAWN_SETSIGMASK));
400 
401   // Check that's what happens...
402   ProcStatus ps = {};
403   GetChildStatus(&sa, &ps);
404 
405   // TIMER_SIGNAL should also be blocked.
406   uint64_t expected_blocked = 0;
407   SignalSetAdd(&expected_blocked, SIGALRM);
408   SignalSetAdd(&expected_blocked, BIONIC_SIGNAL_POSIX_TIMERS);
409   EXPECT_EQ(expected_blocked, ps.sigblk);
410 
411   uint64_t expected_ignored = 0;
412   SignalSetAdd(&expected_ignored, BIONIC_SIGNAL_ART_PROFILER);
413   EXPECT_EQ(expected_ignored, ps.sigign);
414 
415   ASSERT_EQ(0, posix_spawnattr_destroy(&sa));
416 #endif
417 }
418 
TEST(spawn,posix_spawn_POSIX_SPAWN_SETSIGDEF)419 TEST(spawn, posix_spawn_POSIX_SPAWN_SETSIGDEF) {
420 #if defined(__GLIBC__)
421   GTEST_SKIP() << "glibc doesn't ignore the same signals.";
422 #else
423   // Ignore SIGALRM and SIGCONT in the parent...
424   ASSERT_NE(SIG_ERR, signal(SIGALRM, SIG_IGN));
425   ASSERT_NE(SIG_ERR, signal(SIGCONT, SIG_IGN));
426 
427   posix_spawnattr_t sa;
428   ASSERT_EQ(0, posix_spawnattr_init(&sa));
429 
430   // Ask for SIGALRM to be defaulted in the child...
431   sigset_t just_SIGALRM;
432   sigemptyset(&just_SIGALRM);
433   sigaddset(&just_SIGALRM, SIGALRM);
434 
435   ASSERT_EQ(0, posix_spawnattr_setsigdefault(&sa, &just_SIGALRM));
436   ASSERT_EQ(0, posix_spawnattr_setflags(&sa, POSIX_SPAWN_SETSIGDEF));
437 
438   // Check that's what happens...
439   ProcStatus ps = {};
440   GetChildStatus(&sa, &ps);
441 
442   // TIMER_SIGNAL should be blocked.
443   uint64_t expected_blocked = 0;
444   SignalSetAdd(&expected_blocked, BIONIC_SIGNAL_POSIX_TIMERS);
445   EXPECT_EQ(expected_blocked, ps.sigblk);
446 
447   uint64_t expected_ignored = 0;
448   SignalSetAdd(&expected_ignored, SIGCONT);
449   SignalSetAdd(&expected_ignored, BIONIC_SIGNAL_ART_PROFILER);
450   EXPECT_EQ(expected_ignored, ps.sigign);
451 
452   ASSERT_EQ(0, posix_spawnattr_destroy(&sa));
453 #endif
454 }
455 
TEST(spawn,signal_stress)456 TEST(spawn, signal_stress) {
457   // Ensure that posix_spawn doesn't restore the caller's signal mask in the
458   // child without first defaulting any caught signals (http://b/68707996).
459   static pid_t parent = getpid();
460 
461   setpgid(0, 0);
462   signal(SIGRTMIN, SIG_IGN);
463 
464   pid_t pid = fork();
465   ASSERT_NE(-1, pid);
466 
467   if (pid == 0) {
468     for (size_t i = 0; i < 1024; ++i) {
469       kill(0, SIGRTMIN);
470       usleep(10);
471     }
472     _exit(99);
473   }
474 
475   // We test both with and without attributes, because they used to be
476   // different codepaths. We also test with an empty `sigdefault` set.
477   posix_spawnattr_t attr1;
478   posix_spawnattr_init(&attr1);
479 
480   sigset_t empty_mask = {};
481   posix_spawnattr_t attr2;
482   posix_spawnattr_init(&attr2);
483   posix_spawnattr_setflags(&attr2, POSIX_SPAWN_SETSIGDEF);
484   posix_spawnattr_setsigdefault(&attr2, &empty_mask);
485 
486   posix_spawnattr_t* attrs[] = { nullptr, &attr1, &attr2 };
487 
488   // We use a real-time signal because that's a tricky case for LP32
489   // because our sigset_t was too small.
490   ScopedSignalHandler ssh(SIGRTMIN, [](int) { ASSERT_EQ(getpid(), parent); });
491 
492   const size_t pid_count = 128;
493   pid_t spawned_pids[pid_count];
494 
495   ExecTestHelper eth;
496   eth.SetArgs({"true", nullptr});
497   for (size_t i = 0; i < pid_count; ++i) {
498     pid_t spawned_pid;
499     ASSERT_EQ(0, posix_spawn(&spawned_pid, "true", nullptr, attrs[i % 3], eth.GetArgs(), nullptr));
500     spawned_pids[i] = spawned_pid;
501   }
502 
503   for (pid_t spawned_pid : spawned_pids) {
504     ASSERT_EQ(spawned_pid, TEMP_FAILURE_RETRY(waitpid(spawned_pid, nullptr, 0)));
505   }
506 
507   AssertChildExited(pid, 99);
508 }
509