Files
clang-p2996/compiler-rt/lib/tysan/tysan.cpp
Florian Hahn 641fbf1524 [TySan] Add initial Type Sanitizer runtime (#76261)
This patch introduces the runtime components for type sanitizer: a
sanitizer for type-based aliasing violations.

It is based on Hal Finkel's https://reviews.llvm.org/D32197.

C/C++ have type-based aliasing rules, and LLVM's optimizer can exploit
these given TBAA metadata added by Clang. Roughly, a pointer of given
type cannot be used to access an object of a different type (with, of
course, certain exceptions). Unfortunately, there's a lot of code in the
wild that violates these rules (e.g. for type punning), and such code
often must be built with -fno-strict-aliasing. Performance is often
sacrificed as a result. Part of the problem is the difficulty of finding
TBAA violations. Hopefully, this sanitizer will help.

For each TBAA type-access descriptor, encoded in LLVM's IR using
metadata, the corresponding instrumentation pass generates descriptor
tables. Thus, for each type (and access descriptor), we have a unique
pointer representation. Excepting anonymous-namespace types, these
tables are comdat, so the pointer values should be unique across the
program. The descriptors refer to other descriptors to form a type
aliasing tree (just like LLVM's TBAA metadata does). The instrumentation
handles the "fast path" (where the types match exactly and no
partial-overlaps are detected), and defers to the runtime to handle all
of the more-complicated cases. The runtime, of course, is also
responsible for reporting errors when those are detected.

The runtime uses essentially the same shadow memory region as tsan, and
we use 8 bytes of shadow memory, the size of the pointer to the type
descriptor, for every byte of accessed data in the program. The value 0
is used to represent an unknown type. The value -1 is used to represent
an interior byte (a byte that is part of a type, but not the first
byte). The instrumentation first checks for an exact match between the
type of the current access and the type for that address recorded in the
shadow memory. If it matches, it then checks the shadow for the
remainder of the bytes in the type to make sure that they're all -1. If
not, we call the runtime. If the exact match fails, we next check if the
value is 0 (i.e. unknown). If it is, then we check the shadow for the
remainder of the byes in the type (to make sure they're all 0). If
they're not, we call the runtime. We then set the shadow for the access
address and set the shadow for the remaining bytes in the type to -1
(i.e. marking them as interior bytes). If the type indicated by the
shadow memory for the access address is neither an exact match nor 0, we
call the runtime.

The instrumentation pass inserts calls to the memset intrinsic to set
the memory updated by memset, memcpy, and memmove, as well as
allocas/byval (and for lifetime.start/end) to reset the shadow memory to
reflect that the type is now unknown. The runtime intercepts memset,
memcpy, etc. to perform the same function for the library calls.

The runtime essentially repeats these checks, but uses the full TBAA
algorithm, just as the compiler does, to determine when two types are
permitted to alias. In a situation where access overlap has occurred and
aliasing is not permitted, an error is generated.

As a note, this implementation does not use the compressed shadow-memory
scheme discussed previously
(http://lists.llvm.org/pipermail/llvm-dev/2017-April/111766.html). That
scheme would not handle the struct-path (i.e. structure offset)
information that our TBAA represents. I expect we'll want to further
work on compressing the shadow-memory representation, but I think it
makes sense to do that as follow-up work.

This includes build fixes for Linux from Mingjie Xu.

Depends on #76260 (Clang support), #76259 (LLVM support)


PR: https://github.com/llvm/llvm-project/pull/76261
2024-12-17 18:49:50 +00:00

347 lines
9.4 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;
}
static bool isAliasingLegalUp(tysan_type_descriptor *TDA,
tysan_type_descriptor *TDB, int TDAOffset) {
// Walk up the tree starting with TDA to see if we reach 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 - TDAOffset;
TDA = TDA->Member.Base;
}
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;
}
OffsetA -= TDA->Struct.Members[Idx].Offset;
TDA = TDA->Struct.Members[Idx].Type;
} else {
CHECK(false && "invalid enum value");
break;
}
} while (TDA);
return false;
}
static bool isAliasingLegal(tysan_type_descriptor *TDA,
tysan_type_descriptor *TDB, int TDAOffset = 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, 0) || isAliasingLegalUp(TDB, TDA, 0) ||
isAliasingLegalUp(TDA, TDB, TDAOffset);
}
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) {
bool request_fast = StackTrace::WillUseFastUnwind(true);
BufferedStackTrace ST;
ST.Unwind(kStackTraceMax, pc, bp, 0, 0, 0, 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