summaryrefslogtreecommitdiffstats
path: root/src/ostrace.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/ostrace.c')
-rw-r--r--src/ostrace.c436
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
35struct 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 */
50static 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
71ostrace_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
99ostrace_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
106ostrace_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
117static 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
140static 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
183static 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
203void *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
265ostrace_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
317ostrace_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
333ostrace_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
369ostrace_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