1//===-- tysan.cpp ---------------------------------------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// This file is a part of TypeSanitizer.
10//
11// TypeSanitizer runtime.
12//===----------------------------------------------------------------------===//
13
14#include "sanitizer_common/sanitizer_atomic.h"
15#include "sanitizer_common/sanitizer_common.h"
16#include "sanitizer_common/sanitizer_flag_parser.h"
17#include "sanitizer_common/sanitizer_flags.h"
18#include "sanitizer_common/sanitizer_libc.h"
19#include "sanitizer_common/sanitizer_report_decorator.h"
20#include "sanitizer_common/sanitizer_stacktrace.h"
21#include "sanitizer_common/sanitizer_symbolizer.h"
22
23#include "tysan/tysan.h"
24
25#include <stdint.h>
26#include <string.h>
27
28using namespace __sanitizer;
29using namespace __tysan;
30
31extern "C" SANITIZER_INTERFACE_ATTRIBUTE void
32tysan_set_type_unknown(const void *addr, uptr size) {
33 if (tysan_inited)
34 internal_memset(s: shadow_for(ptr: addr), c: 0, n: size * sizeof(uptr));
35}
36
37extern "C" SANITIZER_INTERFACE_ATTRIBUTE void
38tysan_copy_types(const void *daddr, const void *saddr, uptr size) {
39 if (tysan_inited)
40 internal_memmove(dest: shadow_for(ptr: daddr), src: shadow_for(ptr: saddr), n: size * sizeof(uptr));
41}
42
43static const char *getDisplayName(const char *Name) {
44 if (Name[0] == '\0')
45 return "<anonymous type>";
46
47 // Clang generates tags for C++ types that demangle as typeinfo. Remove the
48 // prefix from the generated string.
49 const char *TIPrefix = "typeinfo name for ";
50 size_t TIPrefixLen = strlen(s: TIPrefix);
51
52 const char *DName = Symbolizer::GetOrInit()->Demangle(name: Name);
53 if (!internal_strncmp(s1: DName, s2: TIPrefix, n: TIPrefixLen))
54 DName += TIPrefixLen;
55
56 return DName;
57}
58
59static void printTDName(tysan_type_descriptor *td) {
60 if (((sptr)td) <= 0) {
61 Printf(format: "<unknown type>");
62 return;
63 }
64
65 switch (td->Tag) {
66 default:
67 CHECK(false && "invalid enum value");
68 break;
69 case TYSAN_MEMBER_TD:
70 printTDName(td: td->Member.Access);
71 if (td->Member.Access != td->Member.Base) {
72 Printf(format: " (in ");
73 printTDName(td: td->Member.Base);
74 Printf(format: " at offset %zu)", td->Member.Offset);
75 }
76 break;
77 case TYSAN_STRUCT_TD:
78 Printf(format: "%s", getDisplayName(
79 Name: (char *)(td->Struct.Members + td->Struct.MemberCount)));
80 break;
81 }
82}
83
84static tysan_type_descriptor *getRootTD(tysan_type_descriptor *TD) {
85 tysan_type_descriptor *RootTD = TD;
86
87 do {
88 RootTD = TD;
89
90 if (TD->Tag == TYSAN_STRUCT_TD) {
91 if (TD->Struct.MemberCount > 0)
92 TD = TD->Struct.Members[0].Type;
93 else
94 TD = nullptr;
95 } else if (TD->Tag == TYSAN_MEMBER_TD) {
96 TD = TD->Member.Access;
97 } else {
98 CHECK(false && "invalid enum value");
99 break;
100 }
101 } while (TD);
102
103 return RootTD;
104}
105
106// Walk up TDA to see if it reaches TDB.
107static bool walkAliasTree(tysan_type_descriptor *TDA,
108 tysan_type_descriptor *TDB, uptr OffsetA,
109 uptr OffsetB) {
110 do {
111 if (TDA == TDB)
112 return OffsetA == OffsetB;
113
114 if (TDA->Tag == TYSAN_STRUCT_TD) {
115 // Reached root type descriptor.
116 if (!TDA->Struct.MemberCount)
117 break;
118
119 uptr Idx = 0;
120 for (; Idx < TDA->Struct.MemberCount - 1; ++Idx) {
121 if (TDA->Struct.Members[Idx].Offset >= OffsetA)
122 break;
123 }
124
125 // This offset can't be negative. Therefore we must be accessing something
126 // before the current type (not legal) or partially inside the last type.
127 // In the latter case, we adjust Idx.
128 if (TDA->Struct.Members[Idx].Offset > OffsetA) {
129 // Trying to access something before the current type.
130 if (!Idx)
131 return false;
132
133 Idx -= 1;
134 }
135
136 OffsetA -= TDA->Struct.Members[Idx].Offset;
137 TDA = TDA->Struct.Members[Idx].Type;
138 } else {
139 CHECK(false && "invalid enum value");
140 break;
141 }
142 } while (TDA);
143
144 return false;
145}
146
147// Walk up the tree starting with TDA to see if we reach TDB.
148static bool isAliasingLegalUp(tysan_type_descriptor *TDA,
149 tysan_type_descriptor *TDB) {
150 uptr OffsetA = 0, OffsetB = 0;
151 if (TDB->Tag == TYSAN_MEMBER_TD) {
152 OffsetB = TDB->Member.Offset;
153 TDB = TDB->Member.Base;
154 }
155
156 if (TDA->Tag == TYSAN_MEMBER_TD) {
157 OffsetA = TDA->Member.Offset;
158 TDA = TDA->Member.Base;
159 }
160
161 return walkAliasTree(TDA, TDB, OffsetA, OffsetB);
162}
163
164static bool isAliasingLegalWithOffset(tysan_type_descriptor *TDA,
165 tysan_type_descriptor *TDB,
166 uptr OffsetB) {
167 // This is handled by calls to isAliasingLegalUp.
168 if (OffsetB == 0)
169 return false;
170
171 // You can't have an offset into a member.
172 if (TDB->Tag == TYSAN_MEMBER_TD)
173 return false;
174
175 uptr OffsetA = 0;
176 if (TDA->Tag == TYSAN_MEMBER_TD) {
177 OffsetA = TDA->Member.Offset;
178 TDA = TDA->Member.Base;
179 }
180
181 // Since the access was partially inside TDB (the shadow), it can be assumed
182 // that we are accessing a member in an object. This means that rather than
183 // walk up the scalar access TDA to reach an object, we should walk up the
184 // object TBD to reach the scalar we are accessing it with. The offsets will
185 // still be checked at the end to make sure this alias is legal.
186 return walkAliasTree(TDA: TDB, TDB: TDA, OffsetA: OffsetB, OffsetB: OffsetA);
187}
188
189static bool isAliasingLegal(tysan_type_descriptor *TDA,
190 tysan_type_descriptor *TDB, uptr OffsetB = 0) {
191 if (TDA == TDB || !TDB || !TDA)
192 return true;
193
194 // Aliasing is legal is the two types have different root nodes.
195 if (getRootTD(TD: TDA) != getRootTD(TD: TDB))
196 return true;
197
198 // TDB may have been adjusted by offset TDAOffset in the caller to point to
199 // the outer type. Check for aliasing with and without adjusting for this
200 // offset.
201 return isAliasingLegalUp(TDA, TDB) || isAliasingLegalUp(TDA: TDB, TDB: TDA) ||
202 isAliasingLegalWithOffset(TDA, TDB, OffsetB);
203}
204
205namespace __tysan {
206class Decorator : public __sanitizer::SanitizerCommonDecorator {
207public:
208 Decorator() : SanitizerCommonDecorator() {}
209 const char *Warning() { return Red(); }
210 const char *Name() { return Green(); }
211 const char *End() { return Default(); }
212};
213} // namespace __tysan
214
215ALWAYS_INLINE
216static void reportError(void *Addr, int Size, tysan_type_descriptor *TD,
217 tysan_type_descriptor *OldTD, const char *AccessStr,
218 const char *DescStr, int Offset, uptr pc, uptr bp,
219 uptr sp) {
220 Decorator d;
221 Printf(format: "%s", d.Warning());
222 Report(format: "ERROR: TypeSanitizer: type-aliasing-violation on address %p"
223 " (pc %p bp %p sp %p tid %llu)\n",
224 Addr, (void *)pc, (void *)bp, (void *)sp, GetTid());
225 Printf(format: "%s", d.End());
226 Printf(format: "%s of size %d at %p with type ", AccessStr, Size, Addr);
227
228 Printf(format: "%s", d.Name());
229 printTDName(td: TD);
230 Printf(format: "%s", d.End());
231
232 Printf(format: " %s of type ", DescStr);
233
234 Printf(format: "%s", d.Name());
235 printTDName(td: OldTD);
236 Printf(format: "%s", d.End());
237
238 if (Offset != 0)
239 Printf(format: " that starts at offset %d\n", Offset);
240 else
241 Printf(format: "\n");
242
243 if (pc) {
244 uptr top = 0;
245 uptr bottom = 0;
246 if (flags().print_stacktrace)
247 GetThreadStackTopAndBottom(at_initialization: false, stack_top: &top, stack_bottom: &bottom);
248
249 bool request_fast = StackTrace::WillUseFastUnwind(request_fast_unwind: true);
250 BufferedStackTrace ST;
251 ST.Unwind(max_depth: kStackTraceMax, pc, bp, context: 0, stack_top: top, stack_bottom: bottom, request_fast_unwind: request_fast);
252 ST.Print();
253 } else {
254 Printf(format: "\n");
255 }
256}
257
258ALWAYS_INLINE
259static void SetShadowType(tysan_type_descriptor *td,
260 tysan_type_descriptor **shadowData,
261 uint64_t AccessSize) {
262 *shadowData = td;
263 uint64_t shadowDataInt = (uint64_t)shadowData;
264
265 for (uint64_t i = 1; i < AccessSize; ++i) {
266 int64_t dataOffset = i << PtrShift();
267 int64_t *badShadowData = (int64_t *)(shadowDataInt + dataOffset);
268 int64_t badTD = int64_t(i) * -1;
269 *badShadowData = badTD;
270 }
271}
272
273ALWAYS_INLINE
274static bool GetNotAllBadTD(uint64_t ShadowDataInt, uint64_t AccessSize) {
275 bool notAllBadTD = false;
276 for (uint64_t i = 1; i < AccessSize; ++i) {
277 int64_t **unkShadowData = (int64_t **)(ShadowDataInt + (i << PtrShift()));
278 int64_t *ILdTD = *unkShadowData;
279 notAllBadTD = notAllBadTD || (ILdTD != nullptr);
280 }
281 return notAllBadTD;
282}
283
284ALWAYS_INLINE
285static bool GetNotAllUnkTD(uint64_t ShadowDataInt, uint64_t AccessSize) {
286 bool notAllBadTD = false;
287 for (uint64_t i = 1; i < AccessSize; ++i) {
288 int64_t *badShadowData = (int64_t *)(ShadowDataInt + (i << PtrShift()));
289 int64_t ILdTD = *badShadowData;
290 notAllBadTD = notAllBadTD || (ILdTD >= 0);
291 }
292 return notAllBadTD;
293}
294
295extern "C" SANITIZER_INTERFACE_ATTRIBUTE void
296__tysan_instrument_mem_inst(char *dest, char *src, uint64_t size,
297 bool needsMemMove) {
298 tysan_type_descriptor **destShadowDataPtr = shadow_for(ptr: dest);
299
300 if (!src) {
301 internal_memset(s: (char *)destShadowDataPtr, c: 0, n: size << PtrShift());
302 return;
303 }
304
305 uint64_t srcInt = (uint64_t)src;
306 uint64_t srcShadowInt = ((srcInt & AppMask()) << PtrShift()) + ShadowAddr();
307 uint64_t *srcShadow = (uint64_t *)srcShadowInt;
308
309 if (needsMemMove) {
310 internal_memmove(dest: (char *)destShadowDataPtr, src: srcShadow, n: size << PtrShift());
311 } else {
312 internal_memcpy(dest: (char *)destShadowDataPtr, src: srcShadow, n: size << PtrShift());
313 }
314}
315
316ALWAYS_INLINE
317static void __tysan_check_internal(void *addr, int size,
318 tysan_type_descriptor *td, int flags,
319 uptr pc, uptr bp, uptr sp) {
320 bool IsRead = flags & 1;
321 bool IsWrite = flags & 2;
322 const char *AccessStr;
323 if (IsRead && !IsWrite)
324 AccessStr = "READ";
325 else if (!IsRead && IsWrite)
326 AccessStr = "WRITE";
327 else
328 AccessStr = "ATOMIC UPDATE";
329
330 tysan_type_descriptor **OldTDPtr = shadow_for(ptr: addr);
331 tysan_type_descriptor *OldTD = *OldTDPtr;
332 if (((sptr)OldTD) < 0) {
333 int i = -((sptr)OldTD);
334 OldTDPtr -= i;
335 OldTD = *OldTDPtr;
336
337 if (!isAliasingLegal(TDA: td, TDB: OldTD, OffsetB: i))
338 reportError(Addr: addr, Size: size, TD: td, OldTD, AccessStr,
339 DescStr: "accesses part of an existing object", Offset: -i, pc, bp, sp);
340
341 return;
342 }
343
344 if (!isAliasingLegal(TDA: td, TDB: OldTD)) {
345 reportError(Addr: addr, Size: size, TD: td, OldTD, AccessStr, DescStr: "accesses an existing object",
346 Offset: 0, pc, bp, sp);
347 return;
348 }
349
350 // These types are allowed to alias (or the stored type is unknown), report
351 // an error if we find an interior type.
352
353 for (int i = 0; i < size; ++i) {
354 OldTDPtr = shadow_for(ptr: (void *)(((uptr)addr) + i));
355 OldTD = *OldTDPtr;
356 if (((sptr)OldTD) >= 0 && !isAliasingLegal(TDA: td, TDB: OldTD))
357 reportError(Addr: addr, Size: size, TD: td, OldTD, AccessStr,
358 DescStr: "partially accesses an object", Offset: i, pc, bp, sp);
359 }
360}
361
362extern "C" SANITIZER_INTERFACE_ATTRIBUTE void
363__tysan_check(void *addr, int size, tysan_type_descriptor *td, int flags) {
364 GET_CALLER_PC_BP_SP;
365 __tysan_check_internal(addr, size, td, flags, pc, bp, sp);
366}
367
368extern "C" SANITIZER_INTERFACE_ATTRIBUTE void
369__tysan_instrument_with_shadow_update(void *ptr, tysan_type_descriptor *td,
370 bool sanitizeFunction,
371 uint64_t accessSize, int flags) {
372 tysan_type_descriptor **shadowData = shadow_for(ptr);
373 tysan_type_descriptor *loadedTD = *shadowData;
374 bool shadowIsNull = loadedTD == nullptr;
375
376 // TODO, sanitizeFunction is known at compile time, so maybe this is split
377 // into two different functions
378 if (sanitizeFunction) {
379
380 if (td != loadedTD) {
381
382 // We now know that the types did not match (we're on the slow path). If
383 // the type is unknown, then set it.
384 if (shadowIsNull) {
385 // We're about to set the type. Make sure that all bytes in the value
386 // are also of unknown type.
387 bool isAllUnknownTD = GetNotAllUnkTD(ShadowDataInt: (uint64_t)shadowData, AccessSize: accessSize);
388 if (isAllUnknownTD) {
389 GET_CALLER_PC_BP_SP;
390 __tysan_check_internal(addr: ptr, size: accessSize, td, flags, pc, bp, sp);
391 }
392 SetShadowType(td, shadowData, AccessSize: accessSize);
393 } else {
394 GET_CALLER_PC_BP_SP;
395 __tysan_check_internal(addr: ptr, size: accessSize, td, flags, pc, bp, sp);
396 }
397 } else {
398 // We appear to have the right type. Make sure that all other bytes in
399 // the type are still marked as interior bytes. If not, call the runtime.
400 bool isNotAllBadTD = GetNotAllBadTD(ShadowDataInt: (uint64_t)shadowData, AccessSize: accessSize);
401 if (isNotAllBadTD) {
402 GET_CALLER_PC_BP_SP;
403 __tysan_check_internal(addr: ptr, size: accessSize, td, flags, pc, bp, sp);
404 }
405 }
406 } else if (shadowIsNull) {
407 SetShadowType(td, shadowData, AccessSize: accessSize);
408 }
409}
410
411extern "C" SANITIZER_INTERFACE_ATTRIBUTE void
412__tysan_set_shadow_type(void *ptr, tysan_type_descriptor *td,
413 uint64_t accessSize) {
414 // In the mode where writes always set the type, for a write (which does
415 // not also read), we just set the type.
416 tysan_type_descriptor **shadow = shadow_for(ptr);
417 SetShadowType(td, shadowData: shadow, AccessSize: accessSize);
418}
419
420Flags __tysan::flags_data;
421
422SANITIZER_INTERFACE_ATTRIBUTE uptr __tysan_shadow_memory_address;
423SANITIZER_INTERFACE_ATTRIBUTE uptr __tysan_app_memory_mask;
424
425#ifdef TYSAN_RUNTIME_VMA
426// Runtime detected VMA size.
427int __tysan::vmaSize;
428#endif
429
430void Flags::SetDefaults() {
431#define TYSAN_FLAG(Type, Name, DefaultValue, Description) Name = DefaultValue;
432#include "tysan_flags.inc"
433#undef TYSAN_FLAG
434}
435
436static void RegisterTySanFlags(FlagParser *parser, Flags *f) {
437#define TYSAN_FLAG(Type, Name, DefaultValue, Description) \
438 RegisterFlag(parser, #Name, Description, &f->Name);
439#include "tysan_flags.inc"
440#undef TYSAN_FLAG
441}
442
443static void InitializeFlags() {
444 SetCommonFlagsDefaults();
445 {
446 CommonFlags cf;
447 cf.CopyFrom(other: *common_flags());
448 cf.external_symbolizer_path = GetEnv(name: "TYSAN_SYMBOLIZER_PATH");
449 OverrideCommonFlags(cf);
450 }
451
452 flags().SetDefaults();
453
454 FlagParser parser;
455 RegisterCommonFlags(parser: &parser);
456 RegisterTySanFlags(parser: &parser, f: &flags());
457 parser.ParseString(s: GetEnv(name: "TYSAN_OPTIONS"));
458 InitializeCommonFlags();
459 if (Verbosity())
460 ReportUnrecognizedFlags();
461 if (common_flags()->help)
462 parser.PrintFlagDescriptions();
463}
464
465static void TySanInitializePlatformEarly() {
466 AvoidCVE_2016_2143();
467#ifdef TYSAN_RUNTIME_VMA
468 vmaSize = (MostSignificantSetBitIndex(GET_CURRENT_FRAME()) + 1);
469#if defined(__aarch64__) && !SANITIZER_APPLE
470 if (vmaSize != 39 && vmaSize != 42 && vmaSize != 48) {
471 Printf("FATAL: TypeSanitizer: unsupported VMA range\n");
472 Printf("FATAL: Found %d - Supported 39, 42 and 48\n", vmaSize);
473 Die();
474 }
475#endif
476#endif
477
478 __sanitizer::InitializePlatformEarly();
479
480 __tysan_shadow_memory_address = ShadowAddr();
481 __tysan_app_memory_mask = AppMask();
482}
483
484namespace __tysan {
485bool tysan_inited = false;
486bool tysan_init_is_running;
487} // namespace __tysan
488
489extern "C" SANITIZER_INTERFACE_ATTRIBUTE void __tysan_init() {
490 CHECK(!tysan_init_is_running);
491 if (tysan_inited)
492 return;
493 tysan_init_is_running = true;
494
495 InitializeFlags();
496 TySanInitializePlatformEarly();
497
498 InitializeInterceptors();
499
500 if (!MmapFixedNoReserve(fixed_addr: ShadowAddr(), size: AppAddr() - ShadowAddr()))
501 Die();
502
503 tysan_init_is_running = false;
504 tysan_inited = true;
505}
506
507#if SANITIZER_CAN_USE_PREINIT_ARRAY
508__attribute__((section(".preinit_array"),
509 used)) static void (*tysan_init_ptr)() = __tysan_init;
510#endif
511