xref: /xnu-8019.80.24/tests/task_create_suid_cred.c (revision a325d9c4a84054e40bbe985afedcb50ab80993ea)
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
test_id_cred(suid_cred_t sc_id)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
test_id_cred_reuse(suid_cred_t sc_id)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
test_ls_cred(suid_cred_t sc_ls)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 */
327