xref: /xnu-12377.41.6/osfmk/kern/exclaves_log.c (revision bbb1b6f9e71b8cdde6e5cd6f4841f207dee3d828)
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