]> git.saurik.com Git - apple/xnu.git/blame - libkern/os/log_encode.c
xnu-7195.101.1.tar.gz
[apple/xnu.git] / libkern / os / log_encode.c
CommitLineData
c3c9b80d
A
1/*
2 * Copyright (c) 2015-2020 Apple Inc. All rights reserved.
3 *
4 * @APPLE_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. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24#include <stdbool.h>
25#include <firehose/tracepoint_private.h>
26#include <kern/assert.h>
27#include <kern/counter.h>
28#include <kern/locks.h>
29#include <pexpert/pexpert.h>
30#include <sys/param.h>
31
32#if __has_feature(ptrauth_calls)
33#include <mach/vm_param.h>
34#include <ptrauth.h>
35#endif /* __has_feature(ptrauth_calls) */
36
37#include "log_encode.h"
38#include "log_mem.h"
39
40#define isdigit(ch) (((ch) >= '0') && ((ch) <= '9'))
41#define log_context_cursor(ctx) &(ctx)->ctx_hdr->hdr_data[(ctx)->ctx_content_off]
42
43extern boolean_t doprnt_hide_pointers;
44
45SCALABLE_COUNTER_DEFINE(oslog_p_fmt_invalid_msgcount);
46SCALABLE_COUNTER_DEFINE(oslog_p_fmt_max_args_msgcount);
47SCALABLE_COUNTER_DEFINE(oslog_p_truncated_msgcount);
48
49static bool
50is_kernel_pointer(void *arg, size_t arg_len)
51{
52 if (arg_len < sizeof(void *)) {
53 return false;
54 }
55
56 unsigned long long value = 0;
57 assert(arg_len <= sizeof(value));
58 (void) memcpy(&value, arg, arg_len);
59
60#if __has_feature(ptrauth_calls)
61 /**
62 * Strip out the pointer authentication code before
63 * checking whether the pointer is a kernel address.
64 */
65 value = (unsigned long long)VM_KERNEL_STRIP_PTR(value);
66#endif /* __has_feature(ptrauth_calls) */
67
68 return value >= VM_MIN_KERNEL_AND_KEXT_ADDRESS && value <= VM_MAX_KERNEL_ADDRESS;
69}
70
71static void
72log_context_cursor_advance(os_log_context_t ctx, size_t amount)
73{
74 ctx->ctx_content_off += amount;
75 assert(log_context_cursor(ctx) <= (ctx->ctx_buffer + ctx->ctx_buffer_sz));
76}
77
78static bool
79log_fits(os_log_context_t ctx, size_t data_size)
80{
81 return (ctx->ctx_content_off + data_size) <= ctx->ctx_content_sz;
82}
83
84static bool
85log_fits_cmd(os_log_context_t ctx, size_t data_size)
86{
87 return log_fits(ctx, sizeof(*ctx->ctx_hdr) + data_size);
88}
89
90static void
91log_range_update(os_log_fmt_range_t range, uint16_t offset, uint16_t length)
92{
93 range->offset = offset;
94 /*
95 * Truncated flag may have already been set earlier, hence do not
96 * overwrite it blindly.
97 */
98 if (length < range->length) {
99 range->truncated = true;
100 }
101 range->length = length;
102}
103
104/*
105 * Stores a command in the main section. The value itself is wrapped in
106 * the os_log_fmt_cmd_t struct.
107 */
108static void
109log_add_cmd(os_log_context_t ctx, os_log_fmt_cmd_type_t type, uint8_t flags,
110 void *arg, size_t arg_size)
111{
112 os_log_fmt_cmd_t cmd;
113 const size_t cmd_sz = sizeof(*cmd) + arg_size;
114
115 assert(log_fits_cmd(ctx, cmd_sz));
116 assert(arg_size <= UINT8_MAX);
117
118 cmd = (os_log_fmt_cmd_t)log_context_cursor(ctx);
119 cmd->cmd_type = type;
120 cmd->cmd_flags = flags;
121 cmd->cmd_size = (uint8_t)arg_size;
122 (void) memcpy(cmd->cmd_data, arg, cmd->cmd_size);
123
124 assert(cmd_sz == sizeof(*cmd) + cmd->cmd_size);
125 log_context_cursor_advance(ctx, cmd_sz);
126}
127
128/*
129 * Collect details about argument which needs to be stored in the pubdata
130 * section.
131 */
132static void
133log_collect_public_range_data(os_log_context_t ctx, os_log_fmt_range_t range, void *arg)
134{
135 ctx->ctx_pubdata[ctx->ctx_pubdata_cnt++] = (char *)arg;
136 ctx->ctx_pubdata_sz += range->length;
137}
138
139static void
140log_add_range_data(os_log_context_t ctx, os_log_fmt_range_t range, void *arg)
141{
142 assert(log_fits(ctx, range->length));
143 (void) memcpy(log_context_cursor(ctx), arg, range->length);
144 log_context_cursor_advance(ctx, range->length);
145}
146
147static struct os_log_fmt_range_s
148log_create_range(os_log_context_t ctx, size_t arg_len)
149{
150 const size_t final_arg_len = MIN(arg_len, UINT16_MAX);
151
152 return (struct os_log_fmt_range_s) {
153 .offset = ctx->ctx_pubdata_sz,
154 .length = (uint16_t)final_arg_len,
155 .truncated = (final_arg_len < arg_len)
156 };
157}
158
159static int
160log_add_range_arg(os_log_context_t ctx, os_log_fmt_cmd_type_t type, os_log_fmt_cmd_flags_t flags,
161 void *arg, size_t arg_len)
162{
163 struct os_log_fmt_range_s range;
164
165 if (!log_fits_cmd(ctx, sizeof(range))) {
166 return ENOMEM;
167 }
168
169 range = log_create_range(ctx, arg_len);
170
171 if (flags == OSLF_CMD_FLAG_PUBLIC) {
172 if (ctx->ctx_pubdata_cnt == OS_LOG_MAX_PUB_ARGS) {
173 return ENOMEM;
174 }
175 assert(ctx->ctx_pubdata_cnt < OS_LOG_MAX_PUB_ARGS);
176 log_collect_public_range_data(ctx, &range, arg);
177 }
178 log_add_cmd(ctx, type, flags, &range, sizeof(range));
179 ctx->ctx_hdr->hdr_cmd_cnt++;
180
181 return 0;
182}
183
184/*
185 * Adds a scalar argument value to the main section.
186 */
187static int
188log_add_arg(os_log_context_t ctx, os_log_fmt_cmd_type_t type, void *arg, size_t arg_len)
189{
190 assert(type == OSLF_CMD_TYPE_COUNT || type == OSLF_CMD_TYPE_SCALAR);
191 assert(arg_len < UINT16_MAX);
192
193 if (log_fits_cmd(ctx, arg_len)) {
194 log_add_cmd(ctx, type, OSLF_CMD_FLAG_PUBLIC, arg, arg_len);
195 ctx->ctx_hdr->hdr_cmd_cnt++;
196 return 0;
197 }
198
199 return ENOMEM;
200}
201
202static void
203log_encode_public_data(os_log_context_t ctx)
204{
205 const uint16_t orig_content_off = ctx->ctx_content_off;
206 os_log_fmt_hdr_t const hdr = ctx->ctx_hdr;
207 os_log_fmt_cmd_t cmd = (os_log_fmt_cmd_t)hdr->hdr_data;
208
209 assert(ctx->ctx_pubdata_cnt <= hdr->hdr_cmd_cnt);
210
211 for (int i = 0, pub_i = 0; i < hdr->hdr_cmd_cnt; i++, cmd = (os_log_fmt_cmd_t)(cmd->cmd_data + cmd->cmd_size)) {
212 if (cmd->cmd_type != OSLF_CMD_TYPE_STRING) {
213 continue;
214 }
215
216 os_log_fmt_range_t const range __attribute__((aligned(8))) = (os_log_fmt_range_t)&cmd->cmd_data;
217
218 // Fix offset and length of the argument data in the hdr.
219 log_range_update(range, ctx->ctx_content_off - orig_content_off,
220 MIN(range->length, ctx->ctx_content_sz - ctx->ctx_content_off));
221
222 if (range->truncated) {
223 ctx->ctx_truncated = true;
224 }
225
226 assert(pub_i < ctx->ctx_pubdata_cnt);
227 log_add_range_data(ctx, range, ctx->ctx_pubdata[pub_i++]);
228 }
229}
230
231static bool
232log_expand(os_log_context_t ctx, size_t new_size)
233{
234 assert(new_size > ctx->ctx_buffer_sz);
235
236 if (!oslog_is_safe()) {
237 return false;
238 }
239
240 size_t final_size = new_size;
241
242 void *buf = logmem_alloc(ctx->ctx_logmem, &final_size);
243 if (!buf) {
244 return false;
245 }
246 assert(final_size >= new_size);
247
248 // address length header + already stored data
249 const size_t hdr_size = (uint8_t *)ctx->ctx_hdr - ctx->ctx_buffer;
250 const size_t copy_size = hdr_size + sizeof(*ctx->ctx_hdr) + ctx->ctx_content_sz;
251 assert(copy_size <= new_size);
252 (void) memcpy(buf, ctx->ctx_buffer, copy_size);
253
254 if (ctx->ctx_allocated) {
255 logmem_free(ctx->ctx_logmem, ctx->ctx_buffer, ctx->ctx_buffer_sz);
256 }
257
258 ctx->ctx_buffer = buf;
259 ctx->ctx_buffer_sz = final_size;
260 ctx->ctx_content_sz = (uint16_t)(ctx->ctx_buffer_sz - hdr_size - sizeof(*ctx->ctx_hdr));
261 ctx->ctx_hdr = (os_log_fmt_hdr_t)&ctx->ctx_buffer[hdr_size];
262 ctx->ctx_allocated = true;
263
264 return true;
265}
266
267static int
268log_encode_fmt_arg(void *arg, size_t arg_len, os_log_fmt_cmd_type_t type, os_log_context_t ctx)
269{
270 int rc = 0;
271
272 switch (type) {
273 case OSLF_CMD_TYPE_COUNT:
274 case OSLF_CMD_TYPE_SCALAR:
275 // Scrub kernel pointers.
276 if (doprnt_hide_pointers && is_kernel_pointer(arg, arg_len)) {
277 rc = log_add_range_arg(ctx, type, OSLF_CMD_FLAG_PRIVATE, NULL, 0);
278 ctx->ctx_hdr->hdr_flags |= OSLF_HDR_FLAG_HAS_PRIVATE;
279 } else {
280 rc = log_add_arg(ctx, type, arg, arg_len);
281 }
282 break;
283 case OSLF_CMD_TYPE_STRING:
284 rc = log_add_range_arg(ctx, type, OSLF_CMD_FLAG_PUBLIC, arg, arg_len);
285 ctx->ctx_hdr->hdr_flags |= OSLF_HDR_FLAG_HAS_NON_SCALAR;
286 break;
287 default:
288 panic("Unsupported log value type");
289 }
290
291 return rc;
292}
293
294static int
295log_encode_fmt(os_log_context_t ctx, const char *format, va_list args)
296{
297 const char *percent = strchr(format, '%');
298
299 while (percent != NULL) {
300 ++percent;
301
302 if (percent[0] == '%') {
303 percent = strchr(percent + 1, '%'); // Find next format after %%
304 continue;
305 }
306
307 struct os_log_format_value_s value;
308 int type = OST_INT;
309 int prec = 0;
310 char ch;
311
312 for (bool done = false; !done; percent++) {
313 int err = 0;
314
315 switch (ch = percent[0]) {
316 /* type of types or other */
317 case 'l': // longer
318 type++;
319 break;
320
321 case 'h': // shorter
322 type--;
323 break;
324
325 case 'z':
326 type = OST_SIZE;
327 break;
328
329 case 'j':
330 type = OST_INTMAX;
331 break;
332
333 case 't':
334 type = OST_PTRDIFF;
335 break;
336
337 case 'q':
338 type = OST_LONGLONG;
339 break;
340
341 case '.': // precision
342 if ((percent[1]) == '*') {
343 prec = va_arg(args, int);
344 err = log_encode_fmt_arg(&prec, sizeof(prec), OSLF_CMD_TYPE_COUNT, ctx);
345 if (slowpath(err)) {
346 return err;
347 }
348 percent++;
349 continue;
350 } else {
351 // we have to read the precision and do the right thing
352 const char *fmt = percent + 1;
353 prec = 0;
354 while (isdigit(ch = *fmt++)) {
355 prec = 10 * prec + (ch - '0');
356 }
357
358 if (prec > 1024) {
359 prec = 1024;
360 }
361
362 err = log_encode_fmt_arg(&prec, sizeof(prec), OSLF_CMD_TYPE_COUNT, ctx);
363 }
364 break;
365
366 case '-': // left-align
367 case '+': // force sign
368 case ' ': // prefix non-negative with space
369 case '#': // alternate
370 case '\'': // group by thousands
371 break;
372
373 /* fixed types */
374 case 'd': // integer
375 case 'i': // integer
376 case 'o': // octal
377 case 'u': // unsigned
378 case 'x': // hex
379 case 'X': // upper-hex
380 switch (type) {
381 case OST_CHAR:
382 value.type.ch = (char) va_arg(args, int);
383 err = log_encode_fmt_arg(&value.type.ch, sizeof(value.type.ch), OSLF_CMD_TYPE_SCALAR, ctx);
384 break;
385
386 case OST_SHORT:
387 value.type.s = (short) va_arg(args, int);
388 err = log_encode_fmt_arg(&value.type.s, sizeof(value.type.s), OSLF_CMD_TYPE_SCALAR, ctx);
389 break;
390
391 case OST_INT:
392 value.type.i = va_arg(args, int);
393 err = log_encode_fmt_arg(&value.type.i, sizeof(value.type.i), OSLF_CMD_TYPE_SCALAR, ctx);
394 break;
395
396 case OST_LONG:
397 value.type.l = va_arg(args, long);
398 err = log_encode_fmt_arg(&value.type.l, sizeof(value.type.l), OSLF_CMD_TYPE_SCALAR, ctx);
399 break;
400
401 case OST_LONGLONG:
402 value.type.ll = va_arg(args, long long);
403 err = log_encode_fmt_arg(&value.type.ll, sizeof(value.type.ll), OSLF_CMD_TYPE_SCALAR, ctx);
404 break;
405
406 case OST_SIZE:
407 value.type.z = va_arg(args, size_t);
408 err = log_encode_fmt_arg(&value.type.z, sizeof(value.type.z), OSLF_CMD_TYPE_SCALAR, ctx);
409 break;
410
411 case OST_INTMAX:
412 value.type.im = va_arg(args, intmax_t);
413 err = log_encode_fmt_arg(&value.type.im, sizeof(value.type.im), OSLF_CMD_TYPE_SCALAR, ctx);
414 break;
415
416 case OST_PTRDIFF:
417 value.type.pd = va_arg(args, ptrdiff_t);
418 err = log_encode_fmt_arg(&value.type.pd, sizeof(value.type.pd), OSLF_CMD_TYPE_SCALAR, ctx);
419 break;
420
421 default:
422 return EINVAL;
423 }
424 done = true;
425 break;
426
427 case 'p': // pointer
428 value.type.p = va_arg(args, void *);
429 err = log_encode_fmt_arg(&value.type.p, sizeof(value.type.p), OSLF_CMD_TYPE_SCALAR, ctx);
430 done = true;
431 break;
432
433 case 'c': // char
434 value.type.ch = (char) va_arg(args, int);
435 err = log_encode_fmt_arg(&value.type.ch, sizeof(value.type.ch), OSLF_CMD_TYPE_SCALAR, ctx);
436 done = true;
437 break;
438
439 case 's': // string
440 value.type.pch = va_arg(args, char *);
441 if (prec == 0 && value.type.pch) {
442 prec = (int) strlen(value.type.pch) + 1;
443 }
444 err = log_encode_fmt_arg(value.type.pch, prec, OSLF_CMD_TYPE_STRING, ctx);
445 prec = 0;
446 done = true;
447 break;
448
449 case 'm':
450 value.type.i = 0; // Does %m make sense in the kernel?
451 err = log_encode_fmt_arg(&value.type.i, sizeof(value.type.i), OSLF_CMD_TYPE_SCALAR, ctx);
452 done = true;
453 break;
454
455 default:
456 if (isdigit(ch)) { // [0-9]
457 continue;
458 }
459 return EINVAL;
460 }
461
462 if (slowpath(err)) {
463 return err;
464 }
465
466 if (done) {
467 percent = strchr(percent, '%'); // Find next format
468 break;
469 }
470 }
471 }
472
473 return 0;
474}
475
476static inline size_t
477write_address_location(uint8_t buf[static sizeof(uint64_t)],
478 void *dso, const void *address, firehose_tracepoint_flags_t *flags, bool driverKit)
479{
480 uintptr_t shift_addr = (uintptr_t)address - (uintptr_t)dso;
481
482 kc_format_t kcformat = KCFormatUnknown;
483 __assert_only bool result = PE_get_primary_kc_format(&kcformat);
484 assert(result);
485
486 if (kcformat == KCFormatStatic || kcformat == KCFormatKCGEN) {
487 *flags = _firehose_tracepoint_flags_pc_style_shared_cache;
488 memcpy(buf, (uint32_t[]){ (uint32_t)shift_addr }, sizeof(uint32_t));
489 return sizeof(uint32_t);
490 }
491
492 /*
493 * driverKit will have the dso set as MH_EXECUTE (it is logging from a
494 * syscall in the kernel) but needs logd to parse the address as an
495 * absolute pc.
496 */
497 kernel_mach_header_t *mh = dso;
498 if (mh->filetype == MH_EXECUTE && !driverKit) {
499 *flags = _firehose_tracepoint_flags_pc_style_main_exe;
500 memcpy(buf, (uint32_t[]){ (uint32_t)shift_addr }, sizeof(uint32_t));
501 return sizeof(uint32_t);
502 }
503
504 *flags = _firehose_tracepoint_flags_pc_style_absolute;
505 shift_addr = driverKit ? (uintptr_t)address : VM_KERNEL_UNSLIDE(address);
506 size_t len = sizeof(uintptr_t);
507
508#if __LP64__
509 len = 6; // 48 bits are enough
510#endif
511 memcpy(buf, (uintptr_t[]){ shift_addr }, len);
512
513 return len;
514}
515
516static void
517os_log_encode_location(os_log_context_t ctx, void *addr, void *dso, bool driverKit,
518 firehose_tracepoint_flags_t *ft_flags)
519{
520 const size_t hdr_size = write_address_location(ctx->ctx_buffer, dso, addr, ft_flags, driverKit);
521 ctx->ctx_hdr = (os_log_fmt_hdr_t)&ctx->ctx_buffer[hdr_size];
522 ctx->ctx_content_sz = (uint16_t)(ctx->ctx_buffer_sz - hdr_size - sizeof(*ctx->ctx_hdr));
523}
524
525/*
526 * Encodes argument (meta)data into a format consumed by libtrace. Stores
527 * metadada for all arguments first. Metadata also include scalar argument
528 * values. Second step saves data which are encoded separately from respective
529 * metadata (like strings).
530 */
531bool
532os_log_context_encode(os_log_context_t ctx, const char *fmt, va_list args, void *addr, void *dso, bool driverKit)
533{
534 os_log_encode_location(ctx, addr, dso, driverKit, &ctx->ctx_ft_flags);
535
536 va_list args_copy;
537 va_copy(args_copy, args);
538
539 int rc = log_encode_fmt(ctx, fmt, args);
540
541 va_end(args_copy);
542
543 switch (rc) {
544 case EINVAL:
545 // Bogus/Unsupported fmt string
546 counter_inc(&oslog_p_fmt_invalid_msgcount);
547 return false;
548 case ENOMEM:
549 /*
550 * The fmt contains unreasonable number of arguments (> 32) and
551 * we ran out of space. We could call log_expand()
552 * here and retry. However, using such formatting strings rather
553 * seem like a misuse of the logging system, hence error.
554 */
555 counter_inc(&oslog_p_fmt_max_args_msgcount);
556 return false;
557 case 0:
558 break;
559 default:
560 panic("unhandled return value");
561 }
562
563 if (ctx->ctx_pubdata_sz == 0) {
564 goto finish;
565 }
566
567 if (!log_fits(ctx, ctx->ctx_pubdata_sz)) {
568 size_t space_needed = log_context_cursor(ctx) + ctx->ctx_pubdata_sz - ctx->ctx_buffer;
569 space_needed = MIN(space_needed, logmem_max_size(ctx->ctx_logmem));
570 (void) log_expand(ctx, space_needed);
571 }
572
573 log_encode_public_data(ctx);
574
575 if (ctx->ctx_truncated) {
576 counter_inc(&oslog_p_truncated_msgcount);
577 }
578finish:
579 ctx->ctx_content_sz = (uint16_t)(log_context_cursor(ctx) - ctx->ctx_buffer);
580 ctx->ctx_content_off = 0;
581 return true;
582}
583
584void
585os_log_context_init(os_log_context_t ctx, logmem_t *logmem, uint8_t *buffer, size_t buffer_sz)
586{
587 assert(logmem);
588 assert(buffer);
589 assert(buffer_sz > 0);
590
591 bzero(ctx, sizeof(*ctx));
592 ctx->ctx_logmem = logmem;
593 ctx->ctx_buffer = buffer;
594 ctx->ctx_buffer_sz = buffer_sz;
595}
596
597void
598os_log_context_free(os_log_context_t ctx)
599{
600 if (ctx->ctx_allocated) {
601 logmem_free(ctx->ctx_logmem, ctx->ctx_buffer, ctx->ctx_buffer_sz);
602 }
603}