1 #include <stdlib.h>
2 #include <stdio.h>
3 #include <pthread.h>
4 #include <unistd.h>
5 #include <mach/kern_return.h>
6 #include <mach/mach.h>
7 #include <mach/task.h>
8 #include <mach/thread_status.h>
9 #include <os/tsd.h>
10 #include <assert.h>
11 #include <sys/codesign.h>
12 #include <stdbool.h>
13
14 #include "cs_helpers.h"
15
16 #define MAX_TEST_NUM 10
17
18 #if __arm64__
19 #define machine_thread_state_t arm_thread_state64_t
20 #define EXCEPTION_THREAD_STATE ARM_THREAD_STATE64
21 #define EXCEPTION_THREAD_STATE_COUNT ARM_THREAD_STATE64_COUNT
22 #elif __x86_64__
23 #define machine_thread_state_t x86_thread_state_t
24 #define EXCEPTION_THREAD_STATE x86_THREAD_STATE
25 #define EXCEPTION_THREAD_STATE_COUNT x86_THREAD_STATE_COUNT
26 #else
27 #error Unsupported architecture
28 #endif
29
30 static mach_port_t
alloc_server_port(void)31 alloc_server_port(void)
32 {
33 mach_port_t server_port = MACH_PORT_NULL;
34 kern_return_t kr;
35
36 kr = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &server_port);
37 assert(kr == 0);
38
39 kr = mach_port_insert_right(mach_task_self(), server_port, server_port, MACH_MSG_TYPE_MAKE_SEND);
40 assert(kr == 0);
41
42 return server_port;
43 }
44
45 static mach_port_t
alloc_provisional_reply_port()46 alloc_provisional_reply_port()
47 {
48 kern_return_t kr;
49 mach_port_t reply_port = MACH_PORT_NULL;
50 mach_port_t task = mach_task_self();
51
52 mach_port_options_t opts = {
53 .flags = MPO_PROVISIONAL_REPLY_PORT | MPO_INSERT_SEND_RIGHT,
54 };
55
56 kr = mach_port_construct(mach_task_self(), &opts, 0, &reply_port);
57 assert(kr == 0);
58
59 return reply_port;
60 }
61
62 static mach_port_t
alloc_reply_port()63 alloc_reply_port()
64 {
65 kern_return_t kr;
66 mach_port_t reply_port = MACH_PORT_NULL;
67 mach_port_t task = mach_task_self();
68
69 mach_port_options_t opts = {
70 .flags = MPO_REPLY_PORT | MPO_INSERT_SEND_RIGHT,
71 };
72
73 kr = mach_port_construct(mach_task_self(), &opts, 0, &reply_port);
74 assert(kr == 0);
75
76 return reply_port;
77 }
78
79 /* The rcv right of the port would be marked immovable. */
80 static void
test_immovable_receive_right(void)81 test_immovable_receive_right(void)
82 {
83 kern_return_t kr;
84 mach_port_t server_port = MACH_PORT_NULL, reply_port = MACH_PORT_NULL;
85 struct {
86 mach_msg_header_t header;
87 mach_msg_body_t body;
88 mach_msg_port_descriptor_t desc;
89 } msg;
90
91 server_port = alloc_server_port();
92 reply_port = alloc_reply_port();
93
94 msg.header.msgh_remote_port = server_port;
95 msg.header.msgh_local_port = MACH_PORT_NULL;
96 msg.header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0) | MACH_MSGH_BITS_COMPLEX;
97 msg.header.msgh_size = sizeof msg;
98
99 msg.body.msgh_descriptor_count = 1;
100
101 msg.desc.name = reply_port;
102 msg.desc.disposition = MACH_MSG_TYPE_MOVE_RECEIVE;
103 msg.desc.type = MACH_MSG_PORT_DESCRIPTOR;
104 kr = mach_msg_send(&msg.header);
105
106 printf("[reply_port_defense_client test_immovable_receive_right]: mach_msg2() returned %d\n", kr);
107 }
108
109 /* The only way you could create a send once right is when you send the port in local port of a mach msg with MAKE_SEND_ONCE disposition. */
110 static void
test_make_send_once_right(void)111 test_make_send_once_right(void)
112 {
113 kern_return_t kr;
114 mach_port_t reply_port = alloc_reply_port();
115 kr = mach_port_insert_right(mach_task_self(), reply_port, reply_port, MACH_MSG_TYPE_MAKE_SEND_ONCE);
116 printf("[reply_port_defense_client test_make_send_once_right]: mach_port_insert_right() returned %d\n", kr);
117 }
118
119 /* The send right of the port would only used for guarding a name in ipc space, it would not allow to send a message. */
120 static void
test_using_send_right(void)121 test_using_send_right(void)
122 {
123 kern_return_t kr;
124 mach_port_t reply_port = alloc_reply_port();
125 struct {
126 mach_msg_header_t header;
127 mach_msg_body_t body;
128 } msg;
129
130 msg.header.msgh_remote_port = reply_port;
131 msg.header.msgh_local_port = MACH_PORT_NULL;
132 msg.header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0);
133 msg.header.msgh_size = sizeof msg;
134
135 kr = mach_msg_send(&msg.header);
136 printf("[reply_port_defense_client test_using_send_right]: mach_msg2() returned %d\n", kr);
137 }
138
139 /* The send right of the port would only used for guarding a name in ipc space, it would not allowed to get moved. */
140 static void
test_move_send_right(void)141 test_move_send_right(void)
142 {
143 kern_return_t kr;
144 mach_port_t server_port = MACH_PORT_NULL, reply_port = MACH_PORT_NULL;
145 struct {
146 mach_msg_header_t header;
147 mach_msg_body_t body;
148 mach_msg_port_descriptor_t desc;
149 } msg;
150
151 server_port = alloc_server_port();
152 reply_port = alloc_reply_port();
153
154 msg.header.msgh_remote_port = server_port;
155 msg.header.msgh_local_port = MACH_PORT_NULL;
156 msg.header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0) | MACH_MSGH_BITS_COMPLEX;
157 msg.header.msgh_size = sizeof msg;
158
159 msg.body.msgh_descriptor_count = 1;
160
161 msg.desc.name = reply_port;
162 msg.desc.disposition = MACH_MSG_TYPE_MOVE_SEND;
163 msg.desc.type = MACH_MSG_PORT_DESCRIPTOR;
164
165 kr = mach_msg_send(&msg.header);
166 printf("[reply_port_defense_client test_move_send_right]: mach_msg2() returned %d\n", kr);
167 }
168
169 static void
test_move_provisional_reply_port(void)170 test_move_provisional_reply_port(void)
171 {
172 kern_return_t kr;
173 mach_port_t server_port = MACH_PORT_NULL, reply_port = MACH_PORT_NULL;
174 struct {
175 mach_msg_header_t header;
176 mach_msg_body_t body;
177 mach_msg_port_descriptor_t desc;
178 } msg;
179
180 server_port = alloc_server_port();
181 reply_port = alloc_provisional_reply_port();
182
183 msg.header.msgh_remote_port = server_port;
184 msg.header.msgh_local_port = MACH_PORT_NULL;
185 msg.header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0) | MACH_MSGH_BITS_COMPLEX;
186 msg.header.msgh_size = sizeof msg;
187
188 msg.body.msgh_descriptor_count = 1;
189
190 msg.desc.name = reply_port;
191 msg.desc.disposition = MACH_MSG_TYPE_MOVE_RECEIVE;
192 msg.desc.type = MACH_MSG_PORT_DESCRIPTOR;
193
194 kr = mach_msg_send(&msg.header);
195
196 printf("[reply_port_defense_client test_immovable_receive_right]: mach_msg2() returned %d\n", kr);
197 }
198
199 static void
test_unentitled_thread_set_state(void)200 test_unentitled_thread_set_state(void)
201 {
202 machine_thread_state_t ts;
203 mach_msg_type_number_t count = MACHINE_THREAD_STATE_COUNT;
204
205 /* thread_set_state as a hardened binary should fail */
206 kern_return_t kr = thread_get_state(mach_thread_self(), MACHINE_THREAD_STATE, (thread_state_t)&ts, &count);
207
208 kr = thread_set_state(mach_thread_self(), MACHINE_THREAD_STATE, (thread_state_t)&ts, count);
209 assert(kr != KERN_SUCCESS);
210 exit(-1); /* Should have crashed before here! */
211 }
212
213 static void
unentitled_set_exception_ports_crash(void)214 unentitled_set_exception_ports_crash(void)
215 {
216 mach_port_t exc_port = alloc_server_port();
217
218 /* thread_set_exception_ports as a hardened binary should fail */
219 kern_return_t kr = thread_set_exception_ports(
220 mach_thread_self(),
221 EXC_MASK_ALL,
222 exc_port,
223 (exception_behavior_t)((unsigned int)EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES),
224 EXCEPTION_THREAD_STATE);
225
226 /* thread_set_exception_ports is supposed to crash, unless the policy is turned off.
227 * Things that disable the policy: AMFI boot-args in use, SIP disabled,
228 * third party plugins in a process. The caller of this client will check
229 * whether the test crashed and correctly adhered to these policies.
230 */
231 printf("thread_set_exception_ports did not crash\n");
232 }
233
234 static void
unentitled_set_exception_ports_pass(void)235 unentitled_set_exception_ports_pass(void)
236 {
237 mach_port_t exc_port = alloc_server_port();
238
239 /* thread_set_exception_ports with state *IDENTITY_PROTECTED should not fail */
240 kern_return_t kr = thread_set_exception_ports(
241 mach_thread_self(),
242 EXC_MASK_ALL,
243 exc_port,
244 (exception_behavior_t)((unsigned int)EXCEPTION_STATE_IDENTITY_PROTECTED | MACH_EXCEPTION_CODES),
245 EXCEPTION_THREAD_STATE);
246 assert(kr == 0);
247
248 kr = thread_set_exception_ports(
249 mach_thread_self(),
250 EXC_MASK_ALL,
251 exc_port,
252 (exception_behavior_t)((unsigned int)EXCEPTION_IDENTITY_PROTECTED | MACH_EXCEPTION_CODES),
253 EXCEPTION_THREAD_STATE);
254 assert(kr == 0);
255
256 return;
257 }
258
259 static void
exception_ports_crash(void)260 exception_ports_crash(void)
261 {
262 kern_return_t kr;
263 mach_port_t exc_port;
264 mach_port_options_t opts = {
265 .flags = MPO_INSERT_SEND_RIGHT | MPO_EXCEPTION_PORT,
266 };
267
268 kr = mach_port_construct(mach_task_self(), &opts, 0ull, &exc_port);
269 assert(kr == KERN_SUCCESS);
270
271 kr = task_register_hardened_exception_handler(current_task(),
272 0, EXC_MASK_BAD_ACCESS,
273 EXCEPTION_STATE_IDENTITY_PROTECTED, EXCEPTION_THREAD_STATE, exc_port);
274
275 kr = thread_set_exception_ports(
276 mach_thread_self(),
277 EXC_MASK_BAD_ACCESS,
278 exc_port,
279 (exception_behavior_t)((unsigned int)EXCEPTION_STATE_IDENTITY_PROTECTED | MACH_EXCEPTION_CODES),
280 EXCEPTION_THREAD_STATE);
281
282 printf("thread_set_exception_ports did not crash: %d\n", kr);
283 }
284
285 static void
kobject_reply_port_defense(void)286 kobject_reply_port_defense(void)
287 {
288 machine_thread_state_t ts;
289 mach_msg_type_number_t count = MACHINE_THREAD_STATE_COUNT;
290 mach_port_t port = MACH_PORT_NULL;
291
292 kern_return_t kr = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &port);
293 assert(kr == KERN_SUCCESS);
294
295 // make a kobject call
296 kr = thread_get_state(mach_thread_self(), MACHINE_THREAD_STATE, (thread_state_t)&ts, &count);
297 assert(kr == KERN_SUCCESS);
298
299 // set the MIG reply port to a "normal" port
300 _os_tsd_set_direct(__TSD_MIG_REPLY, (void *)(uintptr_t)port);
301
302 kr = thread_get_state(mach_thread_self(), MACHINE_THREAD_STATE, (thread_state_t)&ts, &count);
303
304 printf("kobject call did not crash: %d\n", kr);
305 }
306
307 int
main(int argc,char * argv[])308 main(int argc, char *argv[])
309 {
310 uint32_t my_csflags = 0;
311 bool thirdparty_hardened = !strcmp(argv[0], "./reply_port_defense_client_3P_hardened");
312
313 /* TODO add some sysctl which disabled platform binary bit here */
314 if (my_csflags & CS_PLATFORM_BINARY == thirdparty_hardened) {
315 printf("platform binary does not match expected\n");
316 return -1;
317 }
318
319
320 void (*tests[MAX_TEST_NUM])(void) = {
321 test_immovable_receive_right, /* 0 */
322 test_make_send_once_right,
323 test_using_send_right, /* 2 */
324 test_move_send_right,
325 test_move_provisional_reply_port, /* 4 */
326 unentitled_set_exception_ports_crash,
327 test_unentitled_thread_set_state, /* 6 */
328 unentitled_set_exception_ports_pass,
329 exception_ports_crash, /* 8 */
330 kobject_reply_port_defense, /* 9 */
331 };
332
333 if (argc < 2) {
334 printf("[reply_port_defense_client]: Specify a test to run.");
335 exit(-1);
336 }
337
338 int test_num = atoi(argv[1]);
339 printf("[reply_port_defense_client]: My Pid: %d Test num: %d third_party_hardened: %s\n",
340 getpid(), test_num, thirdparty_hardened ? "yes" : "no");
341 fflush(stdout);
342 if (test_num >= 0 && test_num < MAX_TEST_NUM) {
343 (*tests[test_num])();
344 } else {
345 printf("[reply_port_defense_client]: Invalid test num. Exiting...\n");
346 exit(-1);
347 }
348 printf("Child exiting cleanly!!\n");
349 fflush(stdout);
350 // return 0;
351 exit(0);
352 }
353