]> git.saurik.com Git - apple/xnu.git/blob - tests/task_create_suid_cred.c
9787918c3f9d40d762b0efbe2c61cc605c4638ce
[apple/xnu.git] / tests / task_create_suid_cred.c
1 #include <mach/mach.h>
2
3 #include <bootstrap.h>
4 #include <darwintest.h>
5 #include <darwintest_multiprocess.h>
6 #include <spawn.h>
7 #include <unistd.h>
8
9 #if defined(UNENTITLED)
10
11 /*
12 * Creating an suid credential should fail without an entitlement.
13 */
14 T_DECL(task_create_suid_cred_unentitled, "task_create_suid_cred (no entitlment)", T_META_ASROOT(true))
15 {
16 kern_return_t ret = KERN_FAILURE;
17 suid_cred_t sc = SUID_CRED_NULL;
18
19 ret = task_create_suid_cred(mach_task_self(), "/usr/bin/id", 0, &sc);
20 T_ASSERT_MACH_ERROR(ret, KERN_NO_ACCESS, "create a new suid cred for id (no entitlement)");
21 }
22
23 #else /* ENTITLED */
24
25 extern char **environ;
26 static const char *server_name = "com.apple.xnu.test.task_create_suid_cred";
27
28 /*
29 * This is a positive test case which spawns /usr/bin/id with a properly created
30 * suid credential and verifies that it correctly produces "euid=0"
31 * Not running as root.
32 */
33 static void
34 test_id_cred(suid_cred_t sc_id)
35 {
36 posix_spawnattr_t attr;
37 posix_spawn_file_actions_t file_actions;
38 pid_t pid = -1;
39 int status = -1;
40 char template[] = "/tmp/suid_cred.XXXXXX";
41 char *path = NULL;
42 FILE *file = NULL;
43 char *line = NULL;
44 size_t linecap = 0;
45 ssize_t linelen = 0;
46 char *id[] = {"/usr/bin/id", NULL};
47 kern_return_t ret = KERN_FAILURE;
48
49 /* Send stdout to a temporary file. */
50 path = mktemp(template);
51 T_QUIET; T_ASSERT_NOTNULL(path, NULL);
52
53 ret = posix_spawn_file_actions_init(&file_actions);
54 T_QUIET; T_ASSERT_POSIX_ZERO(ret, NULL);
55
56 ret = posix_spawn_file_actions_addopen(&file_actions, 1, path,
57 O_WRONLY | O_CREAT | O_TRUNC, 0666);
58 T_QUIET; T_ASSERT_POSIX_ZERO(ret, NULL);
59
60 ret = posix_spawnattr_init(&attr);
61 T_QUIET; T_ASSERT_POSIX_ZERO(ret, NULL);
62 T_QUIET; T_ASSERT_NOTNULL(attr, NULL);
63
64 // Attach the suid cred port
65 ret = posix_spawnattr_setsuidcredport_np(&attr, sc_id);
66 T_QUIET; T_ASSERT_POSIX_ZERO(ret, NULL);
67
68 ret = posix_spawnp(&pid, id[0], &file_actions, &attr, id, environ);
69 T_ASSERT_POSIX_ZERO(ret, "spawn with suid cred");
70
71 ret = posix_spawnattr_destroy(&attr);
72 T_QUIET; T_ASSERT_POSIX_ZERO(ret, NULL);
73
74 ret = posix_spawn_file_actions_destroy(&file_actions);
75 T_QUIET; T_ASSERT_POSIX_ZERO(ret, NULL);
76
77 // Wait for id to finish executing and exit.
78 do {
79 ret = waitpid(pid, &status, 0);
80 } while (ret < 0 && errno == EINTR);
81 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, NULL);
82
83 // Read from the temp file and verify that euid is 0.
84 file = fopen(path, "re");
85 T_QUIET; T_ASSERT_NOTNULL(file, NULL);
86
87 linelen = getline(&line, &linecap, file);
88 T_QUIET; T_ASSERT_GT_LONG(linelen, 0L, NULL);
89
90 T_ASSERT_NOTNULL(strstr(line, "euid=0"), "verify that euid is zero");
91
92 free(line);
93 ret = fclose(file);
94 T_QUIET; T_ASSERT_POSIX_ZERO(ret, NULL);
95
96 ret = unlink(path);
97 T_QUIET; T_ASSERT_POSIX_ZERO(ret, NULL);
98 }
99
100 /*
101 * This is a negative test case which tries to spawn /usr/bin/id with a
102 * previously used credential. It is expected that posix_spawn() fails.
103 * sc_id should have already been used to successfully spawn /usr/bin/id.
104 */
105 static void
106 test_id_cred_reuse(suid_cred_t sc_id)
107 {
108 posix_spawnattr_t attr;
109 char *id[] = {"/usr/bin/id", NULL};
110 kern_return_t ret = KERN_FAILURE;
111
112 ret = posix_spawnattr_init(&attr);
113 T_QUIET; T_ASSERT_POSIX_ZERO(ret, NULL);
114 T_QUIET; T_ASSERT_NOTNULL(attr, NULL);
115
116 // Attach the suid cred port
117 ret = posix_spawnattr_setsuidcredport_np(&attr, sc_id);
118 T_QUIET; T_ASSERT_POSIX_ZERO(ret, NULL);
119
120 ret = posix_spawnp(NULL, id[0], NULL, &attr, id, environ);
121 T_ASSERT_NE(ret, 0, "spawn with used suid cred");
122
123 ret = posix_spawnattr_destroy(&attr);
124 T_QUIET; T_ASSERT_POSIX_ZERO(ret, NULL);
125 }
126
127 /*
128 * This is a negative test case which tries to spawn /usr/bin/id with a
129 * credential for /bin/ls. It is expected that posix_spawn() fails.
130 */
131 static void
132 test_ls_cred(suid_cred_t sc_ls)
133 {
134 posix_spawnattr_t attr;
135 char *id[] = {"/usr/bin/id", NULL};
136 kern_return_t ret = KERN_FAILURE;
137
138 ret = posix_spawnattr_init(&attr);
139 T_QUIET; T_ASSERT_POSIX_ZERO(ret, NULL);
140 T_QUIET; T_ASSERT_NOTNULL(attr, NULL);
141
142 // Attach the suid cred port
143 ret = posix_spawnattr_setsuidcredport_np(&attr, sc_ls);
144 T_QUIET; T_ASSERT_POSIX_ZERO(ret, NULL);
145
146 ret = posix_spawnp(NULL, id[0], NULL, &attr, id, environ);
147 T_ASSERT_NE(ret, 0, "spawn with bad suid cred");
148
149 ret = posix_spawnattr_destroy(&attr);
150 T_QUIET; T_ASSERT_POSIX_ZERO(ret, NULL);
151 }
152
153 /*
154 * The privileged/entitled "server" which creates suid credentials to pass to a
155 * client. Two creds are created, one for /usr/bin/id and the other for /bin/ls.
156 * It waits for the client to contact and replies with the above ports.
157 */
158 T_HELPER_DECL(suid_cred_server_helper, "suid cred server")
159 {
160 mach_port_t server_port = MACH_PORT_NULL;
161 kern_return_t ret = KERN_FAILURE;
162 suid_cred_t sc_id = SUID_CRED_NULL;
163 suid_cred_t sc_ls = SUID_CRED_NULL;
164 mach_msg_empty_rcv_t rmsg = {};
165 struct {
166 mach_msg_header_t header;
167 mach_msg_body_t body;
168 mach_msg_port_descriptor_t id_port;
169 mach_msg_port_descriptor_t ls_port;
170 } smsg = {};
171
172 T_SETUPBEGIN;
173
174 ret = bootstrap_check_in(bootstrap_port, server_name, &server_port);
175 T_ASSERT_MACH_SUCCESS(ret, NULL);
176
177 T_SETUPEND;
178
179 // Wait for a message to reply to.
180 rmsg.header.msgh_size = sizeof(rmsg);
181 rmsg.header.msgh_local_port = server_port;
182
183 ret = mach_msg_receive(&rmsg.header);
184 T_QUIET; T_ASSERT_MACH_SUCCESS(ret, NULL);
185
186 // Setup the reply.
187 smsg.header.msgh_remote_port = rmsg.header.msgh_remote_port;
188 smsg.header.msgh_local_port = MACH_PORT_NULL;
189 smsg.header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_MOVE_SEND_ONCE, 0) | MACH_MSGH_BITS_COMPLEX;
190 smsg.header.msgh_size = sizeof(smsg);
191
192 smsg.body.msgh_descriptor_count = 2;
193
194 // Create an suid cred for 'id'
195 ret = task_create_suid_cred(mach_task_self(), "/usr/bin/id", 0, &sc_id);
196 T_QUIET; T_ASSERT_MACH_SUCCESS(ret, "create a new suid cred for id");
197 T_QUIET; T_ASSERT_NE(sc_id, SUID_CRED_NULL, NULL);
198
199 smsg.id_port.name = sc_id;
200 smsg.id_port.disposition = MACH_MSG_TYPE_COPY_SEND;
201 smsg.id_port.type = MACH_MSG_PORT_DESCRIPTOR;
202
203 // Create an suid cred for 'ls'
204 ret = task_create_suid_cred(mach_task_self(), "/bin/ls", 0, &sc_ls);
205 T_QUIET; T_ASSERT_MACH_SUCCESS(ret, "create a new suid cred for ls");
206 T_QUIET; T_ASSERT_NE(sc_ls, SUID_CRED_NULL, NULL);
207
208 smsg.ls_port.name = sc_ls;
209 smsg.ls_port.disposition = MACH_MSG_TYPE_COPY_SEND;
210 smsg.ls_port.type = MACH_MSG_PORT_DESCRIPTOR;
211
212 ret = mach_msg_send(&smsg.header);
213 T_QUIET; T_ASSERT_MACH_SUCCESS(ret, NULL);
214 }
215
216 /*
217 * The unprivileged "client" which requests suid credentials from the "server",
218 * and runs some test cases with those credentials:
219 * - A positive test case to spawn something with euid 0
220 * - A negative test case to check that a cred can't be used twice
221 * - A negative test case to check that only the approved binary can be used
222 * with the credential.
223 */
224 T_HELPER_DECL(suid_cred_client_helper, "suid cred client")
225 {
226 mach_port_t server_port = MACH_PORT_NULL;
227 mach_port_t client_port = MACH_PORT_NULL;
228 kern_return_t ret = KERN_FAILURE;
229 suid_cred_t sc_id = SUID_CRED_NULL;
230 suid_cred_t sc_ls = SUID_CRED_NULL;
231 mach_msg_empty_send_t smsg = {};
232 struct {
233 mach_msg_header_t header;
234 mach_msg_body_t body;
235 mach_msg_port_descriptor_t id_port;
236 mach_msg_port_descriptor_t ls_port;
237 mach_msg_trailer_t trailer;
238 } rmsg = {};
239
240 uid_t euid = geteuid();
241
242 T_SETUPBEGIN;
243
244 // Make sure the effective UID is non-root.
245 if (euid == 0) {
246 ret = setuid(501);
247 T_ASSERT_POSIX_ZERO(ret, "setuid");
248 }
249
250 /*
251 * As this can race with the "server" starting, give it time to
252 * start up.
253 */
254 for (int i = 0; i < 30; i++) {
255 ret = bootstrap_look_up(bootstrap_port, server_name, &server_port);
256 if (ret != BOOTSTRAP_UNKNOWN_SERVICE) {
257 break;
258 }
259 sleep(1);
260 }
261
262 T_QUIET; T_ASSERT_NE(server_port, MACH_PORT_NULL, NULL);
263
264 // Create a report to receive the reply on.
265 ret = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &client_port);
266 T_ASSERT_MACH_SUCCESS(ret, NULL);
267
268 T_SETUPEND;
269
270 // Request the SUID cred ports
271 smsg.header.msgh_remote_port = server_port;
272 smsg.header.msgh_local_port = client_port;
273 smsg.header.msgh_bits = MACH_MSGH_BITS_SET(MACH_MSG_TYPE_MOVE_SEND, MACH_MSG_TYPE_MAKE_SEND_ONCE, 0, 0);
274 smsg.header.msgh_size = sizeof(smsg);
275
276 ret = mach_msg_send(&smsg.header);
277 T_QUIET; T_ASSERT_MACH_SUCCESS(ret, NULL);
278
279 // Wait for the reply.
280 rmsg.header.msgh_size = sizeof(rmsg);
281 rmsg.header.msgh_local_port = client_port;
282
283 ret = mach_msg_receive(&rmsg.header);
284 T_QUIET; T_ASSERT_MACH_SUCCESS(ret, NULL);
285
286 sc_id = rmsg.id_port.name;
287 T_QUIET; T_ASSERT_NE(sc_id, SUID_CRED_NULL, NULL);
288 test_id_cred(sc_id);
289 test_id_cred_reuse(sc_id);
290
291 sc_ls = rmsg.ls_port.name;
292 T_QUIET; T_ASSERT_NE(sc_ls, SUID_CRED_NULL, NULL);
293 test_ls_cred(sc_ls);
294 }
295
296 T_DECL(task_create_suid_cred, "task_create_suid_cred", T_META_ASROOT(true))
297 {
298 dt_helper_t helpers[] = {
299 dt_launchd_helper_domain("com.apple.xnu.test.task_create_suid_cred.plist",
300 "suid_cred_server_helper", NULL, LAUNCH_SYSTEM_DOMAIN),
301 dt_fork_helper("suid_cred_client_helper"),
302 };
303
304 dt_run_helpers(helpers, sizeof(helpers) / sizeof(helpers[0]), 60);
305 }
306
307 /*
308 * Creating an suid credential should fail for non-root (even if entitled).
309 */
310 T_DECL(task_create_suid_cred_no_root, "task_create_suid_cred (no root)", T_META_ASROOT(true))
311 {
312 kern_return_t ret = KERN_FAILURE;
313 suid_cred_t sc = SUID_CRED_NULL;
314 uid_t euid = geteuid();
315
316 // Make sure the effective UID is non-root.
317 if (euid == 0) {
318 ret = setuid(501);
319 T_QUIET; T_ASSERT_POSIX_ZERO(ret, "setuid");
320 }
321
322 ret = task_create_suid_cred(mach_task_self(), "/usr/bin/id", 0, &sc);
323 T_ASSERT_MACH_ERROR(ret, KERN_NO_ACCESS, "create a new suid cred for id (non-root)");
324 }
325
326 #endif /* ENTITLED */