]>
Commit | Line | Data |
---|---|---|
ea3f0419 A |
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 */ |