1/*
2 * Copyright (c) 2015-2016 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#ifndef log_encode_h
25#define log_encode_h
26
27#include "log_encode_types.h"
28#include <sys/param.h>
29
30#if __has_feature(ptrauth_calls)
31#include <mach/vm_param.h>
32#include <ptrauth.h>
33#endif /* __has_feature(ptrauth_calls) */
34
35#ifdef KERNEL
36#define isdigit(ch) (((ch) >= '0') && ((ch) <= '9'))
37extern boolean_t doprnt_hide_pointers;
38#endif
39
40static bool
41_encode_data(os_log_buffer_value_t content, const void *arg, uint16_t arg_len, os_log_buffer_context_t context)
42{
43 struct os_log_arginfo_s arginfo;
44 void *databuf;
45
46 if (content->flags & OS_LOG_CONTENT_FLAG_PRIVATE) {
47 databuf = context->privdata + context->privdata_off;
48 arginfo.length = MIN(arg_len, (context->privdata_sz - context->privdata_off));
49 arginfo.offset = context->privdata_off;
50 } else {
51 databuf = context->pubdata + context->pubdata_off;
52 arginfo.length = MIN(arg_len, (context->pubdata_sz - context->pubdata_off));
53 arginfo.offset = context->pubdata_off;
54 }
55
56 if (context->arg_content_sz > 0) {
57 arginfo.length = MIN(context->arg_content_sz, arginfo.length);
58 }
59
60 memcpy(content->value, &arginfo, sizeof(arginfo));
61 content->size = sizeof(arginfo);
62
63 if (arginfo.length) {
64 if (content->type == OS_LOG_BUFFER_VALUE_TYPE_STRING
65#ifndef KERNEL
66 || content->type == OS_LOG_BUFFER_VALUE_TYPE_OBJECT
67#endif
68 ) {
69 strlcpy(databuf, arg, arginfo.length);
70 } else {
71 memcpy(databuf, arg, arginfo.length);
72 }
73 }
74
75 if (content->flags & OS_LOG_CONTENT_FLAG_PRIVATE) {
76 context->privdata_off += arginfo.length;
77 } else {
78 context->pubdata_off += arginfo.length;
79 }
80
81 context->content_off += sizeof(*content) + content->size;
82 context->arg_content_sz = 0;
83
84 return true;
85}
86
87#ifndef KERNEL
88static void
89_os_log_parse_annotated(char *annotated, const char **visibility, const char **library, const char **type)
90{
91 char *values[3] = { NULL };
92 int cnt = 0;
93 int idx = 0;
94
95 for (; cnt < 3;) {
96 char *token = strsep(&annotated, ", {}");
97 if (token == NULL) {
98 break;
99 }
100
101 if (*token == '\0') {
102 continue;
103 }
104
105 values[cnt++] = token;
106 }
107
108 if ((cnt > 0) && (!strcmp(values[0], "public") || !strcmp(values[0], "private"))) {
109 if (visibility != NULL) {
110 (*visibility) = values[0];
111 }
112
113 idx++;
114 }
115
116 if (idx < cnt && (library != NULL) && (type != NULL)) {
117 char *decoder = values[idx];
118
119 for (cnt = 0; cnt < 3; ) {
120 char *token = strsep(&decoder, ": {}");
121 if (token == NULL) {
122 break;
123 }
124
125 if (*token == '\0') {
126 continue;
127 }
128
129 values[cnt++] = token;
130 }
131
132 if (cnt == 2) {
133 (*library) = values[0];
134 (*type) = values[1];
135 }
136
137 if (cnt == 1) {
138 (*library) = "builtin";
139 (*type) = values[0];
140 }
141 }
142}
143#endif /* !KERNEL */
144
145OS_ALWAYS_INLINE
146static inline bool
147_os_log_encode_arg(void *arg, uint16_t arg_len, os_log_value_type_t ctype, bool is_private, os_log_buffer_context_t context)
148{
149 os_log_buffer_value_t content = (os_log_buffer_value_t) &context->buffer->content[context->content_off];
150 size_t content_sz = sizeof(*content) + arg_len;
151 char tempString[OS_LOG_BUFFER_MAX_SIZE] = {};
152#ifndef KERNEL
153 bool obj_private = true;
154#endif
155
156#ifdef KERNEL
157 /* scrub kernel pointers */
158 if (doprnt_hide_pointers &&
159 ctype == OS_LOG_BUFFER_VALUE_TYPE_SCALAR &&
160 arg_len >= sizeof(void *)) {
161 unsigned long long value = 0;
162 memcpy(&value, arg, arg_len);
163
164#if __has_feature(ptrauth_calls)
165 /**
166 * Strip out the pointer authentication code before
167 * checking whether the pointer is a kernel address.
168 */
169 value = (unsigned long long)VM_KERNEL_STRIP_PTR(value);
170#endif /* __has_feature(ptrauth_calls) */
171
172 if (value >= VM_MIN_KERNEL_AND_KEXT_ADDRESS && value <= VM_MAX_KERNEL_ADDRESS) {
173 is_private = true;
174 bzero(arg, arg_len);
175 }
176 }
177#endif
178
179 content->type = ctype;
180 content->flags = (is_private ? OS_LOG_CONTENT_FLAG_PRIVATE : 0);
181
182#ifndef KERNEL
183 if (context->annotated != NULL) {
184 const char *visibility = NULL;
185
186 _os_log_parse_annotated(context->annotated, &visibility, NULL, NULL);
187 if (visibility) {
188 if (!strcasecmp(visibility, "private")) {
189 content->flags |= OS_LOG_CONTENT_FLAG_PRIVATE;
190 } else if (!strcasecmp(visibility, "public")) {
191 content->flags &= ~OS_LOG_CONTENT_FLAG_PRIVATE;
192 }
193 }
194
195 context->annotated = NULL;
196 }
197#endif /* !KERNEL */
198
199 switch (ctype) {
200 case OS_LOG_BUFFER_VALUE_TYPE_COUNT:
201 case OS_LOG_BUFFER_VALUE_TYPE_SCALAR:
202 if (is_private) {
203 _encode_data(content, tempString, strlen(tempString) + 1, context);
204 } else {
205 if ((context->content_off + content_sz) > context->content_sz) {
206 return false;
207 }
208
209 memcpy(content->value, arg, arg_len);
210 content->size = arg_len;
211 context->content_off += content_sz;
212 }
213 break;
214
215 case OS_LOG_BUFFER_VALUE_TYPE_STRING:
216 context->buffer->flags |= OS_LOG_BUFFER_HAS_NON_SCALAR;
217 if (_os_log_string_is_public(arg)) {
218 content->flags &= ~OS_LOG_CONTENT_FLAG_PRIVATE;
219 }
220
221 _encode_data(content, arg, arg_len, context);
222 break;
223
224#ifndef KERNEL
225 case OS_LOG_BUFFER_VALUE_TYPE_POINTER:
226 context->buffer->flags |= OS_LOG_BUFFER_HAS_NON_SCALAR;
227 _encode_data(content, arg, arg_len, context);
228 break;
229
230 case OS_LOG_BUFFER_VALUE_TYPE_OBJECT:
231 context->buffer->flags |= OS_LOG_BUFFER_HAS_NON_SCALAR;
232 if (!_NSCF2data(arg, tempString, sizeof(tempString), &obj_private)) {
233 tempString[0] = '\0';
234 }
235
236 if (!obj_private) {
237 content->flags &= ~OS_LOG_CONTENT_FLAG_PRIVATE;
238 }
239
240 _encode_data(content, tempString, strlen(tempString) + 1, context);
241 break;
242#endif /* !KERNEL */
243 }
244
245 if (content->flags & OS_LOG_CONTENT_FLAG_PRIVATE) {
246 context->buffer->flags |= OS_LOG_BUFFER_HAS_PRIVATE;
247 }
248
249 context->arg_idx++;
250
251 return true;
252}
253
254static bool
255_os_log_encode(const char *format, va_list args, int saved_errno, os_log_buffer_context_t context)
256{
257 const char *percent = strchr(format, '%');
258#ifndef KERNEL
259 char annotated[256];
260#endif
261
262 while (percent != NULL) {
263 ++percent;
264 if (percent[0] != '%') {
265 struct os_log_format_value_s value;
266 int type = OST_INT;
267#ifndef KERNEL
268 bool long_double = false;
269#endif
270 int prec = 0;
271 char ch;
272
273 for (bool done = false; !done; percent++) {
274 switch (ch = percent[0]) {
275 /* type of types or other */
276 case 'l': // longer
277 type++;
278 break;
279
280 case 'h': // shorter
281 type--;
282 break;
283
284 case 'z':
285 type = OST_SIZE;
286 break;
287
288 case 'j':
289 type = OST_INTMAX;
290 break;
291
292 case 't':
293 type = OST_PTRDIFF;
294 break;
295
296 case '.': // precision
297 if ((percent[1]) == '*') {
298 prec = va_arg(args, int);
299 _os_log_encode_arg(&prec, sizeof(prec), OS_LOG_BUFFER_VALUE_TYPE_COUNT, false, context);
300 percent++;
301 continue;
302 } else {
303 // we have to read the precision and do the right thing
304 const char *fmt = percent + 1;
305 prec = 0;
306 while (isdigit(ch = *fmt++)) {
307 prec = 10 * prec + (ch - '0');
308 }
309
310 if (prec > 1024) {
311 prec = 1024;
312 }
313
314 _os_log_encode_arg(&prec, sizeof(prec), OS_LOG_BUFFER_VALUE_TYPE_COUNT, false, context);
315 }
316 break;
317
318 case '-': // left-align
319 case '+': // force sign
320 case ' ': // prefix non-negative with space
321 case '#': // alternate
322 case '\'': // group by thousands
323 break;
324
325 /* fixed types */
326 case 'd': // integer
327 case 'i': // integer
328 case 'o': // octal
329 case 'u': // unsigned
330 case 'x': // hex
331 case 'X': // upper-hex
332 switch (type) {
333 case OST_CHAR:
334 value.type.ch = va_arg(args, int);
335 _os_log_encode_arg(&value.type.ch, sizeof(value.type.ch), OS_LOG_BUFFER_VALUE_TYPE_SCALAR, false, context);
336 break;
337
338 case OST_SHORT:
339 value.type.s = va_arg(args, int);
340 _os_log_encode_arg(&value.type.s, sizeof(value.type.s), OS_LOG_BUFFER_VALUE_TYPE_SCALAR, false, context);
341 break;
342
343 case OST_INT:
344 value.type.i = va_arg(args, int);
345 _os_log_encode_arg(&value.type.i, sizeof(value.type.i), OS_LOG_BUFFER_VALUE_TYPE_SCALAR, false, context);
346 break;
347
348 case OST_LONG:
349 value.type.l = va_arg(args, long);
350 _os_log_encode_arg(&value.type.l, sizeof(value.type.l), OS_LOG_BUFFER_VALUE_TYPE_SCALAR, false, context);
351 break;
352
353 case OST_LONGLONG:
354 value.type.ll = va_arg(args, long long);
355 _os_log_encode_arg(&value.type.ll, sizeof(value.type.ll), OS_LOG_BUFFER_VALUE_TYPE_SCALAR, false, context);
356 break;
357
358 case OST_SIZE:
359 value.type.z = va_arg(args, size_t);
360 _os_log_encode_arg(&value.type.z, sizeof(value.type.z), OS_LOG_BUFFER_VALUE_TYPE_SCALAR, false, context);
361 break;
362
363 case OST_INTMAX:
364 value.type.im = va_arg(args, intmax_t);
365 _os_log_encode_arg(&value.type.im, sizeof(value.type.im), OS_LOG_BUFFER_VALUE_TYPE_SCALAR, false, context);
366 break;
367
368 case OST_PTRDIFF:
369 value.type.pd = va_arg(args, ptrdiff_t);
370 _os_log_encode_arg(&value.type.pd, sizeof(value.type.pd), OS_LOG_BUFFER_VALUE_TYPE_SCALAR, false, context);
371 break;
372
373 default:
374 return false;
375 }
376 done = true;
377 break;
378
379#ifndef KERNEL
380 case '{':
381 // we do not support this for shimmed code
382 if (context->shimmed) {
383 return false;
384 }
385
386 for (const char *curr2 = percent + 1; (ch = (*curr2)) != NUL; curr2++) {
387 if (ch == '}') {
388 strlcpy(annotated, percent, MIN(curr2 - (percent + 1), sizeof(annotated)));
389 context->annotated = annotated;
390 percent = curr2;
391 break;
392 }
393 }
394 break;
395#endif /* !KERNEL */
396
397 case 'p': // pointer
398 value.type.p = va_arg(args, void *);
399 _os_log_encode_arg(&value.type.p, sizeof(value.type.p), OS_LOG_BUFFER_VALUE_TYPE_SCALAR, false, context);
400 done = true;
401 break;
402
403#ifndef KERNEL
404 case 'P': // pointer data
405 if (context->shimmed) { // we do not support this for shimmed code
406 return false;
407 }
408
409 context->buffer->flags |= OS_LOG_BUFFER_HAS_NON_SCALAR;
410 value.type.p = va_arg(args, void *);
411
412 // capture the string pointer to generate a symptom
413 if (context->log && context->log->generate_symptoms && context->arg_idx == 1 && value.type.pch && prec) {
414 context->symptom_ptr = value.type.p;
415 context->symptom_ptr_len = prec;
416 }
417
418 _os_log_encode_arg(value.type.p, prec, OS_LOG_BUFFER_VALUE_TYPE_POINTER, false, context);
419 prec = 0;
420 done = true;
421 break;
422#endif /* !KERNEL */
423
424#ifndef KERNEL
425 case 'L': // long double
426 long_double = true;
427 break;
428
429 case 'a': case 'A': case 'e': case 'E': // floating types
430 case 'f': case 'F': case 'g': case 'G':
431 if (long_double) {
432 value.type.ld = va_arg(args, long double);
433 _os_log_encode_arg(&value.type.ld, sizeof(value.type.ld), OS_LOG_BUFFER_VALUE_TYPE_SCALAR, false, context);
434 } else {
435 value.type.d = va_arg(args, double);
436 _os_log_encode_arg(&value.type.d, sizeof(value.type.d), OS_LOG_BUFFER_VALUE_TYPE_SCALAR, false, context);
437 }
438 done = true;
439 break;
440#endif /* !KERNEL */
441
442 case 'c': // char
443 value.type.ch = va_arg(args, int);
444 _os_log_encode_arg(&value.type.ch, sizeof(value.type.ch), OS_LOG_BUFFER_VALUE_TYPE_SCALAR, false, context);
445 done = true;
446 break;
447
448#ifndef KERNEL
449 case 'C': // wide-char
450 value.type.wch = va_arg(args, wint_t);
451 _os_log_encode_arg(&value.type.wch, sizeof(value.type.wch), OS_LOG_BUFFER_VALUE_TYPE_SCALAR, false, context);
452 done = true;
453 break;
454#endif /* !KERNEL */
455
456 case 's': // string
457 value.type.pch = va_arg(args, char *);
458 if (!prec && value.type.pch != NULL) {
459 prec = (int) strlen(value.type.pch) + 1;
460 }
461
462#ifndef KERNEL
463 // capture the string pointer to generate a symptom
464 if (context->log && context->log->generate_symptoms && context->arg_idx == 0 && value.type.pch) {
465 context->symptom_str = value.type.pch;
466 }
467#endif
468
469 context->buffer->flags |= OS_LOG_BUFFER_HAS_NON_SCALAR;
470 _os_log_encode_arg(value.type.pch, prec, OS_LOG_BUFFER_VALUE_TYPE_STRING, false, context);
471 prec = 0;
472 done = true;
473 break;
474
475#ifndef KERNEL
476 case 'S': // wide-string
477 value.type.pwch = va_arg(args, wchar_t *);
478 if (!prec && value.type.pwch != NULL) {
479 prec = (int) wcslen(value.type.pwch) + 1;
480 }
481
482 context->buffer->flags |= OS_LOG_BUFFER_HAS_NON_SCALAR;
483 _os_log_encode_arg(value.type.pwch, prec, OS_LOG_BUFFER_VALUE_TYPE_STRING, false, context);
484 prec = 0;
485 done = true;
486 break;
487#endif /* !KERNEL */
488
489#ifndef KERNEL
490 case '@': // CFTypeRef aka NSObject *
491 context->buffer->flags |= OS_LOG_BUFFER_HAS_NON_SCALAR;
492 _os_log_encode_arg(va_arg(args, void *), 0, OS_LOG_BUFFER_VALUE_TYPE_OBJECT, false, context);
493 done = true;
494 break;
495#endif /* !KERNEL */
496
497 case 'm':
498 value.type.i = saved_errno;
499 _os_log_encode_arg(&value.type.i, sizeof(value.type.i), OS_LOG_BUFFER_VALUE_TYPE_SCALAR, false, context);
500 done = true;
501 break;
502
503 default:
504 if (isdigit(ch)) { // [0-9]
505 continue;
506 }
507 return false;
508 }
509
510 if (done) {
511 percent = strchr(percent, '%'); // Find next format
512 break;
513 }
514 }
515 } else {
516 percent = strchr(percent+1, '%'); // Find next format after %%
517 }
518 }
519
520 context->buffer->arg_cnt = context->arg_idx;
521 context->content_sz = context->content_off;
522 context->pubdata_sz = context->pubdata_off;
523 context->privdata_sz = context->privdata_off;
524 context->arg_idx = context->content_off = context->pubdata_off = context->privdata_off = 0;
525
526 return true;
527}
528
529#endif /* log_encode_h */
530