1 /*
2 * Copyright (c) 2023-2024 Apple Inc. All rights reserved.
3 *
4 * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. The rights granted to you under the License
10 * may not be used to create, or enable the creation or redistribution of,
11 * unlawful or unlicensed copies of an Apple operating system, or to
12 * circumvent, violate, or enable the circumvention or violation of, any
13 * terms of an Apple operating system software license agreement.
14 *
15 * Please obtain a copy of the License at
16 * http://www.opensource.apple.com/apsl/ and read it before using this file.
17 *
18 * The Original Code and all software distributed under the License are
19 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
20 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
21 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
22 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
23 * Please see the License for the specific language governing rights and
24 * limitations under the License.
25 *
26 * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
27 */
28
29 #if CONFIG_EXCLAVES
30
31 #include <firehose/tracepoint_private.h>
32 #include <kern/thread.h>
33 #include <mach/exclaves.h>
34 #include <os/log_private.h>
35 #include <os/log.h>
36 #include <stdbool.h>
37 #include <stdint.h>
38
39 #include "kern/exclaves.tightbeam.h"
40 #include "exclaves_boot.h"
41 #include "exclaves_debug.h"
42 #include "exclaves_resource.h"
43
44 #define EXCLAVES_ID_LOGSERVER_EP \
45 (exclaves_service_lookup(EXCLAVES_DOMAIN_KERNEL, \
46 "com.apple.service.LogServer_xnuproxy"))
47
48 #define EXCLAVES_LOGS_CATEGORY "exclaves-logs"
49 #define EXCLAVES_CONFIG_CATEGORY "exclaves-config"
50
51 extern bool os_log_disabled(void);
52 extern kern_return_t exclaves_oslog_set_trace_mode(uint32_t);
53
54 static bool oslog_exclaves_ready = false;
55 static oslogdarwin_configadmin_s config_admin = {0};
56
57 TUNABLE(bool, oslog_exclaves, "oslog_exclaves", true);
58
59 #if DEVELOPMENT || DEBUG
60
61 #define OS_LOG_MAX_SIZE (4096)
62 #define dbg_counter_inc(c) counter_inc((c))
63
64 SCALABLE_COUNTER_DEFINE(oslog_e_log_count);
65 SCALABLE_COUNTER_DEFINE(oslog_e_log_dropped_count);
66 SCALABLE_COUNTER_DEFINE(oslog_e_metadata_count);
67 SCALABLE_COUNTER_DEFINE(oslog_e_metadata_dropped_count);
68 SCALABLE_COUNTER_DEFINE(oslog_e_signpost_count);
69 SCALABLE_COUNTER_DEFINE(oslog_e_signpost_dropped_count);
70 SCALABLE_COUNTER_DEFINE(oslog_e_replay_failure_count);
71 SCALABLE_COUNTER_DEFINE(oslog_e_query_count);
72 SCALABLE_COUNTER_DEFINE(oslog_e_query_error_count);
73 SCALABLE_COUNTER_DEFINE(oslog_e_trace_mode_set_count);
74 SCALABLE_COUNTER_DEFINE(oslog_e_trace_mode_error_count);
75
76 static size_t
oslogdarwin_logdata_data(const oslogdarwin_logdata_s * ld,uint8_t * ld_data,size_t ld_data_size)77 oslogdarwin_logdata_data(const oslogdarwin_logdata_s *ld, uint8_t *ld_data, size_t ld_data_size)
78 {
79 __block size_t count = 0;
80
81 logbyte__v_visit(&ld->data, ^(size_t i, const uint8_t item) {
82 /*
83 * logbyte__v_visit() does not provide means to stop the iteration
84 * and so the index is being checked not to overflow ld_data
85 * array.
86 */
87 if (i < ld_data_size) {
88 ld_data[i] = item;
89 }
90 count++;
91 });
92 return count;
93 }
94
95 static void
os_log_replay_log(const oslogdarwin_logdata_s * ld,uint8_t * ld_data,size_t ld_data_size)96 os_log_replay_log(const oslogdarwin_logdata_s *ld, uint8_t *ld_data, size_t ld_data_size)
97 {
98 firehose_stream_t stream = (firehose_stream_t)ld->stream;
99 const size_t ld_size = oslogdarwin_logdata_data(ld, ld_data, ld_data_size);
100 if (ld_size > ld_data_size || ld->pubsize > ld_size) {
101 #if DEVELOPMENT || DEBUG
102 panic("ld_size:%lu was >: %lu or <=: %hu", ld_size, ld_data_size, ld->pubsize);
103 #else
104 counter_inc(&oslog_e_replay_failure_count);
105 return;
106 #endif // DEVELOPMENT || DEBUG
107 }
108
109 firehose_tracepoint_id_u ftid = {
110 .ftid_value = ld->ftid
111 };
112
113 switch (ftid.ftid._namespace) {
114 case firehose_tracepoint_namespace_metadata:
115 counter_inc(&oslog_e_metadata_count);
116 if (stream != firehose_stream_metadata || !os_log_encoded_metadata(ftid, ld->stamp, ld_data, ld_size)) {
117 counter_inc(&oslog_e_metadata_dropped_count);
118 }
119 break;
120 case firehose_tracepoint_namespace_log:
121 counter_inc(&oslog_e_log_count);
122 if (!os_log_encoded_log(stream, ftid, ld->stamp, ld_data, ld_size, ld->pubsize)) {
123 counter_inc(&oslog_e_log_dropped_count);
124 }
125 break;
126 case firehose_tracepoint_namespace_signpost:
127 counter_inc(&oslog_e_signpost_count);
128 if (!os_log_encoded_signpost(stream, ftid, ld->stamp, ld_data, ld_size, ld->pubsize)) {
129 counter_inc(&oslog_e_signpost_dropped_count);
130 }
131 break;
132 default:
133 panic("Unsupported Exclaves log type %d", ftid.ftid._namespace);
134 }
135 }
136
137 static void
os_log_replay_logs(const oslogdarwin_logdata_v_s * logs,uint8_t * log_buffer,size_t log_buffer_size)138 os_log_replay_logs(const oslogdarwin_logdata_v_s *logs, uint8_t *log_buffer, size_t log_buffer_size)
139 {
140 oslogdarwin_logdata__v_visit(logs, ^(size_t __unused i, const oslogdarwin_logdata_s *_Nonnull log) {
141 os_log_replay_log(log, log_buffer, log_buffer_size);
142 });
143 }
144
145 /*
146 * The log retrieval thread (served by this handler) does not busy loop the
147 * whole time. It sleeps on a conditional variable in the Exclaves log server
148 * and runs only when there are new logs in Exclaves to pick up and to replay.
149 */
150 static void
log_server_retrieve_logs(__unused void * arg,__unused wait_result_t w)151 log_server_retrieve_logs(__unused void *arg, __unused wait_result_t w)
152 {
153 os_log_t log = os_log_create(OS_LOG_SUBSYSTEM, EXCLAVES_LOGS_CATEGORY);
154
155 tb_endpoint_t ep = tb_endpoint_create_with_value(TB_TRANSPORT_TYPE_XNU,
156 EXCLAVES_ID_LOGSERVER_EP, TB_ENDPOINT_OPTIONS_NONE);
157 if (ep == NULL) {
158 os_log_error(log, "Failed to create log server endpoint\n");
159 return;
160 }
161
162 oslogdarwin_consumer_s consumer = {0};
163
164 tb_error_t err = oslogdarwin_consumer__init(&consumer, ep);
165 if (err != TB_ERROR_SUCCESS) {
166 os_log_error(log, "Failed to initialize log consumer (error: %d)\n", err);
167 return;
168 }
169
170 uint8_t *log_buffer = kalloc_data_tag(OS_LOG_MAX_SIZE, Z_WAITOK_ZERO, VM_KERN_MEMORY_LOG);
171 if (!log_buffer) {
172 os_log_error(log, "Failed to allocate the log buffer\n");
173 return;
174 }
175
176 do {
177 err = oslogdarwin_consumer_getlogs(&consumer, ^(oslogdarwin_logdata_v_s logs) {
178 os_log_replay_logs(&logs, log_buffer, OS_LOG_MAX_SIZE);
179 counter_inc(&oslog_e_query_count);
180 });
181 } while (__probable(err == TB_ERROR_SUCCESS));
182
183 kfree_data(log_buffer, OS_LOG_MAX_SIZE);
184
185 counter_inc(&oslog_e_query_error_count);
186 os_log_error(log, "Failed to retrieve logs with (error: %d). Exiting.\n", err);
187 }
188
189 #else // DEVELOPMENT || DEBUG
190
191 #define dbg_counter_inc(c)
192
193 static void
replay_redacted_log(const oslogdarwin_redactedlogdata_log_s * log)194 replay_redacted_log(const oslogdarwin_redactedlogdata_log_s *log)
195 {
196 uuid_string_t uuidstr;
197 uuid_unparse(log->uuid, uuidstr);
198
199 os_log_at_time(OS_LOG_DEFAULT, (os_log_type_t)log->type, log->stamp, "log,%s,%0x",
200 uuidstr, log->offset);
201 }
202
203 static void
replay_redacted_signpost(const oslogdarwin_redactedlogdata_signpost_s * signpost)204 replay_redacted_signpost(const oslogdarwin_redactedlogdata_signpost_s *signpost)
205 {
206 uuid_string_t uuidstr;
207 uuid_unparse(signpost->uuid, uuidstr);
208
209 os_log_at_time(OS_LOG_DEFAULT, OS_LOG_TYPE_DEBUG, signpost->stamp, "signpost,%s,%0x,%0x,%u,%u",
210 uuidstr, signpost->fmtOffset, signpost->nameOffset, signpost->type, signpost->scope);
211 }
212
213 static void
os_log_replay_redacted_log(const oslogdarwin_redactedlogdata_s * ld)214 os_log_replay_redacted_log(const oslogdarwin_redactedlogdata_s *ld)
215 {
216 const oslogdarwin_redactedlogdata_log_s *log;
217 const oslogdarwin_redactedlogdata_signpost_s *signpost;
218
219 switch (ld->tag) {
220 case OSLOGDARWIN_REDACTEDLOGDATA__LOG:
221 log = oslogdarwin_redactedlogdata_log__get(ld);
222 replay_redacted_log(log);
223 break;
224 case OSLOGDARWIN_REDACTEDLOGDATA__SIGNPOST:
225 signpost = oslogdarwin_redactedlogdata_signpost__get(ld);
226 replay_redacted_signpost(signpost);
227 break;
228 case OSLOGDARWIN_REDACTEDLOGDATA__SUBSYSTEM:
229 // Subsystem registration not supported for now.
230 break;
231 case OSLOGDARWIN_REDACTEDLOGDATA__IMAGELOAD:
232 // Image registration not supported for now.
233 break;
234 default:
235 panic("Unsupported redacted Exclaves log type %llu", ld->tag);
236 }
237 }
238
239 static void
os_log_replay_redacted_logs(const oslogdarwin_redactedlogdata_v_s * logs)240 os_log_replay_redacted_logs(const oslogdarwin_redactedlogdata_v_s *logs)
241 {
242 oslogdarwin_redactedlogdata__v_visit(logs, ^(size_t __unused i, const oslogdarwin_redactedlogdata_s *_Nonnull log) {
243 os_log_replay_redacted_log(log);
244 });
245 }
246
247 /*
248 * The log retrieval thread (served by this handler) does not busy loop the
249 * whole time. It sleeps on a conditional variable in the Exclaves log server
250 * and runs only when there are new logs in Exclaves to pick up and to replay.
251 */
252 static void
redacted_log_server_retrieve_logs(__unused void * arg,__unused wait_result_t w)253 redacted_log_server_retrieve_logs(__unused void *arg, __unused wait_result_t w)
254 {
255 os_log_t log = os_log_create(OS_LOG_SUBSYSTEM, EXCLAVES_LOGS_CATEGORY);
256
257 tb_endpoint_t ep = tb_endpoint_create_with_value(TB_TRANSPORT_TYPE_XNU,
258 EXCLAVES_ID_LOGSERVER_EP, TB_ENDPOINT_OPTIONS_NONE);
259 if (ep == NULL) {
260 os_log_error(log, "Failed to create log server endpoint\n");
261 return;
262 }
263
264 oslogdarwin_redactedconsumer_s consumer = {0};
265
266 tb_error_t err = oslogdarwin_redactedconsumer__init(&consumer, ep);
267 if (err != TB_ERROR_SUCCESS) {
268 os_log_error(log, "Failed to initialize log consumer (error: %d)\n", err);
269 return;
270 }
271
272 do {
273 err = oslogdarwin_redactedconsumer_getlogs(&consumer, ^(oslogdarwin_redactedlogdata_v_s logs) {
274 os_log_replay_redacted_logs(&logs);
275 });
276 } while (__probable(err == TB_ERROR_SUCCESS));
277
278 os_log_error(log, "Failed to retrieve logs (error: %d). Exiting.\n", err);
279 }
280
281 #endif // DEVELOPMENT || DEBUG
282
283 kern_return_t
exclaves_oslog_set_trace_mode(uint32_t mode)284 exclaves_oslog_set_trace_mode(uint32_t mode)
285 {
286 if (os_log_disabled() || !oslog_exclaves || !oslog_exclaves_ready) {
287 return KERN_SUCCESS;
288 }
289
290 os_log_t log = os_log_create(OS_LOG_SUBSYSTEM, EXCLAVES_CONFIG_CATEGORY);
291
292 tb_error_t err = oslogdarwin_configadmin_settracemode(&config_admin, mode);
293 if (err == TB_ERROR_SUCCESS) {
294 dbg_counter_inc(&oslog_e_trace_mode_set_count);
295 return KERN_SUCCESS;
296 }
297
298 dbg_counter_inc(&oslog_e_trace_mode_error_count);
299 os_log_error(log, "Failed to set exclaves trace mode (error: %d)\n", err);
300
301 return KERN_FAILURE;
302 }
303
304 static bool
exclaves_oslog_init_config_admin(oslogdarwin_configadmin_s * admin)305 exclaves_oslog_init_config_admin(oslogdarwin_configadmin_s *admin)
306 {
307 os_log_t log = os_log_create(OS_LOG_SUBSYSTEM, EXCLAVES_CONFIG_CATEGORY);
308
309 tb_endpoint_t ep = tb_endpoint_create_with_value(TB_TRANSPORT_TYPE_XNU,
310 EXCLAVES_ID_LOGSERVER_EP, TB_ENDPOINT_OPTIONS_NONE);
311 if (ep == NULL) {
312 os_log_error(log, "Failed to create log server endpoint\n");
313 return false;
314 }
315
316 tb_error_t err = oslogdarwin_configadmin__init(admin, ep);
317 if (err != TB_ERROR_SUCCESS) {
318 os_log_error(log, "Failed to initialize config client (error: %d)\n", err);
319 return false;
320 }
321
322 return true;
323 }
324
325 static kern_return_t
exclaves_oslog_init(void)326 exclaves_oslog_init(void)
327 {
328 if (os_log_disabled()) {
329 printf("Exclaves logging: Disabled by ATM\n");
330 return KERN_SUCCESS;
331 }
332
333 os_log_t log = os_log_create(OS_LOG_SUBSYSTEM, EXCLAVES_LOGS_CATEGORY);
334
335 if (!oslog_exclaves) {
336 os_log(log, "Exclaves logging: Disabled by boot argument\n");
337 return KERN_SUCCESS;
338 }
339
340 if (EXCLAVES_ID_LOGSERVER_EP == EXCLAVES_INVALID_ID) {
341 exclaves_requirement_assert(EXCLAVES_R_LOG_SERVER,
342 "log server not found");
343 return KERN_SUCCESS;
344 }
345
346 if (!exclaves_oslog_init_config_admin(&config_admin)) {
347 return KERN_FAILURE;
348 }
349
350 thread_t oslog_exclaves_thread = THREAD_NULL;
351 #if DEVELOPMENT || DEBUG
352 thread_continue_t log_handler = log_server_retrieve_logs;
353 #else
354 thread_continue_t log_handler = redacted_log_server_retrieve_logs;
355 #endif
356
357 kern_return_t err = kernel_thread_start(log_handler, NULL, &oslog_exclaves_thread);
358 if (err != KERN_SUCCESS) {
359 os_log_error(log, "Exclaves logging: Disabled. Failed to start retrieval thread (error: %d)\n", err);
360 return KERN_FAILURE;
361 }
362 thread_deallocate(oslog_exclaves_thread);
363
364 oslog_exclaves_ready = true;
365 os_log(log, "Exclaves logging: Enabled\n");
366
367 (void) exclaves_oslog_set_trace_mode(atm_get_diagnostic_config());
368
369 return KERN_SUCCESS;
370 }
371 /* Make sure oslog init runs as early as possible so that there's a chance to
372 * see logs for failures.
373 */
374 EXCLAVES_BOOT_TASK(exclaves_oslog_init, EXCLAVES_BOOT_RANK_SECOND);
375
376 #endif // CONFIG_EXCLAVES
377