4 #include <darwintest.h>
5 #include <darwintest_multiprocess.h>
9 #if defined(UNENTITLED)
12 * Creating an suid credential should fail without an entitlement.
14 T_DECL(task_create_suid_cred_unentitled
, "task_create_suid_cred (no entitlment)", T_META_ASROOT(true))
16 kern_return_t ret
= KERN_FAILURE
;
17 suid_cred_t sc
= SUID_CRED_NULL
;
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)");
25 extern char **environ
;
26 static const char *server_name
= "com.apple.xnu.test.task_create_suid_cred";
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.
34 test_id_cred(suid_cred_t sc_id
)
36 posix_spawnattr_t attr
;
37 posix_spawn_file_actions_t file_actions
;
40 char template[] = "/tmp/suid_cred.XXXXXX";
46 char *id
[] = {"/usr/bin/id", NULL
};
47 kern_return_t ret
= KERN_FAILURE
;
49 /* Send stdout to a temporary file. */
50 path
= mktemp(template);
51 T_QUIET
; T_ASSERT_NOTNULL(path
, NULL
);
53 ret
= posix_spawn_file_actions_init(&file_actions
);
54 T_QUIET
; T_ASSERT_POSIX_ZERO(ret
, NULL
);
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
);
60 ret
= posix_spawnattr_init(&attr
);
61 T_QUIET
; T_ASSERT_POSIX_ZERO(ret
, NULL
);
62 T_QUIET
; T_ASSERT_NOTNULL(attr
, NULL
);
64 // Attach the suid cred port
65 ret
= posix_spawnattr_setsuidcredport_np(&attr
, sc_id
);
66 T_QUIET
; T_ASSERT_POSIX_ZERO(ret
, NULL
);
68 ret
= posix_spawnp(&pid
, id
[0], &file_actions
, &attr
, id
, environ
);
69 T_ASSERT_POSIX_ZERO(ret
, "spawn with suid cred");
71 ret
= posix_spawnattr_destroy(&attr
);
72 T_QUIET
; T_ASSERT_POSIX_ZERO(ret
, NULL
);
74 ret
= posix_spawn_file_actions_destroy(&file_actions
);
75 T_QUIET
; T_ASSERT_POSIX_ZERO(ret
, NULL
);
77 // Wait for id to finish executing and exit.
79 ret
= waitpid(pid
, &status
, 0);
80 } while (ret
< 0 && errno
== EINTR
);
81 T_QUIET
; T_ASSERT_POSIX_SUCCESS(ret
, NULL
);
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
);
87 linelen
= getline(&line
, &linecap
, file
);
88 T_QUIET
; T_ASSERT_GT_LONG(linelen
, 0L, NULL
);
90 T_ASSERT_NOTNULL(strstr(line
, "euid=0"), "verify that euid is zero");
94 T_QUIET
; T_ASSERT_POSIX_ZERO(ret
, NULL
);
97 T_QUIET
; T_ASSERT_POSIX_ZERO(ret
, NULL
);
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.
106 test_id_cred_reuse(suid_cred_t sc_id
)
108 posix_spawnattr_t attr
;
109 char *id
[] = {"/usr/bin/id", NULL
};
110 kern_return_t ret
= KERN_FAILURE
;
112 ret
= posix_spawnattr_init(&attr
);
113 T_QUIET
; T_ASSERT_POSIX_ZERO(ret
, NULL
);
114 T_QUIET
; T_ASSERT_NOTNULL(attr
, NULL
);
116 // Attach the suid cred port
117 ret
= posix_spawnattr_setsuidcredport_np(&attr
, sc_id
);
118 T_QUIET
; T_ASSERT_POSIX_ZERO(ret
, NULL
);
120 ret
= posix_spawnp(NULL
, id
[0], NULL
, &attr
, id
, environ
);
121 T_ASSERT_NE(ret
, 0, "spawn with used suid cred");
123 ret
= posix_spawnattr_destroy(&attr
);
124 T_QUIET
; T_ASSERT_POSIX_ZERO(ret
, NULL
);
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.
132 test_ls_cred(suid_cred_t sc_ls
)
134 posix_spawnattr_t attr
;
135 char *id
[] = {"/usr/bin/id", NULL
};
136 kern_return_t ret
= KERN_FAILURE
;
138 ret
= posix_spawnattr_init(&attr
);
139 T_QUIET
; T_ASSERT_POSIX_ZERO(ret
, NULL
);
140 T_QUIET
; T_ASSERT_NOTNULL(attr
, NULL
);
142 // Attach the suid cred port
143 ret
= posix_spawnattr_setsuidcredport_np(&attr
, sc_ls
);
144 T_QUIET
; T_ASSERT_POSIX_ZERO(ret
, NULL
);
146 ret
= posix_spawnp(NULL
, id
[0], NULL
, &attr
, id
, environ
);
147 T_ASSERT_NE(ret
, 0, "spawn with bad suid cred");
149 ret
= posix_spawnattr_destroy(&attr
);
150 T_QUIET
; T_ASSERT_POSIX_ZERO(ret
, NULL
);
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.
158 T_HELPER_DECL(suid_cred_server_helper
, "suid cred server")
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
= {};
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
;
174 ret
= bootstrap_check_in(bootstrap_port
, server_name
, &server_port
);
175 T_ASSERT_MACH_SUCCESS(ret
, NULL
);
179 // Wait for a message to reply to.
180 rmsg
.header
.msgh_size
= sizeof(rmsg
);
181 rmsg
.header
.msgh_local_port
= server_port
;
183 ret
= mach_msg_receive(&rmsg
.header
);
184 T_QUIET
; T_ASSERT_MACH_SUCCESS(ret
, NULL
);
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
);
192 smsg
.body
.msgh_descriptor_count
= 2;
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
);
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
;
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
);
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
;
212 ret
= mach_msg_send(&smsg
.header
);
213 T_QUIET
; T_ASSERT_MACH_SUCCESS(ret
, NULL
);
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.
224 T_HELPER_DECL(suid_cred_client_helper
, "suid cred client")
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
= {};
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
;
240 uid_t euid
= geteuid();
244 // Make sure the effective UID is non-root.
247 T_ASSERT_POSIX_ZERO(ret
, "setuid");
251 * As this can race with the "server" starting, give it time to
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
) {
262 T_QUIET
; T_ASSERT_NE(server_port
, MACH_PORT_NULL
, NULL
);
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
);
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
);
276 ret
= mach_msg_send(&smsg
.header
);
277 T_QUIET
; T_ASSERT_MACH_SUCCESS(ret
, NULL
);
279 // Wait for the reply.
280 rmsg
.header
.msgh_size
= sizeof(rmsg
);
281 rmsg
.header
.msgh_local_port
= client_port
;
283 ret
= mach_msg_receive(&rmsg
.header
);
284 T_QUIET
; T_ASSERT_MACH_SUCCESS(ret
, NULL
);
286 sc_id
= rmsg
.id_port
.name
;
287 T_QUIET
; T_ASSERT_NE(sc_id
, SUID_CRED_NULL
, NULL
);
289 test_id_cred_reuse(sc_id
);
291 sc_ls
= rmsg
.ls_port
.name
;
292 T_QUIET
; T_ASSERT_NE(sc_ls
, SUID_CRED_NULL
, NULL
);
296 T_DECL(task_create_suid_cred
, "task_create_suid_cred", T_META_ASROOT(true))
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"),
304 dt_run_helpers(helpers
, sizeof(helpers
) / sizeof(helpers
[0]), 60);
308 * Creating an suid credential should fail for non-root (even if entitled).
310 T_DECL(task_create_suid_cred_no_root
, "task_create_suid_cred (no root)", T_META_ASROOT(true))
312 kern_return_t ret
= KERN_FAILURE
;
313 suid_cred_t sc
= SUID_CRED_NULL
;
314 uid_t euid
= geteuid();
316 // Make sure the effective UID is non-root.
319 T_QUIET
; T_ASSERT_POSIX_ZERO(ret
, "setuid");
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)");
326 #endif /* ENTITLED */