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
47TUNABLE(bool, oslog_exclaves, "oslog_exclaves", true);
48
49#if DEVELOPMENT || DEBUG
50
51#define OS_LOG_MAX_SIZE (2048)
52
53SCALABLE_COUNTER_DEFINE(oslog_e_log_count);
54SCALABLE_COUNTER_DEFINE(oslog_e_log_dropped_count);
55SCALABLE_COUNTER_DEFINE(oslog_e_metadata_count);
56SCALABLE_COUNTER_DEFINE(oslog_e_metadata_dropped_count);
57SCALABLE_COUNTER_DEFINE(oslog_e_signpost_count);
58SCALABLE_COUNTER_DEFINE(oslog_e_signpost_dropped_count);
59SCALABLE_COUNTER_DEFINE(oslog_e_query_count);
60SCALABLE_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 */
67static inline void
68interpose_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
74static size_t
75oslogdarwin_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
93static void
94os_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
130static void
131os_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 */
143static void
144log_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
184static void
185replay_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
194static void
195replay_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
204static void
205os_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
227static void
228os_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 */
240static void
241redacted_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
271static kern_return_t
272exclaves_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 */
309EXCLAVES_BOOT_TASK(exclaves_oslog_init, EXCLAVES_BOOT_RANK_SECOND);
310
311#endif // CONFIG_EXCLAVES
312