xref: /xnu-10063.121.3/osfmk/kern/exclaves_log.c (revision 2c2f96dc2b9a4408a43d3150ae9c105355ca3daa)
1 /*
2  * Copyright (c) 2023 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_resource.h"
42 
43 #define EXCLAVES_ID_LOGSERVER_EP                     \
44     (exclaves_service_lookup(EXCLAVES_DOMAIN_KERNEL, \
45     "com.apple.service.LogServer_xnuproxy"))
46 
47 TUNABLE(bool, oslog_exclaves, "oslog_exclaves", true);
48 
49 #if DEVELOPMENT || DEBUG
50 
51 #define OS_LOG_MAX_SIZE (2048)
52 
53 SCALABLE_COUNTER_DEFINE(oslog_e_log_count);
54 SCALABLE_COUNTER_DEFINE(oslog_e_log_dropped_count);
55 SCALABLE_COUNTER_DEFINE(oslog_e_metadata_count);
56 SCALABLE_COUNTER_DEFINE(oslog_e_metadata_dropped_count);
57 SCALABLE_COUNTER_DEFINE(oslog_e_signpost_count);
58 SCALABLE_COUNTER_DEFINE(oslog_e_signpost_dropped_count);
59 SCALABLE_COUNTER_DEFINE(oslog_e_query_count);
60 SCALABLE_COUNTER_DEFINE(oslog_e_error_query_count);
61 
62 /*
63  * Interpose kernel UUID until Exclaves cstring harvesting is in place. This
64  * workaround allows to send out proper logs and signposts instead of plain
65  * strings. As a result, logs and signposts will be attributed to the kernel.
66  */
67 static inline void
interpose_kernel_uuid(uint8_t * ld_data,__assert_only size_t ld_data_size)68 interpose_kernel_uuid(uint8_t *ld_data, __assert_only size_t ld_data_size)
69 {
70 	assert3u(sizeof(uint32_t) + sizeof(uuid_t), <, ld_data_size);
71 	memcpy(&ld_data[sizeof(uint32_t)], kernel_uuid, sizeof(uuid_t));
72 }
73 
74 static size_t
oslogdarwin_logdata_data(const oslogdarwin_logdata_s * ld,uint8_t * ld_data,size_t ld_data_size)75 oslogdarwin_logdata_data(const oslogdarwin_logdata_s *ld, uint8_t *ld_data, size_t ld_data_size)
76 {
77 	__block size_t count = 0;
78 
79 	logbyte__v_visit(&ld->data, ^(size_t i, const uint8_t item) {
80 		/*
81 		 * logbyte__v_visit() does not provide means to stop the iteration
82 		 * and so the index is being checked not to overflow ld_data
83 		 * array.
84 		 */
85 		if (i < ld_data_size) {
86 		        ld_data[i] = item;
87 		}
88 		count++;
89 	});
90 	return count;
91 }
92 
93 static void
os_log_replay_log(const oslogdarwin_logdata_s * ld,uint8_t * ld_data,size_t ld_data_size)94 os_log_replay_log(const oslogdarwin_logdata_s *ld, uint8_t *ld_data, size_t ld_data_size)
95 {
96 	const size_t ld_size = oslogdarwin_logdata_data(ld, ld_data, ld_data_size);
97 	assert3u(ld_size, <=, ld_data_size);
98 	assert3u(ld->pubsize, <=, ld_size);
99 
100 	firehose_tracepoint_id_u ftid = {
101 		.ftid_value = ld->ftid
102 	};
103 
104 	switch (ftid.ftid._namespace) {
105 	case firehose_tracepoint_namespace_metadata:
106 		counter_inc(&oslog_e_metadata_count);
107 		if (!os_log_encoded_metadata(ftid, ld->stamp, ld_data, ld_size)) {
108 			counter_inc(&oslog_e_metadata_dropped_count);
109 		}
110 		break;
111 	case firehose_tracepoint_namespace_log:
112 		counter_inc(&oslog_e_log_count);
113 		interpose_kernel_uuid(ld_data, ld_size);
114 		if (!os_log_encoded_log(ftid, ld->stamp, ld_data, ld_size, ld->pubsize)) {
115 			counter_inc(&oslog_e_log_dropped_count);
116 		}
117 		break;
118 	case firehose_tracepoint_namespace_signpost:
119 		counter_inc(&oslog_e_signpost_count);
120 		interpose_kernel_uuid(ld_data, ld_size);
121 		if (!os_log_encoded_signpost(ftid, ld->stamp, ld_data, ld_size, ld->pubsize)) {
122 			counter_inc(&oslog_e_signpost_dropped_count);
123 		}
124 		break;
125 	default:
126 		panic("Unsupported Exclaves log type %d", ftid.ftid._namespace);
127 	}
128 }
129 
130 static void
os_log_replay_logs(const oslogdarwin_logdata_v_s * logs,uint8_t * log_buffer,size_t log_buffer_size)131 os_log_replay_logs(const oslogdarwin_logdata_v_s *logs, uint8_t *log_buffer, size_t log_buffer_size)
132 {
133 	oslogdarwin_logdata__v_visit(logs, ^(size_t __unused i, const oslogdarwin_logdata_s *_Nonnull log) {
134 		os_log_replay_log(log, log_buffer, log_buffer_size);
135 	});
136 }
137 
138 /*
139  * The log retrieval thread (served by this handler) does not busy loop the
140  * whole time. It sleeps on a conditional variable in the Exclaves log server
141  * and runs only when there are new logs in Exclaves to pick up and to replay.
142  */
143 static void
log_server_retrieve_logs(void * arg,__unused wait_result_t w)144 log_server_retrieve_logs(void *arg, __unused wait_result_t w)
145 {
146 	os_log_t log = arg;
147 
148 	tb_endpoint_t ep = tb_endpoint_create_with_value(TB_TRANSPORT_TYPE_XNU,
149 	    EXCLAVES_ID_LOGSERVER_EP, TB_ENDPOINT_OPTIONS_NONE);
150 	if (ep == NULL) {
151 		os_log_error(log, "Exclaves logging: Failed to create the endpoint\n");
152 		return;
153 	}
154 
155 	oslogdarwin_consumer_s log_server = {0};
156 
157 	tb_error_t err = oslogdarwin_consumer__init(&log_server, ep);
158 	if (err != TB_ERROR_SUCCESS) {
159 		os_log_error(log, "Exclaves logging: Failed to initialize client with %d\n", err);
160 		return;
161 	}
162 
163 	uint8_t *log_buffer = kalloc_data_tag(OS_LOG_MAX_SIZE, Z_WAITOK_ZERO, VM_KERN_MEMORY_LOG);
164 	if (!log_buffer) {
165 		os_log_error(log, "Exclaves logging: Failed to allocate a log buffer\n");
166 		return;
167 	}
168 
169 	do {
170 		err = oslogdarwin_consumer_getlogs(&log_server, ^(oslogdarwin_logdata_v_s logs) {
171 			os_log_replay_logs(&logs, log_buffer, OS_LOG_MAX_SIZE);
172 			counter_inc(&oslog_e_query_count);
173 		});
174 	} while (__probable(err == TB_ERROR_SUCCESS));
175 
176 	kfree_data(log_buffer, OS_LOG_MAX_SIZE);
177 
178 	counter_inc(&oslog_e_error_query_count);
179 	os_log_error(log, "Exclaves logging: Failed to retrieve logs, error: %d. Exiting.\n", err);
180 }
181 
182 #else // DEVELOPMENT || DEBUG
183 
184 static void
replay_redacted_log(const oslogdarwin_redactedlogdata_log_s * log)185 replay_redacted_log(const oslogdarwin_redactedlogdata_log_s *log)
186 {
187 	uuid_string_t uuidstr;
188 	uuid_unparse(log->uuid, uuidstr);
189 
190 	os_log_at_time(OS_LOG_DEFAULT, (os_log_type_t)log->type, log->stamp, "log,%s,%0x",
191 	    uuidstr, log->offset);
192 }
193 
194 static void
replay_redacted_signpost(const oslogdarwin_redactedlogdata_signpost_s * signpost)195 replay_redacted_signpost(const oslogdarwin_redactedlogdata_signpost_s *signpost)
196 {
197 	uuid_string_t uuidstr;
198 	uuid_unparse(signpost->uuid, uuidstr);
199 
200 	os_log_at_time(OS_LOG_DEFAULT, OS_LOG_TYPE_DEBUG, signpost->stamp, "signpost,%s,%0x,%0x,%u,%u",
201 	    uuidstr, signpost->fmtOffset, signpost->nameOffset, signpost->type, signpost->scope);
202 }
203 
204 static void
os_log_replay_redacted_log(const oslogdarwin_redactedlogdata_s * ld)205 os_log_replay_redacted_log(const oslogdarwin_redactedlogdata_s *ld)
206 {
207 	const oslogdarwin_redactedlogdata_log_s *log;
208 	const oslogdarwin_redactedlogdata_signpost_s *signpost;
209 
210 	switch (ld->tag) {
211 	case OSLOGDARWIN_REDACTEDLOGDATA__LOG:
212 		log = oslogdarwin_redactedlogdata_log__get(ld);
213 		replay_redacted_log(log);
214 		break;
215 	case OSLOGDARWIN_REDACTEDLOGDATA__SIGNPOST:
216 		signpost = oslogdarwin_redactedlogdata_signpost__get(ld);
217 		replay_redacted_signpost(signpost);
218 		break;
219 	case OSLOGDARWIN_REDACTEDLOGDATA__SUBSYSTEM:
220 		// Subsystem registration not supported for now.
221 		break;
222 	default:
223 		panic("Unsupported redacted Exclaves log type %llu", ld->tag);
224 	}
225 }
226 
227 static void
os_log_replay_redacted_logs(const oslogdarwin_redactedlogdata_v_s * logs)228 os_log_replay_redacted_logs(const oslogdarwin_redactedlogdata_v_s *logs)
229 {
230 	oslogdarwin_redactedlogdata__v_visit(logs, ^(size_t __unused i, const oslogdarwin_redactedlogdata_s *_Nonnull log) {
231 		os_log_replay_redacted_log(log);
232 	});
233 }
234 
235 /*
236  * The log retrieval thread (served by this handler) does not busy loop the
237  * whole time. It sleeps on a conditional variable in the Exclaves log server
238  * and runs only when there are new logs in Exclaves to pick up and to replay.
239  */
240 static void
redacted_log_server_retrieve_logs(void * arg,__unused wait_result_t w)241 redacted_log_server_retrieve_logs(void *arg, __unused wait_result_t w)
242 {
243 	os_log_t log = arg;
244 
245 	tb_endpoint_t ep = tb_endpoint_create_with_value(TB_TRANSPORT_TYPE_XNU,
246 	    EXCLAVES_ID_LOGSERVER_EP, TB_ENDPOINT_OPTIONS_NONE);
247 	if (ep == NULL) {
248 		os_log_error(log, "Exclaves logging: Failed to create the endpoint\n");
249 		return;
250 	}
251 
252 	oslogdarwin_redactedconsumer_s log_server = {0};
253 
254 	tb_error_t err = oslogdarwin_redactedconsumer__init(&log_server, ep);
255 	if (err != TB_ERROR_SUCCESS) {
256 		os_log_error(log, "Exclaves logging: Failed to initialize client with %d\n", err);
257 		return;
258 	}
259 
260 	do {
261 		err = oslogdarwin_redactedconsumer_getlogs(&log_server, ^(oslogdarwin_redactedlogdata_v_s logs) {
262 			os_log_replay_redacted_logs(&logs);
263 		});
264 	} while (__probable(err == TB_ERROR_SUCCESS));
265 
266 	os_log_error(log, "Exclaves logging: Failed to retrieve logs, error: %d. Exiting.\n", err);
267 }
268 
269 #endif // DEVELOPMENT || DEBUG
270 
271 static kern_return_t
exclaves_oslog_init(void)272 exclaves_oslog_init(void)
273 {
274 	extern bool os_log_disabled(void);
275 
276 	if (os_log_disabled()) {
277 		printf("Exclaves logging: Disabled by ATM\n");
278 		return KERN_SUCCESS;
279 	}
280 
281 	os_log_t log = os_log_create(OS_LOG_XNU_SUBSYSTEM, "oslog-exclaves-handler");
282 
283 	if (!oslog_exclaves) {
284 		os_log(log, "Exclaves logging: Disabled by boot argument\n");
285 		return KERN_SUCCESS;
286 	}
287 
288 	thread_t oslog_exclaves_thread = THREAD_NULL;
289 #if DEVELOPMENT || DEBUG
290 	thread_continue_t log_handler = log_server_retrieve_logs;
291 #else
292 	thread_continue_t log_handler = redacted_log_server_retrieve_logs;
293 #endif
294 
295 	kern_return_t err = kernel_thread_start(log_handler, log, &oslog_exclaves_thread);
296 	if (err != KERN_SUCCESS) {
297 		os_log_error(log, "Exclaves logging: Disabled. Starting the thread failed with %d\n", err);
298 		return KERN_FAILURE;
299 	}
300 	thread_deallocate(oslog_exclaves_thread);
301 
302 	os_log(log, "Exclaves logging: Enabled\n");
303 
304 	return KERN_SUCCESS;
305 }
306 /* Make sure oslog init runs as early as possible so that there's a chance to
307  * see logs for failures.
308  */
309 EXCLAVES_BOOT_TASK(exclaves_oslog_init, EXCLAVES_BOOT_RANK_SECOND);
310 
311 #endif // CONFIG_EXCLAVES
312