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')) |
37 | extern boolean_t doprnt_hide_pointers; |
38 | #endif |
39 | |
40 | static 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 |
88 | static 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 | |
145 | OS_ALWAYS_INLINE |
146 | static 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 | |
254 | static 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 | |