diff options
Diffstat (limited to 'compiler-rt/lib/scudo')
-rw-r--r-- | compiler-rt/lib/scudo/CMakeLists.txt | 32 | ||||
-rw-r--r-- | compiler-rt/lib/scudo/scudo_allocator.cpp | 635 | ||||
-rw-r--r-- | compiler-rt/lib/scudo/scudo_allocator.h | 63 | ||||
-rw-r--r-- | compiler-rt/lib/scudo/scudo_flags.cpp | 81 | ||||
-rw-r--r-- | compiler-rt/lib/scudo/scudo_flags.h | 33 | ||||
-rw-r--r-- | compiler-rt/lib/scudo/scudo_flags.inc | 35 | ||||
-rw-r--r-- | compiler-rt/lib/scudo/scudo_interceptors.cpp | 75 | ||||
-rw-r--r-- | compiler-rt/lib/scudo/scudo_new_delete.cpp | 69 | ||||
-rw-r--r-- | compiler-rt/lib/scudo/scudo_termination.cpp | 41 | ||||
-rw-r--r-- | compiler-rt/lib/scudo/scudo_utils.cpp | 133 | ||||
-rw-r--r-- | compiler-rt/lib/scudo/scudo_utils.h | 59 |
11 files changed, 1256 insertions, 0 deletions
diff --git a/compiler-rt/lib/scudo/CMakeLists.txt b/compiler-rt/lib/scudo/CMakeLists.txt new file mode 100644 index 00000000000..ebf241eb451 --- /dev/null +++ b/compiler-rt/lib/scudo/CMakeLists.txt @@ -0,0 +1,32 @@ +add_custom_target(scudo) + +include_directories(..) + +set(SCUDO_CFLAGS ${SANITIZER_COMMON_CFLAGS}) +append_rtti_flag(OFF SCUDO_CFLAGS) +list(APPEND SCUDO_CFLAGS -msse4.2 -mcx16) + +set(SCUDO_SOURCES + scudo_allocator.cpp + scudo_flags.cpp + scudo_interceptors.cpp + scudo_new_delete.cpp + scudo_termination.cpp + scudo_utils.cpp) + +if(COMPILER_RT_HAS_SCUDO) + foreach(arch ${SCUDO_SUPPORTED_ARCH}) + add_compiler_rt_runtime(clang_rt.scudo + STATIC + ARCHS ${arch} + SOURCES ${SCUDO_SOURCES} + $<TARGET_OBJECTS:RTInterception.${arch}> + $<TARGET_OBJECTS:RTSanitizerCommonNoTermination.${arch}> + $<TARGET_OBJECTS:RTSanitizerCommonLibc.${arch}> + CFLAGS ${SCUDO_CFLAGS} + PARENT_TARGET scudo) + endforeach() +endif() + +add_dependencies(compiler-rt scudo) + diff --git a/compiler-rt/lib/scudo/scudo_allocator.cpp b/compiler-rt/lib/scudo/scudo_allocator.cpp new file mode 100644 index 00000000000..3ad499aed10 --- /dev/null +++ b/compiler-rt/lib/scudo/scudo_allocator.cpp @@ -0,0 +1,635 @@ +//===-- scudo_allocator.cpp -------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// Scudo Hardened Allocator implementation. +/// It uses the sanitizer_common allocator as a base and aims at mitigating +/// heap corruption vulnerabilities. It provides a checksum-guarded chunk +/// header, a delayed free list, and additional sanity checks. +/// +//===----------------------------------------------------------------------===// + +#include "scudo_allocator.h" +#include "scudo_utils.h" + +#include "sanitizer_common/sanitizer_allocator_interface.h" +#include "sanitizer_common/sanitizer_quarantine.h" + +#include <limits.h> +#include <pthread.h> +#include <smmintrin.h> + +#include <atomic> +#include <cstring> + +namespace __scudo { + +const uptr AllocatorSpace = ~0ULL; +const uptr AllocatorSize = 0x10000000000ULL; +const uptr MinAlignmentLog = 4; // 16 bytes for x64 +const uptr MaxAlignmentLog = 24; + +typedef DefaultSizeClassMap SizeClassMap; +typedef SizeClassAllocator64<AllocatorSpace, AllocatorSize, 0, SizeClassMap> + PrimaryAllocator; +typedef SizeClassAllocatorLocalCache<PrimaryAllocator> AllocatorCache; +typedef LargeMmapAllocator<> SecondaryAllocator; +typedef CombinedAllocator<PrimaryAllocator, AllocatorCache, SecondaryAllocator> + ScudoAllocator; + +static ScudoAllocator &getAllocator(); + +static thread_local Xorshift128Plus Prng; +// Global static cookie, initialized at start-up. +static u64 Cookie; + +enum ChunkState : u8 { + ChunkAvailable = 0, + ChunkAllocated = 1, + ChunkQuarantine = 2 +}; + +typedef unsigned __int128 PackedHeader; +typedef std::atomic<PackedHeader> AtomicPackedHeader; + +// Our header requires 128-bit of storage on x64 (the only platform supported +// as of now), which fits nicely with the alignment requirements. +// Having the offset saves us from using functions such as GetBlockBegin, that +// is fairly costly. Our first implementation used the MetaData as well, which +// offers the advantage of being stored away from the chunk itself, but +// accessing it was costly as well. +// The header will be atomically loaded and stored using the 16-byte primitives +// offered by the platform (likely requires cmpxchg16b support). +struct UnpackedHeader { + // 1st 8 bytes + u16 Checksum : 16; + u64 RequestedSize : 40; // Needed for reallocation purposes. + u8 State : 2; // available, allocated, or quarantined + u8 AllocType : 2; // malloc, new, new[], or memalign + u8 Unused_0_ : 4; + // 2nd 8 bytes + u64 Offset : 20; // Offset from the beginning of the backend + // allocation to the beginning chunk itself, in + // multiples of MinAlignment. See comment about its + // maximum value and test in Initialize. + u64 Unused_1_ : 28; + u16 Salt : 16; +}; + +COMPILER_CHECK(sizeof(UnpackedHeader) == sizeof(PackedHeader)); + +const uptr ChunkHeaderSize = sizeof(PackedHeader); + +struct ScudoChunk : UnpackedHeader { + // We can't use the offset member of the chunk itself, as we would double + // fetch it without any warranty that it wouldn't have been tampered. To + // prevent this, we work with a local copy of the header. + void *AllocBeg(UnpackedHeader *Header) { + return reinterpret_cast<void *>( + reinterpret_cast<uptr>(this) - (Header->Offset << MinAlignmentLog)); + } + + // CRC32 checksum of the Chunk pointer and its ChunkHeader. + // It currently uses the Intel Nehalem SSE4.2 crc32 64-bit instruction. + u16 Checksum(UnpackedHeader *Header) const { + u64 HeaderHolder[2]; + memcpy(HeaderHolder, Header, sizeof(HeaderHolder)); + u64 Crc = _mm_crc32_u64(Cookie, reinterpret_cast<uptr>(this)); + // This is somewhat of a shortcut. The checksum is stored in the 16 least + // significant bits of the first 8 bytes of the header, hence zero-ing + // those bits out. It would be more valid to zero the checksum field of the + // UnpackedHeader, but would require holding an additional copy of it. + Crc = _mm_crc32_u64(Crc, HeaderHolder[0] & 0xffffffffffff0000ULL); + Crc = _mm_crc32_u64(Crc, HeaderHolder[1]); + return static_cast<u16>(Crc); + } + + // Loads and unpacks the header, verifying the checksum in the process. + void loadHeader(UnpackedHeader *NewUnpackedHeader) const { + const AtomicPackedHeader *AtomicHeader = + reinterpret_cast<const AtomicPackedHeader *>(this); + PackedHeader NewPackedHeader = + AtomicHeader->load(std::memory_order_relaxed); + *NewUnpackedHeader = bit_cast<UnpackedHeader>(NewPackedHeader); + if ((NewUnpackedHeader->Unused_0_ != 0) || + (NewUnpackedHeader->Unused_1_ != 0) || + (NewUnpackedHeader->Checksum != Checksum(NewUnpackedHeader))) { + dieWithMessage("ERROR: corrupted chunk header at address %p\n", this); + } + } + + // Packs and stores the header, computing the checksum in the process. + void storeHeader(UnpackedHeader *NewUnpackedHeader) { + NewUnpackedHeader->Checksum = Checksum(NewUnpackedHeader); + PackedHeader NewPackedHeader = bit_cast<PackedHeader>(*NewUnpackedHeader); + AtomicPackedHeader *AtomicHeader = + reinterpret_cast<AtomicPackedHeader *>(this); + AtomicHeader->store(NewPackedHeader, std::memory_order_relaxed); + } + + // Packs and stores the header, computing the checksum in the process. We + // compare the current header with the expected provided one to ensure that + // we are not being raced by a corruption occurring in another thread. + void compareExchangeHeader(UnpackedHeader *NewUnpackedHeader, + UnpackedHeader *OldUnpackedHeader) { + NewUnpackedHeader->Checksum = Checksum(NewUnpackedHeader); + PackedHeader NewPackedHeader = bit_cast<PackedHeader>(*NewUnpackedHeader); + PackedHeader OldPackedHeader = bit_cast<PackedHeader>(*OldUnpackedHeader); + AtomicPackedHeader *AtomicHeader = + reinterpret_cast<AtomicPackedHeader *>(this); + if (!AtomicHeader->compare_exchange_strong(OldPackedHeader, + NewPackedHeader, + std::memory_order_relaxed, + std::memory_order_relaxed)) { + dieWithMessage("ERROR: race on chunk header at address %p\n", this); + } + } +}; + +static bool ScudoInitIsRunning = false; + +static pthread_once_t GlobalInited = PTHREAD_ONCE_INIT; +static pthread_key_t pkey; + +static thread_local bool ThreadInited = false; +static thread_local bool ThreadTornDown = false; +static thread_local AllocatorCache Cache; + +static void teardownThread(void *p) { + uptr v = reinterpret_cast<uptr>(p); + // The glibc POSIX thread-local-storage deallocation routine calls user + // provided destructors in a loop of PTHREAD_DESTRUCTOR_ITERATIONS. + // We want to be called last since other destructors might call free and the + // like, so we wait until PTHREAD_DESTRUCTOR_ITERATIONS before draining the + // quarantine and swallowing the cache. + if (v < PTHREAD_DESTRUCTOR_ITERATIONS) { + pthread_setspecific(pkey, reinterpret_cast<void *>(v + 1)); + return; + } + drainQuarantine(); + getAllocator().DestroyCache(&Cache); + ThreadTornDown = true; +} + +static void initInternal() { + SanitizerToolName = "Scudo"; + CHECK(!ScudoInitIsRunning && "Scudo init calls itself!"); + ScudoInitIsRunning = true; + + initFlags(); + + AllocatorOptions Options; + Options.setFrom(getFlags(), common_flags()); + initAllocator(Options); + + ScudoInitIsRunning = false; +} + +static void initGlobal() { + pthread_key_create(&pkey, teardownThread); + initInternal(); +} + +static void NOINLINE initThread() { + pthread_once(&GlobalInited, initGlobal); + pthread_setspecific(pkey, reinterpret_cast<void *>(1)); + getAllocator().InitCache(&Cache); + ThreadInited = true; +} + +struct QuarantineCallback { + explicit QuarantineCallback(AllocatorCache *Cache) + : Cache_(Cache) {} + + // Chunk recycling function, returns a quarantined chunk to the backend. + void Recycle(ScudoChunk *Chunk) { + UnpackedHeader Header; + Chunk->loadHeader(&Header); + if (Header.State != ChunkQuarantine) { + dieWithMessage("ERROR: invalid chunk state when recycling address %p\n", + Chunk); + } + void *Ptr = Chunk->AllocBeg(&Header); + getAllocator().Deallocate(Cache_, Ptr); + } + + /// Internal quarantine allocation and deallocation functions. + void *Allocate(uptr Size) { + // The internal quarantine memory cannot be protected by us. But the only + // structures allocated are QuarantineBatch, that are 8KB for x64. So we + // will use mmap for those, and given that Deallocate doesn't pass a size + // in, we enforce the size of the allocation to be sizeof(QuarantineBatch). + // TODO(kostyak): switching to mmap impacts greatly performances, we have + // to find another solution + // CHECK_EQ(Size, sizeof(QuarantineBatch)); + // return MmapOrDie(Size, "QuarantineBatch"); + return getAllocator().Allocate(Cache_, Size, 1, false); + } + + void Deallocate(void *Ptr) { + // UnmapOrDie(Ptr, sizeof(QuarantineBatch)); + getAllocator().Deallocate(Cache_, Ptr); + } + + AllocatorCache *Cache_; +}; + +typedef Quarantine<QuarantineCallback, ScudoChunk> ScudoQuarantine; +typedef ScudoQuarantine::Cache QuarantineCache; +static thread_local QuarantineCache ThreadQuarantineCache; + +void AllocatorOptions::setFrom(const Flags *f, const CommonFlags *cf) { + MayReturnNull = cf->allocator_may_return_null; + QuarantineSizeMb = f->QuarantineSizeMb; + ThreadLocalQuarantineSizeKb = f->ThreadLocalQuarantineSizeKb; + DeallocationTypeMismatch = f->DeallocationTypeMismatch; + DeleteSizeMismatch = f->DeleteSizeMismatch; + ZeroContents = f->ZeroContents; +} + +void AllocatorOptions::copyTo(Flags *f, CommonFlags *cf) const { + cf->allocator_may_return_null = MayReturnNull; + f->QuarantineSizeMb = QuarantineSizeMb; + f->ThreadLocalQuarantineSizeKb = ThreadLocalQuarantineSizeKb; + f->DeallocationTypeMismatch = DeallocationTypeMismatch; + f->DeleteSizeMismatch = DeleteSizeMismatch; + f->ZeroContents = ZeroContents; +} + +struct Allocator { + static const uptr MaxAllowedMallocSize = 1ULL << 40; + static const uptr MinAlignment = 1 << MinAlignmentLog; + static const uptr MaxAlignment = 1 << MaxAlignmentLog; // 16 MB + + ScudoAllocator BackendAllocator; + ScudoQuarantine AllocatorQuarantine; + + // The fallback caches are used when the thread local caches have been + // 'detroyed' on thread tear-down. They are protected by a Mutex as they can + // be accessed by different threads. + StaticSpinMutex FallbackMutex; + AllocatorCache FallbackAllocatorCache; + QuarantineCache FallbackQuarantineCache; + + bool DeallocationTypeMismatch; + bool ZeroContents; + bool DeleteSizeMismatch; + + explicit Allocator(LinkerInitialized) + : AllocatorQuarantine(LINKER_INITIALIZED), + FallbackQuarantineCache(LINKER_INITIALIZED) {} + + void init(const AllocatorOptions &Options) { + // Currently SSE 4.2 support is required. This might change later. + CHECK(testCPUFeature(SSE4_2)); // for crc32 + + // Verify that the header offset field can hold the maximum offset. In the + // worst case scenario, the backend allocation is already aligned on + // MaxAlignment, so in order to store the header and still be aligned, we + // add an extra MaxAlignment. As a result, the offset from the beginning of + // the backend allocation to the chunk will be MaxAlignment - + // ChunkHeaderSize. + UnpackedHeader Header = {}; + uptr MaximumOffset = (MaxAlignment - ChunkHeaderSize) >> MinAlignmentLog; + Header.Offset = MaximumOffset; + if (Header.Offset != MaximumOffset) { + dieWithMessage("ERROR: the maximum possible offset doesn't fit in the " + "header\n"); + } + + DeallocationTypeMismatch = Options.DeallocationTypeMismatch; + DeleteSizeMismatch = Options.DeleteSizeMismatch; + ZeroContents = Options.ZeroContents; + BackendAllocator.Init(Options.MayReturnNull); + AllocatorQuarantine.Init(static_cast<uptr>(Options.QuarantineSizeMb) << 20, + static_cast<uptr>( + Options.ThreadLocalQuarantineSizeKb) << 10); + BackendAllocator.InitCache(&FallbackAllocatorCache); + Cookie = Prng.Next(); + } + + // Allocates a chunk. + void *allocate(uptr Size, uptr Alignment, AllocType Type) { + if (UNLIKELY(!ThreadInited)) + initThread(); + if (!IsPowerOfTwo(Alignment)) { + dieWithMessage("ERROR: malloc alignment is not a power of 2\n"); + } + if (Alignment > MaxAlignment) + return BackendAllocator.ReturnNullOrDie(); + if (Alignment < MinAlignment) + Alignment = MinAlignment; + if (Size == 0) + Size = 1; + if (Size >= MaxAllowedMallocSize) + return BackendAllocator.ReturnNullOrDie(); + uptr RoundedSize = RoundUpTo(Size, MinAlignment); + uptr ExtraBytes = ChunkHeaderSize; + if (Alignment > MinAlignment) + ExtraBytes += Alignment; + uptr NeededSize = RoundedSize + ExtraBytes; + if (NeededSize >= MaxAllowedMallocSize) + return BackendAllocator.ReturnNullOrDie(); + + void *Ptr; + if (LIKELY(!ThreadTornDown)) { + Ptr = BackendAllocator.Allocate(&Cache, NeededSize, MinAlignment); + } else { + SpinMutexLock l(&FallbackMutex); + Ptr = BackendAllocator.Allocate(&FallbackAllocatorCache, NeededSize, + MinAlignment); + } + if (!Ptr) + return BackendAllocator.ReturnNullOrDie(); + + // If requested, we will zero out the entire contents of the returned chunk. + if (ZeroContents && BackendAllocator.FromPrimary(Ptr)) + memset(Ptr, 0, BackendAllocator.GetActuallyAllocatedSize(Ptr)); + + uptr AllocBeg = reinterpret_cast<uptr>(Ptr); + uptr ChunkBeg = AllocBeg + ChunkHeaderSize; + if (!IsAligned(ChunkBeg, Alignment)) + ChunkBeg = RoundUpTo(ChunkBeg, Alignment); + CHECK_LE(ChunkBeg + Size, AllocBeg + NeededSize); + ScudoChunk *Chunk = + reinterpret_cast<ScudoChunk *>(ChunkBeg - ChunkHeaderSize); + UnpackedHeader Header = {}; + Header.State = ChunkAllocated; + Header.Offset = (ChunkBeg - ChunkHeaderSize - AllocBeg) >> MinAlignmentLog; + Header.AllocType = Type; + Header.RequestedSize = Size; + Header.Salt = static_cast<u16>(Prng.Next()); + Chunk->storeHeader(&Header); + void *UserPtr = reinterpret_cast<void *>(ChunkBeg); + // TODO(kostyak): hooks sound like a terrible idea security wise but might + // be needed for things to work properly? + // if (&__sanitizer_malloc_hook) __sanitizer_malloc_hook(UserPtr, Size); + return UserPtr; + } + + // Deallocates a Chunk, which means adding it to the delayed free list (or + // Quarantine). + void deallocate(void *UserPtr, uptr DeleteSize, AllocType Type) { + if (UNLIKELY(!ThreadInited)) + initThread(); + // TODO(kostyak): see hook comment above + // if (&__sanitizer_free_hook) __sanitizer_free_hook(UserPtr); + if (!UserPtr) + return; + uptr ChunkBeg = reinterpret_cast<uptr>(UserPtr); + if (!IsAligned(ChunkBeg, MinAlignment)) { + dieWithMessage("ERROR: attempted to deallocate a chunk not properly " + "aligned at address %p\n", UserPtr); + } + ScudoChunk *Chunk = + reinterpret_cast<ScudoChunk *>(ChunkBeg - ChunkHeaderSize); + UnpackedHeader OldHeader; + Chunk->loadHeader(&OldHeader); + if (OldHeader.State != ChunkAllocated) { + dieWithMessage("ERROR: invalid chunk state when deallocating address " + "%p\n", Chunk); + } + UnpackedHeader NewHeader = OldHeader; + NewHeader.State = ChunkQuarantine; + Chunk->compareExchangeHeader(&NewHeader, &OldHeader); + if (DeallocationTypeMismatch) { + // The deallocation type has to match the allocation one. + if (NewHeader.AllocType != Type) { + // With the exception of memalign'd Chunks, that can be still be free'd. + if (NewHeader.AllocType != FromMemalign || Type != FromMalloc) { + dieWithMessage("ERROR: allocation type mismatch on address %p\n", + Chunk); + } + } + } + uptr Size = NewHeader.RequestedSize; + if (DeleteSizeMismatch) { + if (DeleteSize && DeleteSize != Size) { + dieWithMessage("ERROR: invalid sized delete on chunk at address %p\n", + Chunk); + } + } + if (LIKELY(!ThreadTornDown)) { + AllocatorQuarantine.Put(&ThreadQuarantineCache, + QuarantineCallback(&Cache), Chunk, Size); + } else { + SpinMutexLock l(&FallbackMutex); + AllocatorQuarantine.Put(&FallbackQuarantineCache, + QuarantineCallback(&FallbackAllocatorCache), + Chunk, Size); + } + } + + // Returns the actual usable size of a chunk. Since this requires loading the + // header, we will return it in the second parameter, as it can be required + // by the caller to perform additional processing. + uptr getUsableSize(const void *Ptr, UnpackedHeader *Header) { + if (UNLIKELY(!ThreadInited)) + initThread(); + if (!Ptr) + return 0; + uptr ChunkBeg = reinterpret_cast<uptr>(Ptr); + ScudoChunk *Chunk = + reinterpret_cast<ScudoChunk *>(ChunkBeg - ChunkHeaderSize); + Chunk->loadHeader(Header); + // Getting the usable size of a chunk only makes sense if it's allocated. + if (Header->State != ChunkAllocated) { + dieWithMessage("ERROR: attempted to size a non-allocated chunk at " + "address %p\n", Chunk); + } + uptr Size = + BackendAllocator.GetActuallyAllocatedSize(Chunk->AllocBeg(Header)); + // UsableSize works as malloc_usable_size, which is also what (AFAIU) + // tcmalloc's MallocExtension::GetAllocatedSize aims at providing. This + // means we will return the size of the chunk from the user beginning to + // the end of the 'user' allocation, hence us subtracting the header size + // and the offset from the size. + if (Size == 0) + return Size; + return Size - ChunkHeaderSize - (Header->Offset << MinAlignmentLog); + } + + // Helper function that doesn't care about the header. + uptr getUsableSize(const void *Ptr) { + UnpackedHeader Header; + return getUsableSize(Ptr, &Header); + } + + // Reallocates a chunk. We can save on a new allocation if the new requested + // size still fits in the chunk. + void *reallocate(void *OldPtr, uptr NewSize) { + if (UNLIKELY(!ThreadInited)) + initThread(); + UnpackedHeader OldHeader; + uptr Size = getUsableSize(OldPtr, &OldHeader); + uptr ChunkBeg = reinterpret_cast<uptr>(OldPtr); + ScudoChunk *Chunk = + reinterpret_cast<ScudoChunk *>(ChunkBeg - ChunkHeaderSize); + if (OldHeader.AllocType != FromMalloc) { + dieWithMessage("ERROR: invalid chunk type when reallocating address %p\n", + Chunk); + } + UnpackedHeader NewHeader = OldHeader; + // The new size still fits in the current chunk. + if (NewSize <= Size) { + NewHeader.RequestedSize = NewSize; + Chunk->compareExchangeHeader(&NewHeader, &OldHeader); + return OldPtr; + } + // Otherwise, we have to allocate a new chunk and copy the contents of the + // old one. + void *NewPtr = allocate(NewSize, MinAlignment, FromMalloc); + if (NewPtr) { + uptr OldSize = OldHeader.RequestedSize; + memcpy(NewPtr, OldPtr, Min(NewSize, OldSize)); + NewHeader.State = ChunkQuarantine; + Chunk->compareExchangeHeader(&NewHeader, &OldHeader); + if (LIKELY(!ThreadTornDown)) { + AllocatorQuarantine.Put(&ThreadQuarantineCache, + QuarantineCallback(&Cache), Chunk, OldSize); + } else { + SpinMutexLock l(&FallbackMutex); + AllocatorQuarantine.Put(&FallbackQuarantineCache, + QuarantineCallback(&FallbackAllocatorCache), + Chunk, OldSize); + } + } + return NewPtr; + } + + void *calloc(uptr NMemB, uptr Size) { + if (UNLIKELY(!ThreadInited)) + initThread(); + uptr Total = NMemB * Size; + if (Size != 0 && Total / Size != NMemB) // Overflow check + return BackendAllocator.ReturnNullOrDie(); + void *Ptr = allocate(Total, MinAlignment, FromMalloc); + // If ZeroContents, the content of the chunk has already been zero'd out. + if (!ZeroContents && Ptr && BackendAllocator.FromPrimary(Ptr)) + memset(Ptr, 0, getUsableSize(Ptr)); + return Ptr; + } + + void drainQuarantine() { + AllocatorQuarantine.Drain(&ThreadQuarantineCache, + QuarantineCallback(&Cache)); + } +}; + +static Allocator Instance(LINKER_INITIALIZED); + +static ScudoAllocator &getAllocator() { + return Instance.BackendAllocator; +} + +void initAllocator(const AllocatorOptions &Options) { + Instance.init(Options); +} + +void drainQuarantine() { + Instance.drainQuarantine(); +} + +void *scudoMalloc(uptr Size, AllocType Type) { + return Instance.allocate(Size, Allocator::MinAlignment, Type); +} + +void scudoFree(void *Ptr, AllocType Type) { + Instance.deallocate(Ptr, 0, Type); +} + +void scudoSizedFree(void *Ptr, uptr Size, AllocType Type) { + Instance.deallocate(Ptr, Size, Type); +} + +void *scudoRealloc(void *Ptr, uptr Size) { + if (!Ptr) + return Instance.allocate(Size, Allocator::MinAlignment, FromMalloc); + if (Size == 0) { + Instance.deallocate(Ptr, 0, FromMalloc); + return nullptr; + } + return Instance.reallocate(Ptr, Size); +} + +void *scudoCalloc(uptr NMemB, uptr Size) { + return Instance.calloc(NMemB, Size); +} + +void *scudoValloc(uptr Size) { + return Instance.allocate(Size, GetPageSizeCached(), FromMemalign); +} + +void *scudoMemalign(uptr Alignment, uptr Size) { + return Instance.allocate(Size, Alignment, FromMemalign); +} + +void *scudoPvalloc(uptr Size) { + uptr PageSize = GetPageSizeCached(); + Size = RoundUpTo(Size, PageSize); + if (Size == 0) { + // pvalloc(0) should allocate one page. + Size = PageSize; + } + return Instance.allocate(Size, PageSize, FromMemalign); +} + +int scudoPosixMemalign(void **MemPtr, uptr Alignment, uptr Size) { + *MemPtr = Instance.allocate(Size, Alignment, FromMemalign); + return 0; +} + +void *scudoAlignedAlloc(uptr Alignment, uptr Size) { + // size must be a multiple of the alignment. To avoid a division, we first + // make sure that alignment is a power of 2. + CHECK(IsPowerOfTwo(Alignment)); + CHECK_EQ((Size & (Alignment - 1)), 0); + return Instance.allocate(Size, Alignment, FromMalloc); +} + +uptr scudoMallocUsableSize(void *Ptr) { + return Instance.getUsableSize(Ptr); +} + +} // namespace __scudo + +using namespace __scudo; + +// MallocExtension helper functions + +uptr __sanitizer_get_current_allocated_bytes() { + uptr stats[AllocatorStatCount]; + getAllocator().GetStats(stats); + return stats[AllocatorStatAllocated]; +} + +uptr __sanitizer_get_heap_size() { + uptr stats[AllocatorStatCount]; + getAllocator().GetStats(stats); + return stats[AllocatorStatMapped]; +} + +uptr __sanitizer_get_free_bytes() { + return 1; +} + +uptr __sanitizer_get_unmapped_bytes() { + return 1; +} + +uptr __sanitizer_get_estimated_allocated_size(uptr size) { + return size; +} + +int __sanitizer_get_ownership(const void *p) { + return Instance.getUsableSize(p) != 0; +} + +uptr __sanitizer_get_allocated_size(const void *p) { + return Instance.getUsableSize(p); +} diff --git a/compiler-rt/lib/scudo/scudo_allocator.h b/compiler-rt/lib/scudo/scudo_allocator.h new file mode 100644 index 00000000000..7e9c7886055 --- /dev/null +++ b/compiler-rt/lib/scudo/scudo_allocator.h @@ -0,0 +1,63 @@ +//===-- scudo_allocator.h ---------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// Header for scudo_allocator.cpp. +/// +//===----------------------------------------------------------------------===// + +#ifndef SCUDO_ALLOCATOR_H_ +#define SCUDO_ALLOCATOR_H_ + +#ifndef __x86_64__ +# error "The Scudo hardened allocator currently only supports x86_64." +#endif + +#include "scudo_flags.h" + +#include "sanitizer_common/sanitizer_allocator.h" + +namespace __scudo { + +enum AllocType : u8 { + FromMalloc = 0, // Memory block came from malloc, realloc, calloc, etc. + FromNew = 1, // Memory block came from operator new. + FromNewArray = 2, // Memory block came from operator new []. + FromMemalign = 3, // Memory block came from memalign, posix_memalign, etc. +}; + +struct AllocatorOptions { + u32 QuarantineSizeMb; + u32 ThreadLocalQuarantineSizeKb; + bool MayReturnNull; + bool DeallocationTypeMismatch; + bool DeleteSizeMismatch; + bool ZeroContents; + + void setFrom(const Flags *f, const CommonFlags *cf); + void copyTo(Flags *f, CommonFlags *cf) const; +}; + +void initAllocator(const AllocatorOptions &options); +void drainQuarantine(); + +void *scudoMalloc(uptr Size, AllocType Type); +void scudoFree(void *Ptr, AllocType Type); +void scudoSizedFree(void *Ptr, uptr Size, AllocType Type); +void *scudoRealloc(void *Ptr, uptr Size); +void *scudoCalloc(uptr NMemB, uptr Size); +void *scudoMemalign(uptr Alignment, uptr Size); +void *scudoValloc(uptr Size); +void *scudoPvalloc(uptr Size); +int scudoPosixMemalign(void **MemPtr, uptr Alignment, uptr Size); +void *scudoAlignedAlloc(uptr Alignment, uptr Size); +uptr scudoMallocUsableSize(void *Ptr); + +} // namespace __scudo + +#endif // SCUDO_ALLOCATOR_H_ diff --git a/compiler-rt/lib/scudo/scudo_flags.cpp b/compiler-rt/lib/scudo/scudo_flags.cpp new file mode 100644 index 00000000000..430dcd2995d --- /dev/null +++ b/compiler-rt/lib/scudo/scudo_flags.cpp @@ -0,0 +1,81 @@ +//===-- scudo_flags.cpp -----------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// Hardened Allocator flag parsing logic. +/// +//===----------------------------------------------------------------------===// + +#include "scudo_flags.h" +#include "scudo_utils.h" + +#include "sanitizer_common/sanitizer_flags.h" +#include "sanitizer_common/sanitizer_flag_parser.h" + +namespace __scudo { + +Flags scudo_flags_dont_use_directly; // use via flags(). + +void Flags::setDefaults() { +#define SCUDO_FLAG(Type, Name, DefaultValue, Description) Name = DefaultValue; +#include "scudo_flags.inc" +#undef SCUDO_FLAG +} + +static void RegisterScudoFlags(FlagParser *parser, Flags *f) { +#define SCUDO_FLAG(Type, Name, DefaultValue, Description) \ + RegisterFlag(parser, #Name, Description, &f->Name); +#include "scudo_flags.inc" +#undef SCUDO_FLAG +} + +void initFlags() { + SetCommonFlagsDefaults(); + { + CommonFlags cf; + cf.CopyFrom(*common_flags()); + cf.exitcode = 1; + OverrideCommonFlags(cf); + } + Flags *f = getFlags(); + f->setDefaults(); + + FlagParser scudo_parser; + RegisterScudoFlags(&scudo_parser, f); + RegisterCommonFlags(&scudo_parser); + + scudo_parser.ParseString(GetEnv("SCUDO_OPTIONS")); + + InitializeCommonFlags(); + + // Sanity checks and default settings for the Quarantine parameters. + + if (f->QuarantineSizeMb < 0) { + const int DefaultQuarantineSizeMb = 64; + f->QuarantineSizeMb = DefaultQuarantineSizeMb; + } + // We enforce an upper limit for the quarantine size of 4Gb. + if (f->QuarantineSizeMb > (4 * 1024)) { + dieWithMessage("ERROR: the quarantine size is too large\n"); + } + if (f->ThreadLocalQuarantineSizeKb < 0) { + const int DefaultThreadLocalQuarantineSizeKb = 1024; + f->ThreadLocalQuarantineSizeKb = DefaultThreadLocalQuarantineSizeKb; + } + // And an upper limit of 128Mb for the thread quarantine cache. + if (f->ThreadLocalQuarantineSizeKb > (128 * 1024)) { + dieWithMessage("ERROR: the per thread quarantine cache size is too " + "large\n"); + } +} + +Flags *getFlags() { + return &scudo_flags_dont_use_directly; +} + +} diff --git a/compiler-rt/lib/scudo/scudo_flags.h b/compiler-rt/lib/scudo/scudo_flags.h new file mode 100644 index 00000000000..c16f635d366 --- /dev/null +++ b/compiler-rt/lib/scudo/scudo_flags.h @@ -0,0 +1,33 @@ +//===-- scudo_flags.h -------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// Header for scudo_flags.cpp. +/// +//===----------------------------------------------------------------------===// + +#ifndef SCUDO_FLAGS_H_ +#define SCUDO_FLAGS_H_ + +namespace __scudo { + +struct Flags { +#define SCUDO_FLAG(Type, Name, DefaultValue, Description) Type Name; +#include "scudo_flags.inc" +#undef SCUDO_FLAG + + void setDefaults(); +}; + +Flags *getFlags(); + +void initFlags(); + +} // namespace __scudo + +#endif // SCUDO_FLAGS_H_ diff --git a/compiler-rt/lib/scudo/scudo_flags.inc b/compiler-rt/lib/scudo/scudo_flags.inc new file mode 100644 index 00000000000..c7a2acf146c --- /dev/null +++ b/compiler-rt/lib/scudo/scudo_flags.inc @@ -0,0 +1,35 @@ +//===-- scudo_flags.inc -----------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// Hardened Allocator runtime flags. +/// +//===----------------------------------------------------------------------===// + +#ifndef SCUDO_FLAG +# error "Define SCUDO_FLAG prior to including this file!" +#endif + +SCUDO_FLAG(int, QuarantineSizeMb, 64, + "Size (in Mb) of quarantine used to delay the actual deallocation " + "of chunks. Lower value may reduce memory usage but decrease the " + "effectiveness of the mitigation.") + +SCUDO_FLAG(int, ThreadLocalQuarantineSizeKb, 1024, + "Size (in Kb) of per-thread cache used to offload the global " + "quarantine. Lower value may reduce memory usage but might increase " + "the contention on the global quarantine.") + +SCUDO_FLAG(bool, DeallocationTypeMismatch, true, + "Report errors on malloc/delete, new/free, new/delete[], etc.") + +SCUDO_FLAG(bool, DeleteSizeMismatch, true, + "Report errors on mismatch between size of new and delete.") + +SCUDO_FLAG(bool, ZeroContents, false, + "Zero chunk contents on allocation and deallocation.") diff --git a/compiler-rt/lib/scudo/scudo_interceptors.cpp b/compiler-rt/lib/scudo/scudo_interceptors.cpp new file mode 100644 index 00000000000..9204652d8b7 --- /dev/null +++ b/compiler-rt/lib/scudo/scudo_interceptors.cpp @@ -0,0 +1,75 @@ +//===-- scudo_interceptors.cpp ----------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// Linux specific malloc interception functions. +/// +//===----------------------------------------------------------------------===// + +#include "sanitizer_common/sanitizer_platform.h" +#if SANITIZER_LINUX + +#include "scudo_allocator.h" + +#include "interception/interception.h" + +using namespace __scudo; + +INTERCEPTOR(void, free, void *ptr) { + scudoFree(ptr, FromMalloc); +} + +INTERCEPTOR(void, cfree, void *ptr) { + scudoFree(ptr, FromMalloc); +} + +INTERCEPTOR(void*, malloc, uptr size) { + return scudoMalloc(size, FromMalloc); +} + +INTERCEPTOR(void*, realloc, void *ptr, uptr size) { + return scudoRealloc(ptr, size); +} + +INTERCEPTOR(void*, calloc, uptr nmemb, uptr size) { + return scudoCalloc(nmemb, size); +} + +INTERCEPTOR(void*, valloc, uptr size) { + return scudoValloc(size); +} + +INTERCEPTOR(void*, memalign, uptr alignment, uptr size) { + return scudoMemalign(alignment, size); +} + +INTERCEPTOR(void*, __libc_memalign, uptr alignment, uptr size) { + return scudoMemalign(alignment, size); +} + +INTERCEPTOR(void*, pvalloc, uptr size) { + return scudoPvalloc(size); +} + +INTERCEPTOR(void*, aligned_alloc, uptr alignment, uptr size) { + return scudoAlignedAlloc(alignment, size); +} + +INTERCEPTOR(int, posix_memalign, void **memptr, uptr alignment, uptr size) { + return scudoPosixMemalign(memptr, alignment, size); +} + +INTERCEPTOR(uptr, malloc_usable_size, void *ptr) { + return scudoMallocUsableSize(ptr); +} + +INTERCEPTOR(int, mallopt, int cmd, int value) { + return -1; +} + +#endif // SANITIZER_LINUX diff --git a/compiler-rt/lib/scudo/scudo_new_delete.cpp b/compiler-rt/lib/scudo/scudo_new_delete.cpp new file mode 100644 index 00000000000..172f5659c93 --- /dev/null +++ b/compiler-rt/lib/scudo/scudo_new_delete.cpp @@ -0,0 +1,69 @@ +//===-- scudo_new_delete.cpp ------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// Interceptors for operators new and delete. +/// +//===----------------------------------------------------------------------===// + +#include "scudo_allocator.h" + +#include "interception/interception.h" + +#include <cstddef> + +using namespace __scudo; + +#define CXX_OPERATOR_ATTRIBUTE INTERCEPTOR_ATTRIBUTE + +// Fake std::nothrow_t to avoid including <new>. +namespace std { +struct nothrow_t {}; +} // namespace std + +CXX_OPERATOR_ATTRIBUTE +void *operator new(size_t size) { + return scudoMalloc(size, FromNew); +} +CXX_OPERATOR_ATTRIBUTE +void *operator new[](size_t size) { + return scudoMalloc(size, FromNewArray); +} +CXX_OPERATOR_ATTRIBUTE +void *operator new(size_t size, std::nothrow_t const&) { + return scudoMalloc(size, FromNew); +} +CXX_OPERATOR_ATTRIBUTE +void *operator new[](size_t size, std::nothrow_t const&) { + return scudoMalloc(size, FromNewArray); +} + +CXX_OPERATOR_ATTRIBUTE +void operator delete(void *ptr) NOEXCEPT { + return scudoFree(ptr, FromNew); +} +CXX_OPERATOR_ATTRIBUTE +void operator delete[](void *ptr) NOEXCEPT { + return scudoFree(ptr, FromNewArray); +} +CXX_OPERATOR_ATTRIBUTE +void operator delete(void *ptr, std::nothrow_t const&) NOEXCEPT { + return scudoFree(ptr, FromNew); +} +CXX_OPERATOR_ATTRIBUTE +void operator delete[](void *ptr, std::nothrow_t const&) NOEXCEPT { + return scudoFree(ptr, FromNewArray); +} +CXX_OPERATOR_ATTRIBUTE +void operator delete(void *ptr, size_t size) NOEXCEPT { + scudoSizedFree(ptr, size, FromNew); +} +CXX_OPERATOR_ATTRIBUTE +void operator delete[](void *ptr, size_t size) NOEXCEPT { + scudoSizedFree(ptr, size, FromNewArray); +} diff --git a/compiler-rt/lib/scudo/scudo_termination.cpp b/compiler-rt/lib/scudo/scudo_termination.cpp new file mode 100644 index 00000000000..32421d3d810 --- /dev/null +++ b/compiler-rt/lib/scudo/scudo_termination.cpp @@ -0,0 +1,41 @@ +//===-- scudo_termination.cpp -----------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// This file contains bare-bones termination functions to replace the +/// __sanitizer ones, in order to avoid any potential abuse of the callbacks +/// functionality. +/// +//===----------------------------------------------------------------------===// + +#include "sanitizer_common/sanitizer_common.h" + +namespace __sanitizer { + +bool AddDieCallback(DieCallbackType callback) { return true; } + +bool RemoveDieCallback(DieCallbackType callback) { return true; } + +void SetUserDieCallback(DieCallbackType callback) {} + +void NORETURN Die() { + if (common_flags()->abort_on_error) + Abort(); + internal__exit(common_flags()->exitcode); +} + +void SetCheckFailedCallback(CheckFailedCallbackType callback) {} + +void NORETURN CheckFailed(const char *file, int line, const char *cond, + u64 v1, u64 v2) { + Report("Sanitizer CHECK failed: %s:%d %s (%lld, %lld)\n", file, line, cond, + v1, v2); + Die(); +} + +} // namespace __sanitizer diff --git a/compiler-rt/lib/scudo/scudo_utils.cpp b/compiler-rt/lib/scudo/scudo_utils.cpp new file mode 100644 index 00000000000..6b96e84ee9d --- /dev/null +++ b/compiler-rt/lib/scudo/scudo_utils.cpp @@ -0,0 +1,133 @@ +//===-- scudo_utils.cpp -----------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// Platform specific utility functions. +/// +//===----------------------------------------------------------------------===// + +#include "scudo_utils.h" + +#include <errno.h> +#include <fcntl.h> +#include <stdarg.h> +#include <unistd.h> + +#include <cstring> + +// TODO(kostyak): remove __sanitizer *Printf uses in favor for our own less +// complicated string formatting code. The following is a +// temporary workaround to be able to use __sanitizer::VSNPrintf. +namespace __sanitizer { + +extern int VSNPrintf(char *buff, int buff_length, const char *format, + va_list args); + +} // namespace __sanitizer + +namespace __scudo { + +FORMAT(1, 2) +void dieWithMessage(const char *Format, ...) { + // Our messages are tiny, 128 characters is more than enough. + char Message[128]; + va_list Args; + va_start(Args, Format); + __sanitizer::VSNPrintf(Message, sizeof(Message), Format, Args); + va_end(Args); + RawWrite(Message); + Die(); +} + +typedef struct { + u32 Eax; + u32 Ebx; + u32 Ecx; + u32 Edx; +} CPUIDInfo; + +static void getCPUID(CPUIDInfo *info, u32 leaf, u32 subleaf) +{ + asm volatile("cpuid" + : "=a" (info->Eax), "=b" (info->Ebx), "=c" (info->Ecx), "=d" (info->Edx) + : "a" (leaf), "c" (subleaf) + ); +} + +// Returns true is the CPU is a "GenuineIntel" or "AuthenticAMD" +static bool isSupportedCPU() +{ + CPUIDInfo Info; + + getCPUID(&Info, 0, 0); + if (memcmp(reinterpret_cast<char *>(&Info.Ebx), "Genu", 4) == 0 && + memcmp(reinterpret_cast<char *>(&Info.Edx), "ineI", 4) == 0 && + memcmp(reinterpret_cast<char *>(&Info.Ecx), "ntel", 4) == 0) { + return true; + } + if (memcmp(reinterpret_cast<char *>(&Info.Ebx), "Auth", 4) == 0 && + memcmp(reinterpret_cast<char *>(&Info.Edx), "enti", 4) == 0 && + memcmp(reinterpret_cast<char *>(&Info.Ecx), "cAMD", 4) == 0) { + return true; + } + return false; +} + +bool testCPUFeature(CPUFeature feature) +{ + static bool InfoInitialized = false; + static CPUIDInfo CPUInfo = {}; + + if (InfoInitialized == false) { + if (isSupportedCPU() == true) + getCPUID(&CPUInfo, 1, 0); + else + UNIMPLEMENTED(); + InfoInitialized = true; + } + switch (feature) { + case SSE4_2: + return ((CPUInfo.Ecx >> 20) & 0x1) != 0; + default: + break; + } + return false; +} + +// readRetry will attempt to read Count bytes from the Fd specified, and if +// interrupted will retry to read additional bytes to reach Count. +static ssize_t readRetry(int Fd, u8 *Buffer, size_t Count) { + ssize_t AmountRead = 0; + while (static_cast<size_t>(AmountRead) < Count) { + ssize_t Result = read(Fd, Buffer + AmountRead, Count - AmountRead); + if (Result > 0) + AmountRead += Result; + else if (!Result) + break; + else if (errno != EINTR) { + AmountRead = -1; + break; + } + } + return AmountRead; +} + +// Default constructor for Xorshift128Plus seeds the state with /dev/urandom +Xorshift128Plus::Xorshift128Plus() { + int Fd = open("/dev/urandom", O_RDONLY); + bool Success = readRetry(Fd, reinterpret_cast<u8 *>(&State_0_), + sizeof(State_0_)) == sizeof(State_0_); + Success &= readRetry(Fd, reinterpret_cast<u8 *>(&State_1_), + sizeof(State_1_)) == sizeof(State_1_); + close(Fd); + if (!Success) { + dieWithMessage("ERROR: failed to read enough data from /dev/urandom.\n"); + } +} + +} // namespace __scudo diff --git a/compiler-rt/lib/scudo/scudo_utils.h b/compiler-rt/lib/scudo/scudo_utils.h new file mode 100644 index 00000000000..07394ff4b91 --- /dev/null +++ b/compiler-rt/lib/scudo/scudo_utils.h @@ -0,0 +1,59 @@ +//===-- scudo_utils.h -------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// Header for scudo_utils.cpp. +/// +//===----------------------------------------------------------------------===// + +#ifndef SCUDO_UTILS_H_ +#define SCUDO_UTILS_H_ + +#include <string.h> + +#include "sanitizer_common/sanitizer_common.h" + +namespace __scudo { + +template <class Dest, class Source> +inline Dest bit_cast(const Source& source) { + static_assert(sizeof(Dest) == sizeof(Source), "Sizes are not equal!"); + Dest dest; + memcpy(&dest, &source, sizeof(dest)); + return dest; +} + +void dieWithMessage(const char *Format, ...); + +enum CPUFeature { + SSE4_2 = 0, + ENUM_CPUFEATURE_MAX +}; +bool testCPUFeature(CPUFeature feature); + +// Tiny PRNG based on https://en.wikipedia.org/wiki/Xorshift#xorshift.2B +// The state (128 bits) will be stored in thread local storage. +struct Xorshift128Plus { + public: + Xorshift128Plus(); + u64 Next() { + u64 x = State_0_; + const u64 y = State_1_; + State_0_ = y; + x ^= x << 23; + State_1_ = x ^ y ^ (x >> 17) ^ (y >> 26); + return State_1_ + y; + } + private: + u64 State_0_; + u64 State_1_; +}; + +} // namespace __scudo + +#endif // SCUDO_UTILS_H_ |