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