]> git.saurik.com Git - apple/xnu.git/blob - libkern/os/log_encode.c
5c2e2e9768a8664dd4ce5d1e583ffa0e5e7990d5
[apple/xnu.git] / libkern / os / log_encode.c
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
43 extern boolean_t doprnt_hide_pointers;
44
45 SCALABLE_COUNTER_DEFINE(oslog_p_fmt_invalid_msgcount);
46 SCALABLE_COUNTER_DEFINE(oslog_p_fmt_max_args_msgcount);
47 SCALABLE_COUNTER_DEFINE(oslog_p_truncated_msgcount);
48
49 static bool
50 is_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
71 static void
72 log_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
78 static bool
79 log_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
84 static bool
85 log_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
90 static void
91 log_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 */
108 static void
109 log_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 */
132 static void
133 log_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
139 static void
140 log_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
147 static struct os_log_fmt_range_s
148 log_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
159 static int
160 log_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 */
187 static int
188 log_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
202 static void
203 log_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
231 static bool
232 log_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
267 static int
268 log_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
294 static int
295 log_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
476 static inline size_t
477 write_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
516 static void
517 os_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 */
531 bool
532 os_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 }
578 finish:
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
584 void
585 os_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
597 void
598 os_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 }