diff options
Diffstat (limited to 'src/ostrace.c')
-rw-r--r-- | src/ostrace.c | 436 |
1 files changed, 436 insertions, 0 deletions
diff --git a/src/ostrace.c b/src/ostrace.c new file mode 100644 index 0000000..68eb6bf --- /dev/null +++ b/src/ostrace.c | |||
@@ -0,0 +1,436 @@ | |||
1 | /* | ||
2 | * ostrace.c | ||
3 | * com.apple.os_trace_relay service implementation. | ||
4 | * | ||
5 | * Copyright (c) 2020-2025 Nikias Bassen, All Rights Reserved. | ||
6 | * | ||
7 | * This library is free software; you can redistribute it and/or | ||
8 | * modify it under the terms of the GNU Lesser General Public | ||
9 | * License as published by the Free Software Foundation; either | ||
10 | * version 2.1 of the License, or (at your option) any later version. | ||
11 | * | ||
12 | * This library is distributed in the hope that it will be useful, | ||
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
15 | * Lesser General Public License for more details. | ||
16 | * | ||
17 | * You should have received a copy of the GNU Lesser General Public | ||
18 | * License along with this library; if not, write to the Free Software | ||
19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||
20 | */ | ||
21 | |||
22 | #ifdef HAVE_CONFIG_H | ||
23 | #include <config.h> | ||
24 | #endif | ||
25 | #include <string.h> | ||
26 | #include <stdlib.h> | ||
27 | |||
28 | #include <plist/plist.h> | ||
29 | |||
30 | #include "ostrace.h" | ||
31 | #include "lockdown.h" | ||
32 | #include "common/debug.h" | ||
33 | #include "endianness.h" | ||
34 | |||
35 | struct ostrace_worker_thread { | ||
36 | ostrace_client_t client; | ||
37 | ostrace_activity_cb_t cbfunc; | ||
38 | void *user_data; | ||
39 | }; | ||
40 | |||
41 | /** | ||
42 | * Convert a service_error_t value to a ostrace_error_t value. | ||
43 | * Used internally to get correct error codes. | ||
44 | * | ||
45 | * @param err An service_error_t error code | ||
46 | * | ||
47 | * @return A matching ostrace_error_t error code, | ||
48 | * OSTRACE_E_UNKNOWN_ERROR otherwise. | ||
49 | */ | ||
50 | static ostrace_error_t ostrace_error(service_error_t err) | ||
51 | { | ||
52 | switch (err) { | ||
53 | case SERVICE_E_SUCCESS: | ||
54 | return OSTRACE_E_SUCCESS; | ||
55 | case SERVICE_E_INVALID_ARG: | ||
56 | return OSTRACE_E_INVALID_ARG; | ||
57 | case SERVICE_E_MUX_ERROR: | ||
58 | return OSTRACE_E_MUX_ERROR; | ||
59 | case SERVICE_E_SSL_ERROR: | ||
60 | return OSTRACE_E_SSL_ERROR; | ||
61 | case SERVICE_E_NOT_ENOUGH_DATA: | ||
62 | return OSTRACE_E_NOT_ENOUGH_DATA; | ||
63 | case SERVICE_E_TIMEOUT: | ||
64 | return OSTRACE_E_TIMEOUT; | ||
65 | default: | ||
66 | break; | ||
67 | } | ||
68 | return OSTRACE_E_UNKNOWN_ERROR; | ||
69 | } | ||
70 | |||
71 | ostrace_error_t ostrace_client_new(idevice_t device, lockdownd_service_descriptor_t service, ostrace_client_t * client) | ||
72 | { | ||
73 | *client = NULL; | ||
74 | |||
75 | if (!device || !service || service->port == 0 || !client || *client) { | ||
76 | debug_info("Incorrect parameter passed to ostrace_client_new."); | ||
77 | return OSTRACE_E_INVALID_ARG; | ||
78 | } | ||
79 | |||
80 | debug_info("Creating ostrace_client, port = %d.", service->port); | ||
81 | |||
82 | service_client_t parent = NULL; | ||
83 | ostrace_error_t ret = ostrace_error(service_client_new(device, service, &parent)); | ||
84 | if (ret != OSTRACE_E_SUCCESS) { | ||
85 | debug_info("Creating base service client failed. Error: %i", ret); | ||
86 | return ret; | ||
87 | } | ||
88 | |||
89 | ostrace_client_t client_loc = (ostrace_client_t) malloc(sizeof(struct ostrace_client_private)); | ||
90 | client_loc->parent = parent; | ||
91 | client_loc->worker = THREAD_T_NULL; | ||
92 | |||
93 | *client = client_loc; | ||
94 | |||
95 | debug_info("ostrace_client successfully created."); | ||
96 | return 0; | ||
97 | } | ||
98 | |||
99 | ostrace_error_t ostrace_client_start_service(idevice_t device, ostrace_client_t * client, const char* label) | ||
100 | { | ||
101 | ostrace_error_t err = OSTRACE_E_UNKNOWN_ERROR; | ||
102 | service_client_factory_start_service(device, OSTRACE_SERVICE_NAME, (void**)client, label, SERVICE_CONSTRUCTOR(ostrace_client_new), &err); | ||
103 | return err; | ||
104 | } | ||
105 | |||
106 | ostrace_error_t ostrace_client_free(ostrace_client_t client) | ||
107 | { | ||
108 | if (!client) | ||
109 | return OSTRACE_E_INVALID_ARG; | ||
110 | ostrace_stop_activity(client); | ||
111 | ostrace_error_t err = ostrace_error(service_client_free(client->parent)); | ||
112 | free(client); | ||
113 | |||
114 | return err; | ||
115 | } | ||
116 | |||
117 | static ostrace_error_t ostrace_send_plist(ostrace_client_t client, plist_t plist) | ||
118 | { | ||
119 | ostrace_error_t res = OSTRACE_E_UNKNOWN_ERROR; | ||
120 | uint32_t blen = 0; | ||
121 | char* bin = NULL; | ||
122 | uint32_t sent = 0; | ||
123 | uint32_t swapped_len = 0; | ||
124 | |||
125 | if (!client || !plist) { | ||
126 | return OSTRACE_E_INVALID_ARG; | ||
127 | } | ||
128 | |||
129 | plist_to_bin(plist, &bin, &blen); | ||
130 | swapped_len = htobe32(blen); | ||
131 | |||
132 | res = ostrace_error(service_send(client->parent, (char*)&swapped_len, 4, &sent)); | ||
133 | if (res == OSTRACE_E_SUCCESS) { | ||
134 | res = ostrace_error(service_send(client->parent, bin, blen, &sent)); | ||
135 | } | ||
136 | free(bin); | ||
137 | return res; | ||
138 | } | ||
139 | |||
140 | static ostrace_error_t ostrace_receive_plist(ostrace_client_t client, plist_t *plist) | ||
141 | { | ||
142 | ostrace_error_t res = OSTRACE_E_UNKNOWN_ERROR; | ||
143 | uint8_t msgtype = 0; | ||
144 | uint32_t received = 0; | ||
145 | res = ostrace_error(service_receive(client->parent, (char*)&msgtype, 1, &received)); | ||
146 | if (res != OSTRACE_E_SUCCESS) { | ||
147 | debug_info("Failed to read message type from service"); | ||
148 | return res; | ||
149 | } | ||
150 | uint32_t rlen = 0; | ||
151 | res = ostrace_error(service_receive(client->parent, (char*)&rlen, 4, &received)); | ||
152 | if (res != OSTRACE_E_SUCCESS) { | ||
153 | debug_info("Failed to read message size from service"); | ||
154 | return res; | ||
155 | } | ||
156 | |||
157 | if (msgtype == 1) { | ||
158 | rlen = be32toh(rlen); | ||
159 | } else if (msgtype == 2) { | ||
160 | rlen = le32toh(rlen); | ||
161 | } else { | ||
162 | debug_info("Unexpected message type %d", msgtype); | ||
163 | return OSTRACE_E_UNKNOWN_ERROR; | ||
164 | } | ||
165 | debug_info("got length %d", rlen); | ||
166 | |||
167 | char* buf = (char*)malloc(rlen); | ||
168 | res = ostrace_error(service_receive(client->parent, buf, rlen, &received)); | ||
169 | if (res != OSTRACE_E_SUCCESS) { | ||
170 | return res; | ||
171 | } | ||
172 | |||
173 | plist_t reply = NULL; | ||
174 | plist_err_t perr = plist_from_memory(buf, received, &reply, NULL); | ||
175 | free(buf); | ||
176 | if (perr != PLIST_ERR_SUCCESS) { | ||
177 | return OSTRACE_E_UNKNOWN_ERROR; | ||
178 | } | ||
179 | *plist = reply; | ||
180 | return OSTRACE_E_SUCCESS; | ||
181 | } | ||
182 | |||
183 | static ostrace_error_t _ostrace_check_result(plist_t reply) | ||
184 | { | ||
185 | ostrace_error_t res = OSTRACE_E_REQUEST_FAILED; | ||
186 | if (!reply) { | ||
187 | return res; | ||
188 | } | ||
189 | plist_t p_status = plist_dict_get_item(reply, "Status"); | ||
190 | if (!p_status) { | ||
191 | return res; | ||
192 | } | ||
193 | const char* status = plist_get_string_ptr(p_status, NULL); | ||
194 | if (!status) { | ||
195 | return res; | ||
196 | } | ||
197 | if (!strcmp(status, "RequestSuccessful")) { | ||
198 | res = OSTRACE_E_SUCCESS; | ||
199 | } | ||
200 | return res; | ||
201 | } | ||
202 | |||
203 | void *ostrace_worker(void *arg) | ||
204 | { | ||
205 | ostrace_error_t res = OSTRACE_E_UNKNOWN_ERROR; | ||
206 | struct ostrace_worker_thread *oswt = (struct ostrace_worker_thread*)arg; | ||
207 | |||
208 | if (!oswt) | ||
209 | return NULL; | ||
210 | |||
211 | uint8_t msgtype = 0; | ||
212 | uint32_t received = 0; | ||
213 | |||
214 | debug_info("Running"); | ||
215 | |||
216 | while (oswt->client->parent) { | ||
217 | res = ostrace_error(service_receive_with_timeout(oswt->client->parent, (char*)&msgtype, 1, &received, 100)); | ||
218 | if (res == OSTRACE_E_TIMEOUT) { | ||
219 | continue; | ||
220 | } | ||
221 | if (res != OSTRACE_E_SUCCESS) { | ||
222 | debug_info("Failed to read message type from service"); | ||
223 | break; | ||
224 | } | ||
225 | uint32_t rlen = 0; | ||
226 | res = ostrace_error(service_receive(oswt->client->parent, (char*)&rlen, 4, &received)); | ||
227 | if (res != OSTRACE_E_SUCCESS) { | ||
228 | debug_info("Failed to read message size from service"); | ||
229 | break; | ||
230 | } | ||
231 | |||
232 | if (msgtype == 1) { | ||
233 | rlen = be32toh(rlen); | ||
234 | } else if (msgtype == 2) { | ||
235 | rlen = le32toh(rlen); | ||
236 | } else { | ||
237 | debug_info("Unexpected message type %d", msgtype); | ||
238 | break; | ||
239 | } | ||
240 | |||
241 | debug_info("got length %d", rlen); | ||
242 | |||
243 | void* buf = malloc(rlen); | ||
244 | res = ostrace_error(service_receive(oswt->client->parent, (char*)buf, rlen, &received)); | ||
245 | if (res != OSTRACE_E_SUCCESS) { | ||
246 | debug_info("Failed to receive %d bytes, error %d", rlen, res); | ||
247 | break; | ||
248 | } | ||
249 | if (received < rlen) { | ||
250 | debug_info("Failed to receive all data, got %d/%d", received, rlen); | ||
251 | break; | ||
252 | } | ||
253 | oswt->cbfunc(buf, received, oswt->user_data); | ||
254 | } | ||
255 | |||
256 | if (oswt) { | ||
257 | free(oswt); | ||
258 | } | ||
259 | |||
260 | debug_info("Exiting"); | ||
261 | |||
262 | return NULL; | ||
263 | } | ||
264 | |||
265 | ostrace_error_t ostrace_start_activity(ostrace_client_t client, plist_t options, ostrace_activity_cb_t callback, void* user_data) | ||
266 | { | ||
267 | if (!client || !callback) | ||
268 | return OSTRACE_E_INVALID_ARG; | ||
269 | |||
270 | ostrace_error_t res = OSTRACE_E_UNKNOWN_ERROR; | ||
271 | |||
272 | if (client->worker) { | ||
273 | debug_info("Another ostrace activity thread appears to be running already."); | ||
274 | return res; | ||
275 | } | ||
276 | |||
277 | plist_t dict = plist_new_dict(); | ||
278 | plist_dict_set_item(dict, "Pid", plist_new_uint(0x0FFFFFFFF)); | ||
279 | plist_dict_set_item(dict, "MessageFilter", plist_new_uint(0xFFFF)); | ||
280 | plist_dict_set_item(dict, "StreamFlags", plist_new_uint(0x3C)); | ||
281 | if (options) { | ||
282 | plist_dict_merge(&dict, options); | ||
283 | } | ||
284 | plist_dict_set_item(dict, "Request", plist_new_string("StartActivity")); | ||
285 | |||
286 | res = ostrace_send_plist(client, dict); | ||
287 | plist_free(dict); | ||
288 | if (res != OSTRACE_E_SUCCESS) { | ||
289 | return res; | ||
290 | } | ||
291 | |||
292 | dict = NULL; | ||
293 | res = ostrace_receive_plist(client, &dict); | ||
294 | if (res != OSTRACE_E_SUCCESS) { | ||
295 | return res; | ||
296 | } | ||
297 | res = _ostrace_check_result(dict); | ||
298 | if (res != OSTRACE_E_SUCCESS) { | ||
299 | return res; | ||
300 | } | ||
301 | |||
302 | /* start worker thread */ | ||
303 | struct ostrace_worker_thread *oswt = (struct ostrace_worker_thread*)malloc(sizeof(struct ostrace_worker_thread)); | ||
304 | if (oswt) { | ||
305 | oswt->client = client; | ||
306 | oswt->cbfunc = callback; | ||
307 | oswt->user_data = user_data; | ||
308 | |||
309 | if (thread_new(&client->worker, ostrace_worker, oswt) == 0) { | ||
310 | res = OSTRACE_E_SUCCESS; | ||
311 | } | ||
312 | } | ||
313 | |||
314 | return res; | ||
315 | } | ||
316 | |||
317 | ostrace_error_t ostrace_stop_activity(ostrace_client_t client) | ||
318 | { | ||
319 | if (client->worker) { | ||
320 | /* notify thread to finish */ | ||
321 | service_client_t parent = client->parent; | ||
322 | client->parent = NULL; | ||
323 | /* join thread to make it exit */ | ||
324 | thread_join(client->worker); | ||
325 | thread_free(client->worker); | ||
326 | client->worker = THREAD_T_NULL; | ||
327 | client->parent = parent; | ||
328 | } | ||
329 | |||
330 | return OSTRACE_E_SUCCESS; | ||
331 | } | ||
332 | |||
333 | ostrace_error_t ostrace_get_pid_list(ostrace_client_t client, plist_t* list) | ||
334 | { | ||
335 | ostrace_error_t res = OSTRACE_E_UNKNOWN_ERROR; | ||
336 | plist_t dict = plist_new_dict(); | ||
337 | plist_dict_set_item(dict, "Request", plist_new_string("PidList")); | ||
338 | |||
339 | if (!client || !list) { | ||
340 | return OSTRACE_E_INVALID_ARG; | ||
341 | } | ||
342 | |||
343 | res = ostrace_send_plist(client, dict); | ||
344 | plist_free(dict); | ||
345 | if (res != OSTRACE_E_SUCCESS) { | ||
346 | return res; | ||
347 | } | ||
348 | |||
349 | plist_t reply = NULL; | ||
350 | res = ostrace_receive_plist(client, &reply); | ||
351 | if (res != OSTRACE_E_SUCCESS) { | ||
352 | return res; | ||
353 | } | ||
354 | res = _ostrace_check_result(reply); | ||
355 | if (res != OSTRACE_E_SUCCESS) { | ||
356 | return res; | ||
357 | } | ||
358 | |||
359 | plist_t payload = plist_dict_get_item(reply, "Payload"); | ||
360 | if (!payload) { | ||
361 | return OSTRACE_E_REQUEST_FAILED; | ||
362 | } | ||
363 | *list = plist_copy(payload); | ||
364 | plist_free(reply); | ||
365 | |||
366 | return OSTRACE_E_SUCCESS; | ||
367 | } | ||
368 | |||
369 | ostrace_error_t ostrace_create_archive(ostrace_client_t client, plist_t options, ostrace_archive_write_cb_t callback, void* user_data) | ||
370 | { | ||
371 | ostrace_error_t res = OSTRACE_E_UNKNOWN_ERROR; | ||
372 | if (!client || !callback) { | ||
373 | return OSTRACE_E_INVALID_ARG; | ||
374 | } | ||
375 | plist_t dict = plist_new_dict(); | ||
376 | if (options) { | ||
377 | plist_dict_merge(&dict, options); | ||
378 | } | ||
379 | plist_dict_set_item(dict, "Request", plist_new_string("CreateArchive")); | ||
380 | |||
381 | res = ostrace_send_plist(client, dict); | ||
382 | plist_free(dict); | ||
383 | if (res != OSTRACE_E_SUCCESS) { | ||
384 | return res; | ||
385 | } | ||
386 | |||
387 | plist_t reply = NULL; | ||
388 | res = ostrace_receive_plist(client, &reply); | ||
389 | if (res != OSTRACE_E_SUCCESS) { | ||
390 | return res; | ||
391 | } | ||
392 | |||
393 | res = _ostrace_check_result(reply); | ||
394 | if (res != OSTRACE_E_SUCCESS) { | ||
395 | return res; | ||
396 | } | ||
397 | |||
398 | debug_info("Receiving archive...\n"); | ||
399 | while (1) { | ||
400 | uint8_t msgtype = 0; | ||
401 | uint32_t received = 0; | ||
402 | res = ostrace_error(service_receive(client->parent, (char*)&msgtype, 1, &received)); | ||
403 | if (res != OSTRACE_E_SUCCESS) { | ||
404 | debug_info("Could not read message type from service: %d", res); | ||
405 | break; | ||
406 | } | ||
407 | if (msgtype != 3) { | ||
408 | debug_info("Unexpected packet type %d", msgtype); | ||
409 | return OSTRACE_E_REQUEST_FAILED; | ||
410 | } | ||
411 | uint32_t rlen = 0; | ||
412 | res = ostrace_error(service_receive(client->parent, (char*)&rlen, 4, &received)); | ||
413 | if (res != OSTRACE_E_SUCCESS) { | ||
414 | debug_info("Failed to read message size from service"); | ||
415 | break; | ||
416 | } | ||
417 | |||
418 | rlen = le32toh(rlen); | ||
419 | debug_info("got length %d", rlen); | ||
420 | |||
421 | unsigned char* buf = (unsigned char*)malloc(rlen); | ||
422 | res = ostrace_error(service_receive(client->parent, (char*)buf, rlen, &received)); | ||
423 | if (res != OSTRACE_E_SUCCESS) { | ||
424 | debug_info("Could not read data from service: %d", res); | ||
425 | break; | ||
426 | } | ||
427 | if (callback(buf, received, user_data) < 0) { | ||
428 | debug_info("Aborted through callback"); | ||
429 | return OSTRACE_E_REQUEST_FAILED; | ||
430 | } | ||
431 | } | ||
432 | debug_info("Done.\n"); | ||
433 | |||
434 | return OSTRACE_E_SUCCESS; | ||
435 | } | ||
436 | |||