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