Fixes issue #125079 as well as another case I discovered while trying to build LLVM with TySan.
394 lines
11 KiB
C++
394 lines
11 KiB
C++
//===-- tysan.cpp ---------------------------------------------------------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This file is a part of TypeSanitizer.
|
|
//
|
|
// TypeSanitizer runtime.
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "sanitizer_common/sanitizer_atomic.h"
|
|
#include "sanitizer_common/sanitizer_common.h"
|
|
#include "sanitizer_common/sanitizer_flag_parser.h"
|
|
#include "sanitizer_common/sanitizer_flags.h"
|
|
#include "sanitizer_common/sanitizer_libc.h"
|
|
#include "sanitizer_common/sanitizer_report_decorator.h"
|
|
#include "sanitizer_common/sanitizer_stacktrace.h"
|
|
#include "sanitizer_common/sanitizer_symbolizer.h"
|
|
|
|
#include "tysan/tysan.h"
|
|
|
|
#include <string.h>
|
|
|
|
using namespace __sanitizer;
|
|
using namespace __tysan;
|
|
|
|
extern "C" SANITIZER_INTERFACE_ATTRIBUTE void
|
|
tysan_set_type_unknown(const void *addr, uptr size) {
|
|
if (tysan_inited)
|
|
internal_memset(shadow_for(addr), 0, size * sizeof(uptr));
|
|
}
|
|
|
|
extern "C" SANITIZER_INTERFACE_ATTRIBUTE void
|
|
tysan_copy_types(const void *daddr, const void *saddr, uptr size) {
|
|
if (tysan_inited)
|
|
internal_memmove(shadow_for(daddr), shadow_for(saddr), size * sizeof(uptr));
|
|
}
|
|
|
|
static const char *getDisplayName(const char *Name) {
|
|
if (Name[0] == '\0')
|
|
return "<anonymous type>";
|
|
|
|
// Clang generates tags for C++ types that demangle as typeinfo. Remove the
|
|
// prefix from the generated string.
|
|
const char *TIPrefix = "typeinfo name for ";
|
|
size_t TIPrefixLen = strlen(TIPrefix);
|
|
|
|
const char *DName = Symbolizer::GetOrInit()->Demangle(Name);
|
|
if (!internal_strncmp(DName, TIPrefix, TIPrefixLen))
|
|
DName += TIPrefixLen;
|
|
|
|
return DName;
|
|
}
|
|
|
|
static void printTDName(tysan_type_descriptor *td) {
|
|
if (((sptr)td) <= 0) {
|
|
Printf("<unknown type>");
|
|
return;
|
|
}
|
|
|
|
switch (td->Tag) {
|
|
default:
|
|
CHECK(false && "invalid enum value");
|
|
break;
|
|
case TYSAN_MEMBER_TD:
|
|
printTDName(td->Member.Access);
|
|
if (td->Member.Access != td->Member.Base) {
|
|
Printf(" (in ");
|
|
printTDName(td->Member.Base);
|
|
Printf(" at offset %zu)", td->Member.Offset);
|
|
}
|
|
break;
|
|
case TYSAN_STRUCT_TD:
|
|
Printf("%s", getDisplayName(
|
|
(char *)(td->Struct.Members + td->Struct.MemberCount)));
|
|
break;
|
|
}
|
|
}
|
|
|
|
static tysan_type_descriptor *getRootTD(tysan_type_descriptor *TD) {
|
|
tysan_type_descriptor *RootTD = TD;
|
|
|
|
do {
|
|
RootTD = TD;
|
|
|
|
if (TD->Tag == TYSAN_STRUCT_TD) {
|
|
if (TD->Struct.MemberCount > 0)
|
|
TD = TD->Struct.Members[0].Type;
|
|
else
|
|
TD = nullptr;
|
|
} else if (TD->Tag == TYSAN_MEMBER_TD) {
|
|
TD = TD->Member.Access;
|
|
} else {
|
|
CHECK(false && "invalid enum value");
|
|
break;
|
|
}
|
|
} while (TD);
|
|
|
|
return RootTD;
|
|
}
|
|
|
|
// Walk up TDA to see if it reaches TDB.
|
|
static bool walkAliasTree(tysan_type_descriptor *TDA,
|
|
tysan_type_descriptor *TDB, uptr OffsetA,
|
|
uptr OffsetB) {
|
|
do {
|
|
if (TDA == TDB)
|
|
return OffsetA == OffsetB;
|
|
|
|
if (TDA->Tag == TYSAN_STRUCT_TD) {
|
|
// Reached root type descriptor.
|
|
if (!TDA->Struct.MemberCount)
|
|
break;
|
|
|
|
uptr Idx = 0;
|
|
for (; Idx < TDA->Struct.MemberCount - 1; ++Idx) {
|
|
if (TDA->Struct.Members[Idx].Offset >= OffsetA)
|
|
break;
|
|
}
|
|
|
|
// This offset can't be negative. Therefore we must be accessing something
|
|
// before the current type (not legal) or partially inside the last type.
|
|
// In the latter case, we adjust Idx.
|
|
if (TDA->Struct.Members[Idx].Offset > OffsetA) {
|
|
// Trying to access something before the current type.
|
|
if (!Idx)
|
|
return false;
|
|
|
|
Idx -= 1;
|
|
}
|
|
|
|
OffsetA -= TDA->Struct.Members[Idx].Offset;
|
|
TDA = TDA->Struct.Members[Idx].Type;
|
|
} else {
|
|
CHECK(false && "invalid enum value");
|
|
break;
|
|
}
|
|
} while (TDA);
|
|
|
|
return false;
|
|
}
|
|
|
|
// Walk up the tree starting with TDA to see if we reach TDB.
|
|
static bool isAliasingLegalUp(tysan_type_descriptor *TDA,
|
|
tysan_type_descriptor *TDB) {
|
|
uptr OffsetA = 0, OffsetB = 0;
|
|
if (TDB->Tag == TYSAN_MEMBER_TD) {
|
|
OffsetB = TDB->Member.Offset;
|
|
TDB = TDB->Member.Base;
|
|
}
|
|
|
|
if (TDA->Tag == TYSAN_MEMBER_TD) {
|
|
OffsetA = TDA->Member.Offset;
|
|
TDA = TDA->Member.Base;
|
|
}
|
|
|
|
return walkAliasTree(TDA, TDB, OffsetA, OffsetB);
|
|
}
|
|
|
|
static bool isAliasingLegalWithOffset(tysan_type_descriptor *TDA,
|
|
tysan_type_descriptor *TDB,
|
|
uptr OffsetB) {
|
|
// This is handled by calls to isAliasingLegalUp.
|
|
if (OffsetB == 0)
|
|
return false;
|
|
|
|
// You can't have an offset into a member.
|
|
if (TDB->Tag == TYSAN_MEMBER_TD)
|
|
return false;
|
|
|
|
uptr OffsetA = 0;
|
|
if (TDA->Tag == TYSAN_MEMBER_TD) {
|
|
OffsetA = TDA->Member.Offset;
|
|
TDA = TDA->Member.Base;
|
|
}
|
|
|
|
// Since the access was partially inside TDB (the shadow), it can be assumed
|
|
// that we are accessing a member in an object. This means that rather than
|
|
// walk up the scalar access TDA to reach an object, we should walk up the
|
|
// object TBD to reach the scalar we are accessing it with. The offsets will
|
|
// still be checked at the end to make sure this alias is legal.
|
|
return walkAliasTree(TDB, TDA, OffsetB, OffsetA);
|
|
}
|
|
|
|
static bool isAliasingLegal(tysan_type_descriptor *TDA,
|
|
tysan_type_descriptor *TDB, uptr OffsetB = 0) {
|
|
if (TDA == TDB || !TDB || !TDA)
|
|
return true;
|
|
|
|
// Aliasing is legal is the two types have different root nodes.
|
|
if (getRootTD(TDA) != getRootTD(TDB))
|
|
return true;
|
|
|
|
// TDB may have been adjusted by offset TDAOffset in the caller to point to
|
|
// the outer type. Check for aliasing with and without adjusting for this
|
|
// offset.
|
|
return isAliasingLegalUp(TDA, TDB) || isAliasingLegalUp(TDB, TDA) ||
|
|
isAliasingLegalWithOffset(TDA, TDB, OffsetB);
|
|
}
|
|
|
|
namespace __tysan {
|
|
class Decorator : public __sanitizer::SanitizerCommonDecorator {
|
|
public:
|
|
Decorator() : SanitizerCommonDecorator() {}
|
|
const char *Warning() { return Red(); }
|
|
const char *Name() { return Green(); }
|
|
const char *End() { return Default(); }
|
|
};
|
|
} // namespace __tysan
|
|
|
|
ALWAYS_INLINE
|
|
static void reportError(void *Addr, int Size, tysan_type_descriptor *TD,
|
|
tysan_type_descriptor *OldTD, const char *AccessStr,
|
|
const char *DescStr, int Offset, uptr pc, uptr bp,
|
|
uptr sp) {
|
|
Decorator d;
|
|
Printf("%s", d.Warning());
|
|
Report("ERROR: TypeSanitizer: type-aliasing-violation on address %p"
|
|
" (pc %p bp %p sp %p tid %llu)\n",
|
|
Addr, (void *)pc, (void *)bp, (void *)sp, GetTid());
|
|
Printf("%s", d.End());
|
|
Printf("%s of size %d at %p with type ", AccessStr, Size, Addr);
|
|
|
|
Printf("%s", d.Name());
|
|
printTDName(TD);
|
|
Printf("%s", d.End());
|
|
|
|
Printf(" %s of type ", DescStr);
|
|
|
|
Printf("%s", d.Name());
|
|
printTDName(OldTD);
|
|
Printf("%s", d.End());
|
|
|
|
if (Offset != 0)
|
|
Printf(" that starts at offset %d\n", Offset);
|
|
else
|
|
Printf("\n");
|
|
|
|
if (pc) {
|
|
uptr top = 0;
|
|
uptr bottom = 0;
|
|
if (flags().print_stacktrace)
|
|
GetThreadStackTopAndBottom(false, &top, &bottom);
|
|
|
|
bool request_fast = StackTrace::WillUseFastUnwind(true);
|
|
BufferedStackTrace ST;
|
|
ST.Unwind(kStackTraceMax, pc, bp, 0, top, bottom, request_fast);
|
|
ST.Print();
|
|
} else {
|
|
Printf("\n");
|
|
}
|
|
}
|
|
|
|
extern "C" SANITIZER_INTERFACE_ATTRIBUTE void
|
|
__tysan_check(void *addr, int size, tysan_type_descriptor *td, int flags) {
|
|
GET_CALLER_PC_BP_SP;
|
|
|
|
bool IsRead = flags & 1;
|
|
bool IsWrite = flags & 2;
|
|
const char *AccessStr;
|
|
if (IsRead && !IsWrite)
|
|
AccessStr = "READ";
|
|
else if (!IsRead && IsWrite)
|
|
AccessStr = "WRITE";
|
|
else
|
|
AccessStr = "ATOMIC UPDATE";
|
|
|
|
tysan_type_descriptor **OldTDPtr = shadow_for(addr);
|
|
tysan_type_descriptor *OldTD = *OldTDPtr;
|
|
if (((sptr)OldTD) < 0) {
|
|
int i = -((sptr)OldTD);
|
|
OldTDPtr -= i;
|
|
OldTD = *OldTDPtr;
|
|
|
|
if (!isAliasingLegal(td, OldTD, i))
|
|
reportError(addr, size, td, OldTD, AccessStr,
|
|
"accesses part of an existing object", -i, pc, bp, sp);
|
|
|
|
return;
|
|
}
|
|
|
|
if (!isAliasingLegal(td, OldTD)) {
|
|
reportError(addr, size, td, OldTD, AccessStr, "accesses an existing object",
|
|
0, pc, bp, sp);
|
|
return;
|
|
}
|
|
|
|
// These types are allowed to alias (or the stored type is unknown), report
|
|
// an error if we find an interior type.
|
|
|
|
for (int i = 0; i < size; ++i) {
|
|
OldTDPtr = shadow_for((void *)(((uptr)addr) + i));
|
|
OldTD = *OldTDPtr;
|
|
if (((sptr)OldTD) >= 0 && !isAliasingLegal(td, OldTD))
|
|
reportError(addr, size, td, OldTD, AccessStr,
|
|
"partially accesses an object", i, pc, bp, sp);
|
|
}
|
|
}
|
|
|
|
Flags __tysan::flags_data;
|
|
|
|
SANITIZER_INTERFACE_ATTRIBUTE uptr __tysan_shadow_memory_address;
|
|
SANITIZER_INTERFACE_ATTRIBUTE uptr __tysan_app_memory_mask;
|
|
|
|
#ifdef TYSAN_RUNTIME_VMA
|
|
// Runtime detected VMA size.
|
|
int __tysan::vmaSize;
|
|
#endif
|
|
|
|
void Flags::SetDefaults() {
|
|
#define TYSAN_FLAG(Type, Name, DefaultValue, Description) Name = DefaultValue;
|
|
#include "tysan_flags.inc"
|
|
#undef TYSAN_FLAG
|
|
}
|
|
|
|
static void RegisterTySanFlags(FlagParser *parser, Flags *f) {
|
|
#define TYSAN_FLAG(Type, Name, DefaultValue, Description) \
|
|
RegisterFlag(parser, #Name, Description, &f->Name);
|
|
#include "tysan_flags.inc"
|
|
#undef TYSAN_FLAG
|
|
}
|
|
|
|
static void InitializeFlags() {
|
|
SetCommonFlagsDefaults();
|
|
{
|
|
CommonFlags cf;
|
|
cf.CopyFrom(*common_flags());
|
|
cf.external_symbolizer_path = GetEnv("TYSAN_SYMBOLIZER_PATH");
|
|
OverrideCommonFlags(cf);
|
|
}
|
|
|
|
flags().SetDefaults();
|
|
|
|
FlagParser parser;
|
|
RegisterCommonFlags(&parser);
|
|
RegisterTySanFlags(&parser, &flags());
|
|
parser.ParseString(GetEnv("TYSAN_OPTIONS"));
|
|
InitializeCommonFlags();
|
|
if (Verbosity())
|
|
ReportUnrecognizedFlags();
|
|
if (common_flags()->help)
|
|
parser.PrintFlagDescriptions();
|
|
}
|
|
|
|
static void TySanInitializePlatformEarly() {
|
|
AvoidCVE_2016_2143();
|
|
#ifdef TYSAN_RUNTIME_VMA
|
|
vmaSize = (MostSignificantSetBitIndex(GET_CURRENT_FRAME()) + 1);
|
|
#if defined(__aarch64__) && !SANITIZER_APPLE
|
|
if (vmaSize != 39 && vmaSize != 42 && vmaSize != 48) {
|
|
Printf("FATAL: TypeSanitizer: unsupported VMA range\n");
|
|
Printf("FATAL: Found %d - Supported 39, 42 and 48\n", vmaSize);
|
|
Die();
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
__sanitizer::InitializePlatformEarly();
|
|
|
|
__tysan_shadow_memory_address = ShadowAddr();
|
|
__tysan_app_memory_mask = AppMask();
|
|
}
|
|
|
|
namespace __tysan {
|
|
bool tysan_inited = false;
|
|
bool tysan_init_is_running;
|
|
} // namespace __tysan
|
|
|
|
extern "C" SANITIZER_INTERFACE_ATTRIBUTE void __tysan_init() {
|
|
CHECK(!tysan_init_is_running);
|
|
if (tysan_inited)
|
|
return;
|
|
tysan_init_is_running = true;
|
|
|
|
InitializeFlags();
|
|
TySanInitializePlatformEarly();
|
|
|
|
InitializeInterceptors();
|
|
|
|
if (!MmapFixedNoReserve(ShadowAddr(), AppAddr() - ShadowAddr()))
|
|
Die();
|
|
|
|
tysan_init_is_running = false;
|
|
tysan_inited = true;
|
|
}
|
|
|
|
#if SANITIZER_CAN_USE_PREINIT_ARRAY
|
|
__attribute__((section(".preinit_array"),
|
|
used)) static void (*tysan_init_ptr)() = __tysan_init;
|
|
#endif
|