1 #include <darwintest.h>
2 #include <signal.h>
3 #include <spawn.h>
4 #include <unistd.h>
5 #include <stdlib.h>
6 #include <sys/wait.h>
7 #include <libproc.h>
8 #include <sys/reason.h>
9 #include <TargetConditionals.h>
10
11 T_GLOBAL_META(
12 T_META_NAMESPACE("xnu.spawn"),
13 T_META_RADAR_COMPONENT_NAME("xnu"),
14 T_META_RADAR_COMPONENT_VERSION("spawn"),
15 T_META_ENABLED(TARGET_OS_OSX));
16
17 static void
__run_cmd(const char * cmd_prefix,const char * filename,const char * error)18 __run_cmd(const char *cmd_prefix, const char *filename, const char *error)
19 {
20 char cmd[PATH_MAX];
21
22 strlcpy(cmd, cmd_prefix, sizeof(cmd));
23 strlcat(cmd, filename, sizeof(cmd));
24
25 FILE *file = popen(cmd, "r");
26 T_QUIET; T_WITH_ERRNO; T_ASSERT_NOTNULL(file, "%s (cmd = %s)", error, cmd);
27 pclose(file);
28 }
29
30 static void
__spawn_exec(const char * args[],short flags)31 __spawn_exec(const char *args[], short flags)
32 {
33 posix_spawnattr_t attr;
34 int error;
35
36 error = posix_spawnattr_init(&attr);
37 T_QUIET; T_ASSERT_POSIX_SUCCESS(error, "spawn attributes initialized");
38
39 error = posix_spawnattr_setflags(&attr, POSIX_SPAWN_SETEXEC | flags);
40 T_QUIET; T_ASSERT_POSIX_SUCCESS(error, "spawn attributes flags set");
41
42 posix_spawnp(NULL, args[0], NULL, &attr, args, NULL);
43 }
44
45 static void
invalid_code_signature_helper()46 invalid_code_signature_helper()
47 {
48 char filename[PATH_MAX];
49 sprintf(filename, "/tmp/echo-test-%ld", random());
50 T_LOG("temporary file created: %s", filename);
51
52 __run_cmd("cp /bin/echo ", filename, "create a temporary copy");
53 __run_cmd("codesign --force --sign - --team-identifier 0 ", filename, "codesign the temporary copy with an invalid team ID");
54
55 /* Exec into the modified binary */
56 const char* args[] = { filename, NULL };
57 __spawn_exec(args, 0);
58 }
59
60 static void
bad_spawnattr_helper()61 bad_spawnattr_helper()
62 {
63 const char* args[] = { "/bin/echo", NULL};
64 int error;
65
66 error = setsid();
67 T_QUIET; T_ASSERT_POSIX_SUCCESS(error, "set SID before exec");
68
69 __spawn_exec(args, POSIX_SPAWN_SETSID);
70 }
71
72 static void
setup_child_and_wait_for_exit(void (* do_exec)(void),uint64_t expected_reason_namespace,uint64_t expected_reason_code)73 setup_child_and_wait_for_exit(
74 void (*do_exec)(void),
75 uint64_t expected_reason_namespace,
76 uint64_t expected_reason_code)
77 {
78 pid_t child = fork();
79 if (child > 0) {
80 int status, ret;
81 struct proc_exitreasonbasicinfo exit_reason;
82
83 sleep(1);
84
85 ret = proc_pidinfo(child, PROC_PIDEXITREASONBASICINFO, 1, &exit_reason, PROC_PIDEXITREASONBASICINFOSIZE);
86 T_QUIET; T_ASSERT_EQ(ret, PROC_PIDEXITREASONBASICINFOSIZE, "retrive basic exit reason info");
87
88 waitpid(child, &status, 0);
89 T_QUIET; T_EXPECT_FALSE(WIFEXITED(status), "process did not exit normally");
90 T_QUIET; T_EXPECT_TRUE(WIFSIGNALED(status), "process was terminated because of a signal");
91 T_EXPECT_EQ(WTERMSIG(status), SIGKILL, "process was SIGKILLed");
92
93 T_EXPECT_EQ(exit_reason.beri_namespace, expected_reason_namespace, "killed with reason EXEC");
94 T_EXPECT_EQ(exit_reason.beri_code, expected_reason_code, "reason code is %d", expected_reason_code);
95 } else {
96 do_exec();
97 T_FAIL("Shouldn't reach here!");
98 }
99 }
100
101 T_DECL(spawn_exec_double_set_sid, "exec fails upon trying to set SID twice")
102 {
103 setup_child_and_wait_for_exit(bad_spawnattr_helper, OS_REASON_EXEC, EXEC_EXIT_REASON_BAD_PSATTR);
104 }
105
106 T_DECL(spawn_exec_invalid_cs, "exec fails due to invalid code signature")
107 {
108 #if defined(__arm64__)
109 setup_child_and_wait_for_exit(invalid_code_signature_helper, OS_REASON_EXEC, EXEC_EXIT_REASON_SECURITY_POLICY);
110 #else /* __arm64__ */
111 setup_child_and_wait_for_exit(invalid_code_signature_helper, OS_REASON_CODESIGNING, CODESIGNING_EXIT_REASON_TASKGATED_INVALID_SIG);
112 #endif /* __arm64__ */
113 }
114