1 /*
2 * Copyright (c) 2019 Google Inc. All rights reserved
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining
5 * a copy of this software and associated documentation files
6 * (the "Software"), to deal in the Software without restriction,
7 * including without limitation the rights to use, copy, modify, merge,
8 * publish, distribute, sublicense, and/or sell copies of the Software,
9 * and to permit persons to whom the Software is furnished to do so,
10 * subject to the following conditions:
11 *
12 * The above copyright notice and this permission notice shall be
13 * included in all copies or substantial portions of the Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 */
23
24 #include "ubsan.h"
25
26 #include <assert.h>
27 #include <inttypes.h>
28 #include <stdbool.h>
29 #include <stdint.h>
30 #include <stdio.h>
31 #include <string.h>
32 #include <sys/types.h>
33 #include <trusty/string.h>
34
35 /*
36 * in_ubsan_* is used to track whether we are currently processing a UBSan
37 * report. This is useful information so that if UBSan gets tripped again
38 * (due to e.g. a bug in printf, or the logging code) we don't sit in an
39 * infinite recursion trying to report the bug over and over.
40 */
41 static bool in_ubsan_get(void);
42 static void in_ubsan_set(bool);
43
44 #ifdef TRUSTY_USERSPACE
45 /* TODO Once TLS is available, make this __thread */
46 static bool in_ubsan = false;
in_ubsan_get(void)47 static inline bool in_ubsan_get(void) {
48 return in_ubsan;
49 }
in_ubsan_set(bool val)50 static inline void in_ubsan_set(bool val) {
51 in_ubsan = val;
52 }
53 #else
54 /*
55 * Single copy of in_ubsan for when we don't have a current thread, e.g.,
56 * during early boot
57 */
58 static bool in_ubsan_early = false;
59
60 #include <kernel/thread.h>
in_ubsan_get(void)61 static inline bool in_ubsan_get(void) {
62 thread_t* curr = get_current_thread();
63 return curr ? thread_tls_get(curr, TLS_ENTRY_UBSAN) : in_ubsan_early;
64 }
in_ubsan_set(bool val)65 static inline void in_ubsan_set(bool val) {
66 thread_t* curr = get_current_thread();
67 if (curr) {
68 thread_tls_set(curr, TLS_ENTRY_UBSAN, val);
69 } else {
70 in_ubsan_early = val;
71 }
72 }
73 #endif
74
75 #define VALUE_RENDER_SIZE 64
76 #define DETAIL_RENDER_SIZE 1024
77
val_signed(const struct type_descriptor * type,value_handle_t val)78 static int64_t val_signed(const struct type_descriptor* type,
79 value_handle_t val) {
80 if (type_is_inline(type)) {
81 /* Sign extend if smaller than ssize_t-bits */
82 size_t undefined_bits = sizeof(size_t) * 8 - type_width_bits(type);
83 val <<= undefined_bits;
84 ssize_t out = (ssize_t)val;
85 out >>= undefined_bits;
86 return out;
87 } else {
88 /*
89 * This truncates, but we don't have a good way to deal with
90 * ints larger than 64 bits and it at least gets *some* data.
91 *
92 * For values larger than 64 bits, this will also have the wrong
93 * sign.
94 */
95 return *(int64_t*)val;
96 }
97 }
98
val_unsigned(const struct type_descriptor * type,value_handle_t val)99 static uint64_t val_unsigned(const struct type_descriptor* type,
100 value_handle_t val) {
101 if (type_is_inline(type)) {
102 return (uint64_t)val;
103 } else {
104 /* This truncates, but gets some data out */
105 return *(uint64_t*)val;
106 }
107 }
108
render_val(char * out,size_t out_size,const struct type_descriptor * type,value_handle_t val)109 static void render_val(char* out,
110 size_t out_size,
111 const struct type_descriptor* type,
112 value_handle_t val) {
113 size_t width = type_width_bits(type);
114 if (type_is_signed_integer(type)) {
115 if (width > sizeof(int64_t) * 8) {
116 size_t warn_len = scnprintf(out, out_size,
117 "~int%zu_t->int64_t:truncated ", width);
118 out += warn_len;
119 out_size -= warn_len;
120 }
121 scnprintf(out, out_size, "%" PRId64, val_signed(type, val));
122 } else if (type_is_unsigned_integer(type)) {
123 if (width > sizeof(uint64_t) * 8) {
124 size_t warn_len = scnprintf(
125 out, out_size, "~uint%zu_t->uint64_t:truncated ", width);
126 out += warn_len;
127 out_size -= warn_len;
128 }
129 scnprintf(out, out_size, "%" PRIu64, val_unsigned(type, val));
130 } else if (type_is_float(type)) {
131 /*
132 * Printing floating point correctly requires a more powerful printf
133 * which may not be available, and printing large floats will pull in
134 * softfloat support.
135 * Since it is unlikely the exact value of a float triggering a
136 * sanitizer will be important, we don't format it.
137 */
138 scnprintf(out, out_size, "<floating point value>");
139 } else {
140 scnprintf(out, out_size, "<value with unknown type>");
141 }
142 }
143
log(struct source_location * location,const char * kind,const char * details)144 static void log(struct source_location* location,
145 const char* kind,
146 const char* details) {
147 fprintf(stderr, "UBSan: (%s) %s:%d:%d\nDetails: %s\n", kind,
148 location->filename, location->line, location->column, details);
149 }
150
ubsan_fail(const char * msg)151 static void ubsan_fail(const char* msg) {
152 #ifdef TRUSTY_USERSPACE
153 fprintf(stderr, "ubsan panic: %s\n", msg);
154 *(volatile char*)0 = 0;
155 #else
156 panic("%s\n", msg);
157 #endif
158 }
159
start(void)160 static bool start(void) {
161 if (in_ubsan_get()) {
162 return false;
163 }
164 in_ubsan_set(true);
165 return true;
166 }
167
finish(void)168 static void finish(void) {
169 assert(in_ubsan_get());
170 ubsan_fail("UBSan violation");
171 }
172
173 /*
174 * UBSAN_START should be used at the beginning of each ubsan handler.
175 * It will abort if we are already processing a UBSan report, and set the
176 * flag if we are.
177 */
178 #define UBSAN_START \
179 if (!start()) { \
180 return; \
181 }
182
183 /*
184 * UBSAN_FINISH should be used at the end of each ubsan handler.
185 * It will mark us as having left the handler, and terminate due to the error
186 * report.
187 */
188 #define UBSAN_FINISH finish();
189
handle_overflow(struct overflow_data * data,value_handle_t lhs,value_handle_t rhs,const char * op)190 static void handle_overflow(struct overflow_data* data,
191 value_handle_t lhs,
192 value_handle_t rhs,
193 const char* op) {
194 UBSAN_START;
195 char rendered_lhs[VALUE_RENDER_SIZE];
196 char rendered_rhs[VALUE_RENDER_SIZE];
197 char details[DETAIL_RENDER_SIZE];
198
199 char overflow_kind[16];
200
201 scnprintf(overflow_kind, sizeof(overflow_kind), "overflow:%s", op);
202
203 const struct type_descriptor* type = data->type;
204
205 render_val(rendered_lhs, sizeof(rendered_lhs), type, lhs);
206 render_val(rendered_rhs, sizeof(rendered_rhs), type, rhs);
207
208 scnprintf(details, sizeof(details),
209 "%s integer overflow: %s %s %s cannot be represented in type"
210 " %s\n",
211 type_is_signed_integer(type) ? "signed" : "unsigned",
212 rendered_lhs, op, rendered_rhs, type->name);
213
214 log(&data->loc, overflow_kind, details);
215 UBSAN_FINISH;
216 }
217
UBSAN_HANDLER(add_overflow,struct overflow_data * data,value_handle_t lhs,value_handle_t rhs)218 UBSAN_HANDLER(add_overflow,
219 struct overflow_data* data,
220 value_handle_t lhs,
221 value_handle_t rhs) {
222 handle_overflow(data, lhs, rhs, "+");
223 }
224
UBSAN_HANDLER(sub_overflow,struct overflow_data * data,value_handle_t lhs,value_handle_t rhs)225 UBSAN_HANDLER(sub_overflow,
226 struct overflow_data* data,
227 value_handle_t lhs,
228 value_handle_t rhs) {
229 handle_overflow(data, lhs, rhs, "-");
230 }
231
UBSAN_HANDLER(mul_overflow,struct overflow_data * data,value_handle_t lhs,value_handle_t rhs)232 UBSAN_HANDLER(mul_overflow,
233 struct overflow_data* data,
234 value_handle_t lhs,
235 value_handle_t rhs) {
236 handle_overflow(data, lhs, rhs, "*");
237 }
238
UBSAN_HANDLER(divrem_overflow,struct overflow_data * data,value_handle_t lhs,value_handle_t rhs)239 UBSAN_HANDLER(divrem_overflow,
240 struct overflow_data* data,
241 value_handle_t lhs,
242 value_handle_t rhs) {
243 handle_overflow(data, lhs, rhs, "/%");
244 }
245
UBSAN_HANDLER(negate_overflow,struct overflow_data * data,value_handle_t val)246 UBSAN_HANDLER(negate_overflow, struct overflow_data* data, value_handle_t val) {
247 UBSAN_START;
248 char rendered_val[VALUE_RENDER_SIZE];
249 char details[DETAIL_RENDER_SIZE];
250
251 render_val(rendered_val, sizeof(rendered_val), data->type, val);
252 scnprintf(details, sizeof(details),
253 "negation of %s cannot be represented in type %s", rendered_val,
254 data->type->name);
255
256 log(&data->loc, "negation overflow", details);
257 UBSAN_FINISH;
258 }
259
UBSAN_HANDLER(pointer_overflow,struct pointer_overflow_data * data,uintptr_t base,uintptr_t result)260 UBSAN_HANDLER(pointer_overflow,
261 struct pointer_overflow_data* data,
262 uintptr_t base,
263 uintptr_t result) {
264 UBSAN_START;
265 char details[DETAIL_RENDER_SIZE];
266 snprintf_filtered(details, sizeof(details),
267 "pointer arithmetic on %p overflowed resulting in %p",
268 (void*)base, (void*)result);
269 log(&data->loc, "pointer_overflow", details);
270 UBSAN_FINISH;
271 }
272
UBSAN_HANDLER(implicit_conversion,struct implicit_conversion_data * data,value_handle_t src,value_handle_t dst)273 UBSAN_HANDLER(implicit_conversion,
274 struct implicit_conversion_data* data,
275 value_handle_t src,
276 value_handle_t dst) {
277 UBSAN_START;
278 char rendered_src[VALUE_RENDER_SIZE];
279 char rendered_dst[VALUE_RENDER_SIZE];
280 char details[DETAIL_RENDER_SIZE];
281 const char* kind_str;
282
283 if (data->check_kind <
284 sizeof(implicit_conversion_check_kinds) / sizeof(const char*)) {
285 kind_str = implicit_conversion_check_kinds[data->check_kind];
286 } else {
287 kind_str = "unknown";
288 }
289
290 render_val(rendered_src, sizeof(rendered_src), data->from_type, src);
291 render_val(rendered_dst, sizeof(rendered_dst), data->to_type, dst);
292 scnprintf(details, sizeof(details),
293 "implicit conversion (%s) from %s to %s\n", kind_str,
294 rendered_src, rendered_dst);
295
296 log(&data->loc, "implicit conversion", details);
297 UBSAN_FINISH;
298 }
299
UBSAN_HANDLER(invalid_builtin,struct invalid_builtin_data * data)300 UBSAN_HANDLER(invalid_builtin, struct invalid_builtin_data* data) {
301 UBSAN_START;
302 const char* details;
303 switch (data->check_kind) {
304 case BCK_CTZ_PASSED_ZERO:
305 details = "zero passed to ctz";
306 break;
307 case BCK_CLZ_PASSED_ZERO:
308 details = "zero passed to clz";
309 break;
310 default:
311 details = "unknown builtin misuse kind";
312 }
313 log(&data->loc, "invalid builtin usage", details);
314 UBSAN_FINISH;
315 }
316
UBSAN_HANDLER(type_mismatch_v1,struct type_mismatch_data * data,value_handle_t ptr)317 UBSAN_HANDLER(type_mismatch_v1,
318 struct type_mismatch_data* data,
319 value_handle_t ptr) {
320 UBSAN_START;
321 char details[DETAIL_RENDER_SIZE];
322
323 intptr_t alignment = 1 << data->log_alignment;
324 if (!ptr) {
325 scnprintf(details, sizeof(details), "%s null pointer type %s",
326 type_check_kinds[data->type_check_kind], data->type->name);
327 } else if (ptr & (alignment - 1)) {
328 snprintf_filtered(
329 details, sizeof(details),
330 "%s misaligned pointer %p for type %s which requires %d byte alignment",
331 type_check_kinds[data->type_check_kind], (void*)ptr,
332 data->type->name, (int)alignment);
333 } else {
334 snprintf_filtered(
335 details, sizeof(details),
336 "%s pointer %p points at a region with insufficient space for a value of type %s",
337 type_check_kinds[data->type_check_kind], (void*)ptr,
338 data->type->name);
339 }
340 log(&data->loc, "type mismatch", details);
341 UBSAN_FINISH;
342 }
343
UBSAN_HANDLER(builtin_unreachable,struct unreachable_data * data)344 UBSAN_HANDLER(builtin_unreachable, struct unreachable_data* data) {
345 UBSAN_START;
346 log(&data->loc, "hit a supposedly unreachable point", "");
347 UBSAN_FINISH;
348 ubsan_fail("executing through unreachable would be unwise");
349 }
350
UBSAN_HANDLER(missing_return,struct unreachable_data * data)351 UBSAN_HANDLER(missing_return, struct unreachable_data* data) {
352 UBSAN_START;
353 log(&data->loc, "hit a missing return statement", "");
354 UBSAN_FINISH;
355 ubsan_fail("executing past the end of a function would be unwise");
356 }
357
is_negative(const struct type_descriptor * type,value_handle_t val)358 static bool is_negative(const struct type_descriptor* type,
359 value_handle_t val) {
360 if (type_is_signed_integer(type)) {
361 return val_signed(type, val) < 0;
362 }
363 return false;
364 }
365
UBSAN_HANDLER(shift_out_of_bounds,struct shift_out_of_bounds_data * data,value_handle_t lhs,value_handle_t rhs)366 UBSAN_HANDLER(shift_out_of_bounds,
367 struct shift_out_of_bounds_data* data,
368 value_handle_t lhs,
369 value_handle_t rhs) {
370 UBSAN_START;
371 char rendered_lhs[VALUE_RENDER_SIZE];
372 char rendered_rhs[VALUE_RENDER_SIZE];
373 char details[DETAIL_RENDER_SIZE];
374
375 render_val(rendered_lhs, sizeof(rendered_lhs), data->lhs_type, lhs);
376 render_val(rendered_rhs, sizeof(rendered_rhs), data->rhs_type, rhs);
377
378 uint64_t rhs_u64 = val_unsigned(data->rhs_type, rhs);
379
380 if (is_negative(data->rhs_type, rhs)) {
381 scnprintf(details, sizeof(details), "shift amount is negative: %s",
382 rendered_rhs);
383 } else if (type_width_bits(data->lhs_type) < rhs_u64) {
384 scnprintf(details, sizeof(details),
385 "shift amount %s is too large for type %s", rendered_rhs,
386 data->lhs_type->name);
387 } else if (is_negative(data->lhs_type, lhs)) {
388 /* At this point, we know we are dealing with a left shift, as right
389 * shift is covered by the above two cases */
390 scnprintf(details, sizeof(details),
391 "left shifting a negative value: %s", rendered_lhs);
392 } else {
393 scnprintf(details, sizeof(details), "%s << %s does not fit in %s",
394 rendered_lhs, rendered_rhs, data->lhs_type->name);
395 }
396
397 log(&data->loc, "shift out of bounds", details);
398 UBSAN_FINISH;
399 }
400
UBSAN_HANDLER(out_of_bounds,struct out_of_bounds_data * data,value_handle_t index)401 UBSAN_HANDLER(out_of_bounds,
402 struct out_of_bounds_data* data,
403 value_handle_t index) {
404 UBSAN_START;
405 char rendered_index[VALUE_RENDER_SIZE];
406 char details[DETAIL_RENDER_SIZE];
407
408 render_val(rendered_index, sizeof(rendered_index), data->index_type, index);
409 scnprintf(details, sizeof(details), "index %s out of bounds for %s\n",
410 rendered_index, data->array_type->name);
411
412 log(&data->loc, "out of bounds access", details);
413 UBSAN_FINISH;
414 }
415
UBSAN_HANDLER(load_invalid_value,struct invalid_value_data * data,value_handle_t val)416 UBSAN_HANDLER(load_invalid_value,
417 struct invalid_value_data* data,
418 value_handle_t val) {
419 UBSAN_START;
420 char rendered_val[VALUE_RENDER_SIZE];
421 char details[DETAIL_RENDER_SIZE];
422
423 render_val(rendered_val, sizeof(rendered_val), data->type, val);
424 scnprintf(details, sizeof(details),
425 "load of value %s outside of range for type %s", rendered_val,
426 data->type->name);
427
428 log(&data->loc, "invalid value", details);
429 UBSAN_FINISH;
430 }
431
UBSAN_HANDLER(float_cast_overflow,struct float_cast_overflow_data * data,value_handle_t val)432 UBSAN_HANDLER(float_cast_overflow,
433 struct float_cast_overflow_data* data,
434 value_handle_t val) {
435 UBSAN_START;
436 /* Since we aren't rendering floats, there's not much point in details */
437 log(&data->loc, "float cast overflow", "");
438 UBSAN_FINISH;
439 }
440
UBSAN_HANDLER(cfi_check_fail_abort,struct cfi_check_fail_data * data,value_handle_t val,uintptr_t vtable_is_valid)441 UBSAN_HANDLER(cfi_check_fail_abort,
442 struct cfi_check_fail_data* data,
443 value_handle_t val,
444 uintptr_t vtable_is_valid) {
445 UBSAN_START;
446 char rendered_val[VALUE_RENDER_SIZE];
447 char details[DETAIL_RENDER_SIZE];
448
449 render_val(rendered_val, sizeof(rendered_val), data->type, val);
450 scnprintf(details, sizeof(details), "type of the value: %s type name: %s",
451 rendered_val, data->type->name);
452 log(&data->loc, "cfi check fail abort", details);
453 UBSAN_FINISH;
454 }
455
UBSAN_HANDLER(vla_bound_not_positive,struct vla_bound_data * data,value_handle_t val)456 UBSAN_HANDLER(vla_bound_not_positive,
457 struct vla_bound_data* data,
458 value_handle_t val) {
459 UBSAN_START;
460 char rendered_val[VALUE_RENDER_SIZE];
461 char details[DETAIL_RENDER_SIZE];
462
463 render_val(rendered_val, sizeof(rendered_val), data->type, val);
464 scnprintf(details, sizeof(details), "bound %s is not positive for type %s",
465 rendered_val, data->type->name);
466 log(&data->loc,
467 "variable length array bound evaluates to non-positive value", details);
468 UBSAN_FINISH;
469 }
470
UBSAN_HANDLER(alignment_assumption,struct alignment_assumption_data * data,value_handle_t pointer,value_handle_t alignment,value_handle_t offset)471 UBSAN_HANDLER(alignment_assumption,
472 struct alignment_assumption_data* data,
473 value_handle_t pointer,
474 value_handle_t alignment,
475 value_handle_t offset) {
476 UBSAN_START;
477 char details[DETAIL_RENDER_SIZE];
478
479 if (location_is_valid(data->assumption_loc)) {
480 log(&data->assumption_loc, "alignment assumption was specified here",
481 /* Details: */ "below");
482 }
483
484 if (!offset) {
485 scnprintf(details, sizeof(details),
486 "assumption of %" PRIuPTR
487 " byte alignment for pointer of type %s failed",
488 alignment, data->type->name);
489 } else {
490 scnprintf(details, sizeof(details),
491 "assumption of %" PRIuPTR
492 " byte alignment (with offset of %" PRIuPTR
493 " bytes) for pointer of type %s failed",
494 alignment, offset, data->type->name);
495 }
496
497 log(&data->loc, "alignment assumption is incorrect", details);
498 UBSAN_FINISH;
499 }
500