xref: /netbsd-src/external/bsd/kyua-testers/dist/run_test.c (revision bdc22b2e01993381dcefeff2bc9b56ca75a4235c)
1 // Copyright 2012 Google Inc.
2 // All rights reserved.
3 //
4 // Redistribution and use in source and binary forms, with or without
5 // modification, are permitted provided that the following conditions are
6 // met:
7 //
8 // * Redistributions of source code must retain the above copyright
9 //   notice, this list of conditions and the following disclaimer.
10 // * Redistributions in binary form must reproduce the above copyright
11 //   notice, this list of conditions and the following disclaimer in the
12 //   documentation and/or other materials provided with the distribution.
13 // * Neither the name of Google Inc. nor the names of its contributors
14 //   may be used to endorse or promote products derived from this software
15 //   without specific prior written permission.
16 //
17 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 
29 #if defined(HAVE_CONFIG_H)
30 #   include "config.h"
31 #endif
32 
33 #include "run.h"
34 
35 #include <sys/resource.h>
36 #include <sys/stat.h>
37 #include <sys/wait.h>
38 
39 #include <err.h>
40 #include <errno.h>
41 #include <pwd.h>
42 #include <signal.h>
43 #include <stdbool.h>
44 #include <stdio.h>
45 #include <stdlib.h>
46 #include <unistd.h>
47 
48 #include <atf-c.h>
49 
50 #include "defs.h"
51 #include "env.h"
52 #include "error.h"
53 #include "fs.h"
54 
55 
56 /// Evalutes an expression and ensures it does not return an error.
57 ///
58 /// \param expr A expression that must evaluate to kyua_error_t.
59 #define RE(expr) ATF_REQUIRE(!kyua_error_is_set(expr))
60 
61 
62 static void check_env(const void* KYUA_DEFS_UNUSED_PARAM(cookie))
63     KYUA_DEFS_NORETURN;
64 
65 
66 /// Subprocess that validates the cleanliness of the environment.
67 ///
68 /// \param unused_cookie NULL.
69 ///
70 /// \post Exits with success if the environment is clean; failure otherwise.
71 static void
72 check_env(const void* KYUA_DEFS_UNUSED_PARAM(cookie))
73 {
74     bool failed = false;
75 
76     const char* empty[] = { "LANG", "LC_ALL", "LC_COLLATE", "LC_CTYPE",
77                             "LC_MESSAGES", "LC_MONETARY", "LC_NUMERIC",
78                             "LC_TIME", NULL };
79     const char** iter;
80     for (iter = empty; *iter != NULL; ++iter) {
81         if (getenv(*iter) != NULL) {
82             failed = true;
83             printf("%s was not unset\n", *iter);
84         }
85     }
86 
87     if (strcmp(getenv("HOME"), ".") != 0) {
88         failed = true;
89         printf("HOME was not set to .\n");
90     }
91     if (strcmp(getenv("TZ"), "UTC") != 0) {
92         failed = true;
93         printf("TZ was not set to UTC\n");
94     }
95     if (strcmp(getenv("LEAVE_ME_ALONE"), "kill-some-day") != 0) {
96         failed = true;
97         printf("LEAVE_ME_ALONE was modified while it should not have been\n");
98     }
99 
100     exit(failed ? EXIT_FAILURE : EXIT_SUCCESS);
101 }
102 
103 
104 static void check_process_group(const void* KYUA_DEFS_UNUSED_PARAM(cookie))
105     KYUA_DEFS_NORETURN;
106 
107 
108 /// Subprocess that validates that it has become the leader of a process group.
109 ///
110 /// \param unused_cookie NULL.
111 ///
112 /// \post Exits with success if the process lives in its own process group;
113 /// failure otherwise.
114 static void
115 check_process_group(const void* KYUA_DEFS_UNUSED_PARAM(cookie))
116 {
117     exit(getpgid(getpid()) == getpid() ? EXIT_SUCCESS : EXIT_FAILURE);
118 }
119 
120 
121 static void check_signals(const void* KYUA_DEFS_UNUSED_PARAM(cookie))
122     KYUA_DEFS_NORETURN;
123 
124 
125 /// Subprocess that validates that signals have been reset to their defaults.
126 ///
127 /// \param unused_cookie NULL.
128 ///
129 /// \post Exits with success if the process has its signals reset to their
130 /// default values; failure otherwise.
131 static void
132 check_signals(const void* KYUA_DEFS_UNUSED_PARAM(cookie))
133 {
134     int signo;
135     for (signo = 1; signo <= LAST_SIGNO; signo++) {
136         if (signo == SIGKILL || signo == SIGSTOP) {
137             // Don't attempt to check immutable signals, as this results in
138             // an unconditional error in some systems.  E.g. Mac OS X 10.8
139             // reports 'Invalid argument' when querying SIGKILL.
140             continue;
141         }
142 
143         struct sigaction old_sa;
144         if (sigaction(signo, NULL, &old_sa) == -1) {
145             err(EXIT_FAILURE, "Failed to query signal information for %d",
146                 signo);
147         }
148         if (old_sa.sa_handler != SIG_DFL) {
149             errx(EXIT_FAILURE, "Signal %d not reset to its default handler",
150                  signo);
151         }
152         printf("Signal %d has its default handler set\n", signo);
153     }
154 
155     exit(EXIT_SUCCESS);
156 }
157 
158 
159 static void check_umask(const void* KYUA_DEFS_UNUSED_PARAM(cookie))
160     KYUA_DEFS_NORETURN;
161 
162 
163 /// Subprocess that validates that the umask has been reset.
164 ///
165 /// \param unused_cookie NULL.
166 ///
167 /// \post Exits with success if the umask matches the expected value; failure
168 /// otherwise.
169 static void
170 check_umask(const void* KYUA_DEFS_UNUSED_PARAM(cookie))
171 {
172     exit(umask(0) == 0022 ? EXIT_SUCCESS : EXIT_FAILURE);
173 }
174 
175 
176 static void check_work_directory(const void* cookie) KYUA_DEFS_NORETURN;
177 
178 
179 /// Subprocess that validates that the umask has been reset.
180 ///
181 /// \param cookie The name of a file to expect in the current directory.
182 ///
183 /// \post Exits with success if the umask matches the expected value; failure
184 /// otherwise.
185 static void
186 check_work_directory(const void* cookie)
187 {
188     const char* exp_file = (const char*)cookie;
189     exit(atf_utils_file_exists(exp_file) ? EXIT_SUCCESS : EXIT_FAILURE);
190 }
191 
192 
193 static void check_uid_not_root(const void* KYUA_DEFS_UNUSED_PARAM(cookie))
194     KYUA_DEFS_NORETURN;
195 
196 
197 /// Subprocess that validates that the UID is not root.
198 ///
199 /// \param unused_cookie NULL.
200 ///
201 /// \post Exits with success if the UID is not root; failure otherwise.
202 static void
203 check_uid_not_root(const void* KYUA_DEFS_UNUSED_PARAM(cookie))
204 {
205     exit(getuid() != 0 ? EXIT_SUCCESS : EXIT_FAILURE);
206 }
207 
208 
209 static void check_gid_not_root(const void* KYUA_DEFS_UNUSED_PARAM(cookie))
210     KYUA_DEFS_NORETURN;
211 
212 
213 /// Subprocess that validates that the GID is not root.
214 ///
215 /// \param unused_cookie NULL.
216 ///
217 /// \post Exits with success if the GID is not root; failure otherwise.
218 static void
219 check_gid_not_root(const void* KYUA_DEFS_UNUSED_PARAM(cookie))
220 {
221     exit(getgid() != 0 ? EXIT_SUCCESS : EXIT_FAILURE);
222 }
223 
224 
225 static void check_not_root(const void* KYUA_DEFS_UNUSED_PARAM(cookie))
226     KYUA_DEFS_NORETURN;
227 
228 
229 /// Subprocess that validates that the UID and GID are not root.
230 ///
231 /// \param unused_cookie NULL.
232 ///
233 /// \post Exits with success if the UID and GID are not root; failure otherwise.
234 static void
235 check_not_root(const void* KYUA_DEFS_UNUSED_PARAM(cookie))
236 {
237     exit(getuid() != 0 && getgid() != 0 ? EXIT_SUCCESS : EXIT_FAILURE);
238 }
239 
240 
241 /// Uses kyua_fork, kyua_exec and kyua_wait to execute a subprocess.
242 ///
243 /// \param program Path to the program to run.
244 /// \param args Arguments to the program.
245 /// \param [out] exitstatus The exit status of the subprocess, if it exits
246 ///     successfully without timing out nor receiving a signal.
247 ///
248 /// \return Returns the error code of kyua_run_wait (which should have the
249 ///     error representation of the exec call in the subprocess).
250 static kyua_error_t
251 exec_check(const char* program, const char* const* args, int* exitstatus)
252 {
253     kyua_run_params_t run_params;
254     kyua_run_params_init(&run_params);
255 
256     pid_t pid;
257     kyua_error_t error = kyua_run_fork(&run_params, &pid);
258     if (!kyua_error_is_set(error) && pid == 0)
259         kyua_run_exec(program, args);
260     ATF_REQUIRE(!kyua_error_is_set(error));
261     int status; bool timed_out;
262     error = kyua_run_wait(pid, &status, &timed_out);
263     if (!kyua_error_is_set(error)) {
264         ATF_REQUIRE(!timed_out);
265         ATF_REQUIRE_MSG(WIFEXITED(status),
266                         "Subprocess expected to exit successfully");
267         *exitstatus = WEXITSTATUS(status);
268     }
269     return error;
270 }
271 
272 
273 /// Uses kyua_fork and kyua_wait to spawn a subprocess.
274 ///
275 /// \param run_params The parameters to configure the subprocess.  Can be NULL
276 ///     to indicate to use the default set of parameters.
277 /// \param hook Any of the check_* functions provided in this module.
278 /// \param cookie The data to pass to the hook.
279 ///
280 /// \return True if the subprocess exits successfully; false otherwise.
281 static bool
282 fork_check(const kyua_run_params_t* run_params,
283            void (*hook)(const void*), const void* cookie)
284 {
285     kyua_run_params_t default_run_params;
286     if (run_params == NULL) {
287         kyua_run_params_init(&default_run_params);
288         run_params = &default_run_params;
289     }
290 
291     pid_t pid;
292     kyua_error_t error = kyua_run_fork(run_params, &pid);
293     if (!kyua_error_is_set(error) && pid == 0)
294         hook(cookie);
295     ATF_REQUIRE(!kyua_error_is_set(error));
296     int status; bool timed_out;
297     error = kyua_run_wait(pid, &status, &timed_out);
298     if (kyua_error_is_set(error))
299         atf_tc_fail("wait failed; unexpected problem during exec?");
300     ATF_REQUIRE(!timed_out);
301     return WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS;
302 }
303 
304 
305 ATF_TC_WITHOUT_HEAD(run_params_init__defaults);
306 ATF_TC_BODY(run_params_init__defaults, tc)
307 {
308     kyua_run_params_t run_params;
309     kyua_run_params_init(&run_params);
310 
311     ATF_REQUIRE_EQ(60, run_params.timeout_seconds);
312     ATF_REQUIRE_EQ(getuid(), run_params.unprivileged_user);
313     ATF_REQUIRE_EQ(getgid(), run_params.unprivileged_group);
314     ATF_REQUIRE_STREQ(".", run_params.work_directory);
315 }
316 
317 
318 ATF_TC_WITHOUT_HEAD(fork_exec_wait__ok);
319 ATF_TC_BODY(fork_exec_wait__ok, tc)
320 {
321     const char* const args[] = {"sh", "-c", "exit 42", NULL};
322     int exitstatus = -1;  // Shut up GCC warning.
323     const kyua_error_t error = exec_check("/bin/sh", args, &exitstatus);
324     ATF_REQUIRE(!kyua_error_is_set(error));
325     ATF_REQUIRE_EQ(42, exitstatus);
326 }
327 
328 
329 ATF_TC(fork_exec_wait__eacces);
330 ATF_TC_HEAD(fork_exec_wait__eacces, tc)
331 {
332     atf_tc_set_md_var(tc, "require.user", "unprivileged");
333 }
334 ATF_TC_BODY(fork_exec_wait__eacces, tc)
335 {
336     ATF_REQUIRE(mkdir("dir", 0000) != -1);
337 
338     const char* const args[] = {"foo", NULL};
339     int unused_exitstatus;
340     const kyua_error_t error = exec_check("./dir/foo", args,
341                                           &unused_exitstatus);
342     ATF_REQUIRE(kyua_error_is_set(error));
343     ATF_REQUIRE(kyua_error_is_type(error, "libc"));
344     ATF_REQUIRE_EQ(EACCES, kyua_libc_error_errno(error));
345 }
346 
347 
348 ATF_TC_WITHOUT_HEAD(fork_exec_wait__enoent);
349 ATF_TC_BODY(fork_exec_wait__enoent, tc)
350 {
351     const char* const args[] = {"foo", NULL};
352     int unused_exitstatus;
353     const kyua_error_t error = exec_check("./foo", args, &unused_exitstatus);
354     ATF_REQUIRE(kyua_error_is_set(error));
355     ATF_REQUIRE(kyua_error_is_type(error, "libc"));
356     ATF_REQUIRE_EQ(ENOENT, kyua_libc_error_errno(error));
357 }
358 
359 
360 ATF_TC_WITHOUT_HEAD(fork_wait__core_size);
361 ATF_TC_BODY(fork_wait__core_size, tc)
362 {
363     struct rlimit rl;
364     rl.rlim_cur = 0;
365     rl.rlim_max = RLIM_INFINITY;
366     if (setrlimit(RLIMIT_CORE, &rl) == -1)
367         atf_tc_skip("Failed to lower the core size limit");
368 
369     kyua_run_params_t run_params;
370     kyua_run_params_init(&run_params);
371 
372     pid_t pid;
373     kyua_error_t error = kyua_run_fork(&run_params, &pid);
374     if (!kyua_error_is_set(error) && pid == 0)
375         abort();
376 
377     ATF_REQUIRE(!kyua_error_is_set(error));
378     int status; bool timed_out;
379     error = kyua_run_wait(pid, &status, &timed_out);
380     if (kyua_error_is_set(error))
381         atf_tc_fail("wait failed; unexpected problem during exec?");
382 
383     ATF_REQUIRE(!timed_out);
384     ATF_REQUIRE(WIFSIGNALED(status));
385     ATF_REQUIRE_MSG(WCOREDUMP(status), "Core not dumped as expected");
386 }
387 
388 
389 ATF_TC_WITHOUT_HEAD(fork_wait__env);
390 ATF_TC_BODY(fork_wait__env, tc)
391 {
392     kyua_env_set("HOME", "/non-existent/directory");
393     kyua_env_set("LANG", "C");
394     kyua_env_set("LC_ALL", "C");
395     kyua_env_set("LC_COLLATE", "C");
396     kyua_env_set("LC_CTYPE", "C");
397     kyua_env_set("LC_MESSAGES", "C");
398     kyua_env_set("LC_MONETARY", "C");
399     kyua_env_set("LC_NUMERIC", "C");
400     kyua_env_set("LC_TIME", "C");
401     kyua_env_set("LEAVE_ME_ALONE", "kill-some-day");
402     kyua_env_set("TZ", "EST+5");
403 
404     ATF_REQUIRE_MSG(fork_check(NULL, check_env, NULL),
405                     "Unclean environment in subprocess");
406 }
407 
408 
409 ATF_TC_WITHOUT_HEAD(fork_wait__process_group);
410 ATF_TC_BODY(fork_wait__process_group, tc)
411 {
412     ATF_REQUIRE_MSG(fork_check(NULL, check_process_group, NULL),
413                     "Subprocess not in its own process group");
414 }
415 
416 
417 ATF_TC_WITHOUT_HEAD(fork_wait__signals);
418 ATF_TC_BODY(fork_wait__signals, tc)
419 {
420     ATF_REQUIRE_MSG(LAST_SIGNO > 10, "LAST_SIGNO as detected by configure is "
421                     "suspiciously low");
422 
423     int signo;
424     for (signo = 1; signo <= LAST_SIGNO; signo++) {
425         if (signo == SIGKILL || signo == SIGSTOP) {
426             // Ignore immutable signals.
427             continue;
428         }
429         if (signo == SIGCHLD) {
430             // If we were to reset SIGCHLD to SIG_IGN (which is different than
431             // not touching the signal at all, leaving it at its default value),
432             // our child process will not become a zombie and the call to
433             // kyua_run_wait will fail.  Avoid this.
434             continue;
435         }
436 
437         struct sigaction sa;
438         sa.sa_handler = SIG_IGN;
439         sigemptyset(&sa.sa_mask);
440         sa.sa_flags = 0;
441         printf("Ignoring signal %d\n", signo);
442         ATF_REQUIRE(sigaction(signo, &sa, NULL) != -1);
443     }
444 
445     ATF_REQUIRE_MSG(fork_check(NULL, check_signals, NULL),
446                     "Signals not reset to their default state");
447 }
448 
449 
450 ATF_TC_WITHOUT_HEAD(fork_wait__timeout);
451 ATF_TC_BODY(fork_wait__timeout, tc)
452 {
453     kyua_run_params_t run_params;
454     kyua_run_params_init(&run_params);
455     run_params.timeout_seconds = 1;
456 
457     pid_t pid;
458     kyua_error_t error = kyua_run_fork(&run_params, &pid);
459     if (!kyua_error_is_set(error) && pid == 0) {
460         sigset_t mask;
461         sigemptyset(&mask);
462         for (;;)
463             sigsuspend(&mask);
464     }
465     ATF_REQUIRE(!kyua_error_is_set(error));
466     int status; bool timed_out;
467     kyua_run_wait(pid, &status, &timed_out);
468     ATF_REQUIRE(timed_out);
469     ATF_REQUIRE(WIFSIGNALED(status));
470     ATF_REQUIRE_EQ(SIGKILL, WTERMSIG(status));
471 }
472 
473 
474 ATF_TC_WITHOUT_HEAD(fork_wait__umask);
475 ATF_TC_BODY(fork_wait__umask, tc)
476 {
477     (void)umask(0222);
478     ATF_REQUIRE_MSG(fork_check(NULL, check_umask, NULL),
479                     "Subprocess does not have the predetermined 0022 umask");
480 }
481 
482 
483 ATF_TC(fork_wait__unprivileged_user);
484 ATF_TC_HEAD(fork_wait__unprivileged_user, tc)
485 {
486     atf_tc_set_md_var(tc, "require.config", "unprivileged-user");
487     atf_tc_set_md_var(tc, "require.user", "root");
488 }
489 ATF_TC_BODY(fork_wait__unprivileged_user, tc)
490 {
491     const struct passwd* pw = getpwnam(atf_tc_get_config_var(
492         tc, "unprivileged-user"));
493     ATF_REQUIRE_MSG(pw != NULL, "Cannot find unprivileged user");
494 
495     kyua_run_params_t run_params;
496     kyua_run_params_init(&run_params);
497     run_params.unprivileged_user = pw->pw_uid;
498 
499     ATF_REQUIRE_MSG(fork_check(&run_params, check_uid_not_root, NULL),
500                     "Subprocess is still running with UID set to root");
501 }
502 
503 
504 ATF_TC(fork_wait__unprivileged_group);
505 ATF_TC_HEAD(fork_wait__unprivileged_group, tc)
506 {
507     atf_tc_set_md_var(tc, "require.config", "unprivileged-user");
508     atf_tc_set_md_var(tc, "require.user", "root");
509 }
510 ATF_TC_BODY(fork_wait__unprivileged_group, tc)
511 {
512     const struct passwd* pw = getpwnam(atf_tc_get_config_var(
513         tc, "unprivileged-user"));
514     ATF_REQUIRE_MSG(pw != NULL, "Cannot find unprivileged user");
515 
516     kyua_run_params_t run_params;
517     kyua_run_params_init(&run_params);
518     run_params.unprivileged_group = pw->pw_gid;
519 
520     ATF_REQUIRE_MSG(fork_check(&run_params, check_gid_not_root, NULL),
521                     "Subprocess is still running with GID set to root");
522 }
523 
524 
525 ATF_TC(fork_wait__unprivileged_both);
526 ATF_TC_HEAD(fork_wait__unprivileged_both, tc)
527 {
528     atf_tc_set_md_var(tc, "require.config", "unprivileged-user");
529     atf_tc_set_md_var(tc, "require.user", "root");
530 }
531 ATF_TC_BODY(fork_wait__unprivileged_both, tc)
532 {
533     const struct passwd* pw = getpwnam(atf_tc_get_config_var(
534         tc, "unprivileged-user"));
535     ATF_REQUIRE_MSG(pw != NULL, "Cannot find unprivileged user");
536 
537     kyua_run_params_t run_params;
538     kyua_run_params_init(&run_params);
539     run_params.unprivileged_user = pw->pw_uid;
540     run_params.unprivileged_group = pw->pw_gid;
541 
542     ATF_REQUIRE_MSG(fork_check(&run_params, check_not_root, NULL),
543                     "Subprocess is still running with root privileges");
544 }
545 
546 
547 ATF_TC_WITHOUT_HEAD(fork_wait__work_directory);
548 ATF_TC_BODY(fork_wait__work_directory, tc)
549 {
550     ATF_REQUIRE(mkdir("the-work-directory", 0755) != -1);
551     atf_utils_create_file("the-work-directory/data-file", "%s", "");
552 
553     kyua_run_params_t run_params;
554     kyua_run_params_init(&run_params);
555     run_params.work_directory = "./the-work-directory";
556     ATF_REQUIRE_MSG(fork_check(&run_params, check_work_directory, "data-file"),
557                     "Subprocess not in its own process group");
558 }
559 
560 
561 ATF_TC_WITHOUT_HEAD(work_directory__builtin_tmpdir);
562 ATF_TC_BODY(work_directory__builtin_tmpdir, tc)
563 {
564     char* tmpdir;
565     RE(kyua_fs_make_absolute("worktest", &tmpdir));
566     ATF_REQUIRE(mkdir(tmpdir, 0755) != -1);
567     RE(kyua_env_unset("TMPDIR"));
568     kyua_run_tmpdir = tmpdir;
569 
570     char* work_directory;
571     RE(kyua_run_work_directory_enter("template.XXXXXX", getuid(), getgid(),
572                                      &work_directory));
573 
574     {
575         char* template_test;
576         RE(kyua_fs_concat(&template_test, atf_tc_get_config_var(tc, "srcdir"),
577                           "worktest", "template.XXXXXX", NULL));
578         ATF_REQUIRE(access(template_test, X_OK) == -1);
579         free(template_test);
580     }
581 
582     ATF_REQUIRE(access(work_directory, X_OK) != -1);
583 
584     ATF_REQUIRE(rmdir(tmpdir) == -1);  // Not yet empty.
585     RE(kyua_run_work_directory_leave(&work_directory));
586     ATF_REQUIRE(rmdir(tmpdir) != -1);
587     free(tmpdir);
588 }
589 
590 
591 ATF_TC_WITHOUT_HEAD(work_directory__env_tmpdir);
592 ATF_TC_BODY(work_directory__env_tmpdir, tc)
593 {
594     char* tmpdir;
595     RE(kyua_fs_make_absolute("worktest", &tmpdir));
596     ATF_REQUIRE(mkdir(tmpdir, 0755) != -1);
597     RE(kyua_env_set("TMPDIR", tmpdir));
598 
599     char* work_directory;
600     RE(kyua_run_work_directory_enter("template.XXXXXX", getuid(), getgid(),
601                                      &work_directory));
602 
603     {
604         char* template_test;
605         RE(kyua_fs_concat(&template_test, atf_tc_get_config_var(tc, "srcdir"),
606                           "worktest", "template.XXXXXX", NULL));
607         ATF_REQUIRE(access(template_test, X_OK) == -1);
608         free(template_test);
609     }
610 
611     ATF_REQUIRE(access(work_directory, X_OK) != -1);
612 
613     ATF_REQUIRE(rmdir(tmpdir) == -1);  // Not yet empty.
614     RE(kyua_run_work_directory_leave(&work_directory));
615     ATF_REQUIRE(rmdir(tmpdir) != -1);
616     free(tmpdir);
617 }
618 
619 
620 ATF_TC(work_directory__permissions);
621 ATF_TC_HEAD(work_directory__permissions, tc)
622 {
623     atf_tc_set_md_var(tc, "require.config", "unprivileged-user");
624     atf_tc_set_md_var(tc, "require.user", "root");
625 }
626 ATF_TC_BODY(work_directory__permissions, tc)
627 {
628     const struct passwd* pw = getpwnam(atf_tc_get_config_var(
629         tc, "unprivileged-user"));
630 
631     printf("%d %d %d %d\n", getuid(), getgid(), pw->pw_uid, pw->pw_gid);
632 
633     char* work_directory;
634     RE(kyua_run_work_directory_enter("template.XXXXXX", pw->pw_uid, pw->pw_gid,
635                                      &work_directory));
636 
637     struct stat sb;
638     ATF_REQUIRE(stat(work_directory, &sb) != -1);
639     ATF_REQUIRE_EQ(pw->pw_uid, sb.st_uid);
640     ATF_REQUIRE_EQ(pw->pw_gid, sb.st_gid);
641 
642     RE(kyua_run_work_directory_leave(&work_directory));
643 }
644 
645 
646 ATF_TC(work_directory__mkdtemp_error);
647 ATF_TC_HEAD(work_directory__mkdtemp_error, tc)
648 {
649     atf_tc_set_md_var(tc, "require.user", "unprivileged");
650 }
651 ATF_TC_BODY(work_directory__mkdtemp_error, tc)
652 {
653     char* tmpdir;
654     RE(kyua_fs_make_absolute("worktest", &tmpdir));
655     ATF_REQUIRE(mkdir(tmpdir, 0555) != -1);
656     RE(kyua_env_set("TMPDIR", tmpdir));
657 
658     char* work_directory;
659     const kyua_error_t error = kyua_run_work_directory_enter(
660         "template.XXXXXX", getuid(), getgid(), &work_directory);
661     ATF_REQUIRE(kyua_error_is_set(error));
662     ATF_REQUIRE(kyua_error_is_type(error, "libc"));
663     ATF_REQUIRE_EQ(EACCES, kyua_libc_error_errno(error));
664     kyua_error_free(error);
665 
666     ATF_REQUIRE(rmdir(tmpdir) != -1);  // Empty; subdirectory not created.
667     free(tmpdir);
668 }
669 
670 
671 ATF_TC(work_directory__permissions_error);
672 ATF_TC_HEAD(work_directory__permissions_error, tc)
673 {
674     atf_tc_set_md_var(tc, "require.user", "unprivileged");
675 }
676 ATF_TC_BODY(work_directory__permissions_error, tc)
677 {
678     char* tmpdir;
679     RE(kyua_fs_make_absolute("worktest", &tmpdir));
680     ATF_REQUIRE(mkdir(tmpdir, 0755) != -1);
681     RE(kyua_env_set("TMPDIR", tmpdir));
682 
683     char* work_directory;
684     const kyua_error_t error = kyua_run_work_directory_enter(
685         "template.XXXXXX", getuid() + 1, getgid(), &work_directory);
686     ATF_REQUIRE(kyua_error_is_set(error));
687     ATF_REQUIRE(kyua_error_is_type(error, "libc"));
688     ATF_REQUIRE_EQ(EPERM, kyua_libc_error_errno(error));
689     kyua_error_free(error);
690 
691     ATF_REQUIRE(rmdir(tmpdir) != -1);  // Empty; subdirectory not created.
692     free(tmpdir);
693 }
694 
695 
696 /// Performs a signal delivery test to the work directory handling code.
697 ///
698 /// \param signo The signal to deliver.
699 static void
700 work_directory_signal_check(const int signo)
701 {
702     char* tmpdir;
703     RE(kyua_fs_make_absolute("worktest", &tmpdir));
704     ATF_REQUIRE(mkdir(tmpdir, 0755) != -1);
705     RE(kyua_env_set("TMPDIR", tmpdir));
706 
707     char* work_directory;
708     RE(kyua_run_work_directory_enter("template.XXXXXX", getuid(), getgid(),
709                                      &work_directory));
710 
711     kyua_run_params_t run_params;
712     kyua_run_params_init(&run_params);
713     run_params.work_directory = work_directory;
714 
715     pid_t pid;
716     RE(kyua_run_fork(&run_params, &pid));
717     if (pid == 0) {
718         sleep(run_params.timeout_seconds * 2);
719         abort();
720     }
721 
722     // This should cause the handled installed by the work_directory management
723     // code to terminate the subprocess so that we get a chance to run the
724     // cleanup code ourselves.
725     kill(getpid(), signo);
726 
727     int status; bool timed_out;
728     RE(kyua_run_wait(pid, &status, &timed_out));
729     ATF_REQUIRE(!timed_out);
730     ATF_REQUIRE(WIFSIGNALED(status));
731     ATF_REQUIRE_EQ(SIGKILL, WTERMSIG(status));
732 
733     ATF_REQUIRE(rmdir(tmpdir) == -1);  // Not yet empty.
734     RE(kyua_run_work_directory_leave(&work_directory));
735     ATF_REQUIRE(rmdir(tmpdir) != -1);
736     free(tmpdir);
737 }
738 
739 
740 ATF_TC_WITHOUT_HEAD(work_directory__sighup);
741 ATF_TC_BODY(work_directory__sighup, tc)
742 {
743     work_directory_signal_check(SIGHUP);
744 }
745 
746 
747 ATF_TC_WITHOUT_HEAD(work_directory__sigint);
748 ATF_TC_BODY(work_directory__sigint, tc)
749 {
750     work_directory_signal_check(SIGINT);
751 }
752 
753 
754 ATF_TC_WITHOUT_HEAD(work_directory__sigterm);
755 ATF_TC_BODY(work_directory__sigterm, tc)
756 {
757     work_directory_signal_check(SIGTERM);
758 }
759 
760 
761 ATF_TP_ADD_TCS(tp)
762 {
763     ATF_TP_ADD_TC(tp, run_params_init__defaults);
764 
765     ATF_TP_ADD_TC(tp, fork_exec_wait__ok);
766     ATF_TP_ADD_TC(tp, fork_exec_wait__eacces);
767     ATF_TP_ADD_TC(tp, fork_exec_wait__enoent);
768 
769     ATF_TP_ADD_TC(tp, fork_wait__core_size);
770     ATF_TP_ADD_TC(tp, fork_wait__env);
771     ATF_TP_ADD_TC(tp, fork_wait__process_group);
772     ATF_TP_ADD_TC(tp, fork_wait__signals);
773     ATF_TP_ADD_TC(tp, fork_wait__timeout);
774     ATF_TP_ADD_TC(tp, fork_wait__umask);
775     ATF_TP_ADD_TC(tp, fork_wait__unprivileged_user);
776     ATF_TP_ADD_TC(tp, fork_wait__unprivileged_group);
777     ATF_TP_ADD_TC(tp, fork_wait__unprivileged_both);
778     ATF_TP_ADD_TC(tp, fork_wait__work_directory);
779 
780     ATF_TP_ADD_TC(tp, work_directory__builtin_tmpdir);
781     ATF_TP_ADD_TC(tp, work_directory__env_tmpdir);
782     ATF_TP_ADD_TC(tp, work_directory__permissions);
783     ATF_TP_ADD_TC(tp, work_directory__permissions_error);
784     ATF_TP_ADD_TC(tp, work_directory__mkdtemp_error);
785     ATF_TP_ADD_TC(tp, work_directory__sighup);
786     ATF_TP_ADD_TC(tp, work_directory__sigint);
787     ATF_TP_ADD_TC(tp, work_directory__sigterm);
788 
789     return atf_no_error();
790 }
791