Path: blob/main/contrib/llvm-project/compiler-rt/lib/scudo/standalone/combined.h
35291 views
//===-- combined.h ----------------------------------------------*- C++ -*-===//1//2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3// See https://llvm.org/LICENSE.txt for license information.4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5//6//===----------------------------------------------------------------------===//78#ifndef SCUDO_COMBINED_H_9#define SCUDO_COMBINED_H_1011#include "allocator_config_wrapper.h"12#include "atomic_helpers.h"13#include "chunk.h"14#include "common.h"15#include "flags.h"16#include "flags_parser.h"17#include "local_cache.h"18#include "mem_map.h"19#include "memtag.h"20#include "mutex.h"21#include "options.h"22#include "quarantine.h"23#include "report.h"24#include "secondary.h"25#include "stack_depot.h"26#include "string_utils.h"27#include "tsd.h"2829#include "scudo/interface.h"3031#ifdef GWP_ASAN_HOOKS32#include "gwp_asan/guarded_pool_allocator.h"33#include "gwp_asan/optional/backtrace.h"34#include "gwp_asan/optional/segv_handler.h"35#endif // GWP_ASAN_HOOKS3637extern "C" inline void EmptyCallback() {}3839#ifdef HAVE_ANDROID_UNSAFE_FRAME_POINTER_CHASE40// This function is not part of the NDK so it does not appear in any public41// header files. We only declare/use it when targeting the platform.42extern "C" size_t android_unsafe_frame_pointer_chase(scudo::uptr *buf,43size_t num_entries);44#endif4546namespace scudo {4748template <class Config, void (*PostInitCallback)(void) = EmptyCallback>49class Allocator {50public:51using AllocatorConfig = BaseConfig<Config>;52using PrimaryT =53typename AllocatorConfig::template PrimaryT<PrimaryConfig<Config>>;54using SecondaryT =55typename AllocatorConfig::template SecondaryT<SecondaryConfig<Config>>;56using CacheT = typename PrimaryT::CacheT;57typedef Allocator<Config, PostInitCallback> ThisT;58typedef typename AllocatorConfig::template TSDRegistryT<ThisT> TSDRegistryT;5960void callPostInitCallback() {61pthread_once(&PostInitNonce, PostInitCallback);62}6364struct QuarantineCallback {65explicit QuarantineCallback(ThisT &Instance, CacheT &LocalCache)66: Allocator(Instance), Cache(LocalCache) {}6768// Chunk recycling function, returns a quarantined chunk to the backend,69// first making sure it hasn't been tampered with.70void recycle(void *Ptr) {71Chunk::UnpackedHeader Header;72Chunk::loadHeader(Allocator.Cookie, Ptr, &Header);73if (UNLIKELY(Header.State != Chunk::State::Quarantined))74reportInvalidChunkState(AllocatorAction::Recycling, Ptr);7576Header.State = Chunk::State::Available;77Chunk::storeHeader(Allocator.Cookie, Ptr, &Header);7879if (allocatorSupportsMemoryTagging<AllocatorConfig>())80Ptr = untagPointer(Ptr);81void *BlockBegin = Allocator::getBlockBegin(Ptr, &Header);82Cache.deallocate(Header.ClassId, BlockBegin);83}8485// We take a shortcut when allocating a quarantine batch by working with the86// appropriate class ID instead of using Size. The compiler should optimize87// the class ID computation and work with the associated cache directly.88void *allocate(UNUSED uptr Size) {89const uptr QuarantineClassId = SizeClassMap::getClassIdBySize(90sizeof(QuarantineBatch) + Chunk::getHeaderSize());91void *Ptr = Cache.allocate(QuarantineClassId);92// Quarantine batch allocation failure is fatal.93if (UNLIKELY(!Ptr))94reportOutOfMemory(SizeClassMap::getSizeByClassId(QuarantineClassId));9596Ptr = reinterpret_cast<void *>(reinterpret_cast<uptr>(Ptr) +97Chunk::getHeaderSize());98Chunk::UnpackedHeader Header = {};99Header.ClassId = QuarantineClassId & Chunk::ClassIdMask;100Header.SizeOrUnusedBytes = sizeof(QuarantineBatch);101Header.State = Chunk::State::Allocated;102Chunk::storeHeader(Allocator.Cookie, Ptr, &Header);103104// Reset tag to 0 as this chunk may have been previously used for a tagged105// user allocation.106if (UNLIKELY(useMemoryTagging<AllocatorConfig>(107Allocator.Primary.Options.load())))108storeTags(reinterpret_cast<uptr>(Ptr),109reinterpret_cast<uptr>(Ptr) + sizeof(QuarantineBatch));110111return Ptr;112}113114void deallocate(void *Ptr) {115const uptr QuarantineClassId = SizeClassMap::getClassIdBySize(116sizeof(QuarantineBatch) + Chunk::getHeaderSize());117Chunk::UnpackedHeader Header;118Chunk::loadHeader(Allocator.Cookie, Ptr, &Header);119120if (UNLIKELY(Header.State != Chunk::State::Allocated))121reportInvalidChunkState(AllocatorAction::Deallocating, Ptr);122DCHECK_EQ(Header.ClassId, QuarantineClassId);123DCHECK_EQ(Header.Offset, 0);124DCHECK_EQ(Header.SizeOrUnusedBytes, sizeof(QuarantineBatch));125126Header.State = Chunk::State::Available;127Chunk::storeHeader(Allocator.Cookie, Ptr, &Header);128Cache.deallocate(QuarantineClassId,129reinterpret_cast<void *>(reinterpret_cast<uptr>(Ptr) -130Chunk::getHeaderSize()));131}132133private:134ThisT &Allocator;135CacheT &Cache;136};137138typedef GlobalQuarantine<QuarantineCallback, void> QuarantineT;139typedef typename QuarantineT::CacheT QuarantineCacheT;140141void init() {142performSanityChecks();143144// Check if hardware CRC32 is supported in the binary and by the platform,145// if so, opt for the CRC32 hardware version of the checksum.146if (&computeHardwareCRC32 && hasHardwareCRC32())147HashAlgorithm = Checksum::HardwareCRC32;148149if (UNLIKELY(!getRandom(&Cookie, sizeof(Cookie))))150Cookie = static_cast<u32>(getMonotonicTime() ^151(reinterpret_cast<uptr>(this) >> 4));152153initFlags();154reportUnrecognizedFlags();155156// Store some flags locally.157if (getFlags()->may_return_null)158Primary.Options.set(OptionBit::MayReturnNull);159if (getFlags()->zero_contents)160Primary.Options.setFillContentsMode(ZeroFill);161else if (getFlags()->pattern_fill_contents)162Primary.Options.setFillContentsMode(PatternOrZeroFill);163if (getFlags()->dealloc_type_mismatch)164Primary.Options.set(OptionBit::DeallocTypeMismatch);165if (getFlags()->delete_size_mismatch)166Primary.Options.set(OptionBit::DeleteSizeMismatch);167if (allocatorSupportsMemoryTagging<AllocatorConfig>() &&168systemSupportsMemoryTagging())169Primary.Options.set(OptionBit::UseMemoryTagging);170171QuarantineMaxChunkSize =172static_cast<u32>(getFlags()->quarantine_max_chunk_size);173174Stats.init();175// TODO(chiahungduan): Given that we support setting the default value in176// the PrimaryConfig and CacheConfig, consider to deprecate the use of177// `release_to_os_interval_ms` flag.178const s32 ReleaseToOsIntervalMs = getFlags()->release_to_os_interval_ms;179Primary.init(ReleaseToOsIntervalMs);180Secondary.init(&Stats, ReleaseToOsIntervalMs);181Quarantine.init(182static_cast<uptr>(getFlags()->quarantine_size_kb << 10),183static_cast<uptr>(getFlags()->thread_local_quarantine_size_kb << 10));184}185186void enableRingBuffer() NO_THREAD_SAFETY_ANALYSIS {187AllocationRingBuffer *RB = getRingBuffer();188if (RB)189RB->Depot->enable();190RingBufferInitLock.unlock();191}192193void disableRingBuffer() NO_THREAD_SAFETY_ANALYSIS {194RingBufferInitLock.lock();195AllocationRingBuffer *RB = getRingBuffer();196if (RB)197RB->Depot->disable();198}199200// Initialize the embedded GWP-ASan instance. Requires the main allocator to201// be functional, best called from PostInitCallback.202void initGwpAsan() {203#ifdef GWP_ASAN_HOOKS204gwp_asan::options::Options Opt;205Opt.Enabled = getFlags()->GWP_ASAN_Enabled;206Opt.MaxSimultaneousAllocations =207getFlags()->GWP_ASAN_MaxSimultaneousAllocations;208Opt.SampleRate = getFlags()->GWP_ASAN_SampleRate;209Opt.InstallSignalHandlers = getFlags()->GWP_ASAN_InstallSignalHandlers;210Opt.Recoverable = getFlags()->GWP_ASAN_Recoverable;211// Embedded GWP-ASan is locked through the Scudo atfork handler (via212// Allocator::disable calling GWPASan.disable). Disable GWP-ASan's atfork213// handler.214Opt.InstallForkHandlers = false;215Opt.Backtrace = gwp_asan::backtrace::getBacktraceFunction();216GuardedAlloc.init(Opt);217218if (Opt.InstallSignalHandlers)219gwp_asan::segv_handler::installSignalHandlers(220&GuardedAlloc, Printf,221gwp_asan::backtrace::getPrintBacktraceFunction(),222gwp_asan::backtrace::getSegvBacktraceFunction(),223Opt.Recoverable);224225GuardedAllocSlotSize =226GuardedAlloc.getAllocatorState()->maximumAllocationSize();227Stats.add(StatFree, static_cast<uptr>(Opt.MaxSimultaneousAllocations) *228GuardedAllocSlotSize);229#endif // GWP_ASAN_HOOKS230}231232#ifdef GWP_ASAN_HOOKS233const gwp_asan::AllocationMetadata *getGwpAsanAllocationMetadata() {234return GuardedAlloc.getMetadataRegion();235}236237const gwp_asan::AllocatorState *getGwpAsanAllocatorState() {238return GuardedAlloc.getAllocatorState();239}240#endif // GWP_ASAN_HOOKS241242ALWAYS_INLINE void initThreadMaybe(bool MinimalInit = false) {243TSDRegistry.initThreadMaybe(this, MinimalInit);244}245246void unmapTestOnly() {247unmapRingBuffer();248TSDRegistry.unmapTestOnly(this);249Primary.unmapTestOnly();250Secondary.unmapTestOnly();251#ifdef GWP_ASAN_HOOKS252if (getFlags()->GWP_ASAN_InstallSignalHandlers)253gwp_asan::segv_handler::uninstallSignalHandlers();254GuardedAlloc.uninitTestOnly();255#endif // GWP_ASAN_HOOKS256}257258TSDRegistryT *getTSDRegistry() { return &TSDRegistry; }259QuarantineT *getQuarantine() { return &Quarantine; }260261// The Cache must be provided zero-initialized.262void initCache(CacheT *Cache) { Cache->init(&Stats, &Primary); }263264// Release the resources used by a TSD, which involves:265// - draining the local quarantine cache to the global quarantine;266// - releasing the cached pointers back to the Primary;267// - unlinking the local stats from the global ones (destroying the cache does268// the last two items).269void commitBack(TSD<ThisT> *TSD) {270TSD->assertLocked(/*BypassCheck=*/true);271Quarantine.drain(&TSD->getQuarantineCache(),272QuarantineCallback(*this, TSD->getCache()));273TSD->getCache().destroy(&Stats);274}275276void drainCache(TSD<ThisT> *TSD) {277TSD->assertLocked(/*BypassCheck=*/true);278Quarantine.drainAndRecycle(&TSD->getQuarantineCache(),279QuarantineCallback(*this, TSD->getCache()));280TSD->getCache().drain();281}282void drainCaches() { TSDRegistry.drainCaches(this); }283284ALWAYS_INLINE void *getHeaderTaggedPointer(void *Ptr) {285if (!allocatorSupportsMemoryTagging<AllocatorConfig>())286return Ptr;287auto UntaggedPtr = untagPointer(Ptr);288if (UntaggedPtr != Ptr)289return UntaggedPtr;290// Secondary, or pointer allocated while memory tagging is unsupported or291// disabled. The tag mismatch is okay in the latter case because tags will292// not be checked.293return addHeaderTag(Ptr);294}295296ALWAYS_INLINE uptr addHeaderTag(uptr Ptr) {297if (!allocatorSupportsMemoryTagging<AllocatorConfig>())298return Ptr;299return addFixedTag(Ptr, 2);300}301302ALWAYS_INLINE void *addHeaderTag(void *Ptr) {303return reinterpret_cast<void *>(addHeaderTag(reinterpret_cast<uptr>(Ptr)));304}305306NOINLINE u32 collectStackTrace(UNUSED StackDepot *Depot) {307#ifdef HAVE_ANDROID_UNSAFE_FRAME_POINTER_CHASE308// Discard collectStackTrace() frame and allocator function frame.309constexpr uptr DiscardFrames = 2;310uptr Stack[MaxTraceSize + DiscardFrames];311uptr Size =312android_unsafe_frame_pointer_chase(Stack, MaxTraceSize + DiscardFrames);313Size = Min<uptr>(Size, MaxTraceSize + DiscardFrames);314return Depot->insert(Stack + Min<uptr>(DiscardFrames, Size), Stack + Size);315#else316return 0;317#endif318}319320uptr computeOddEvenMaskForPointerMaybe(const Options &Options, uptr Ptr,321uptr ClassId) {322if (!Options.get(OptionBit::UseOddEvenTags))323return 0;324325// If a chunk's tag is odd, we want the tags of the surrounding blocks to be326// even, and vice versa. Blocks are laid out Size bytes apart, and adding327// Size to Ptr will flip the least significant set bit of Size in Ptr, so328// that bit will have the pattern 010101... for consecutive blocks, which we329// can use to determine which tag mask to use.330return 0x5555U << ((Ptr >> SizeClassMap::getSizeLSBByClassId(ClassId)) & 1);331}332333NOINLINE void *allocate(uptr Size, Chunk::Origin Origin,334uptr Alignment = MinAlignment,335bool ZeroContents = false) NO_THREAD_SAFETY_ANALYSIS {336initThreadMaybe();337338const Options Options = Primary.Options.load();339if (UNLIKELY(Alignment > MaxAlignment)) {340if (Options.get(OptionBit::MayReturnNull))341return nullptr;342reportAlignmentTooBig(Alignment, MaxAlignment);343}344if (Alignment < MinAlignment)345Alignment = MinAlignment;346347#ifdef GWP_ASAN_HOOKS348if (UNLIKELY(GuardedAlloc.shouldSample())) {349if (void *Ptr = GuardedAlloc.allocate(Size, Alignment)) {350Stats.lock();351Stats.add(StatAllocated, GuardedAllocSlotSize);352Stats.sub(StatFree, GuardedAllocSlotSize);353Stats.unlock();354return Ptr;355}356}357#endif // GWP_ASAN_HOOKS358359const FillContentsMode FillContents = ZeroContents ? ZeroFill360: TSDRegistry.getDisableMemInit()361? NoFill362: Options.getFillContentsMode();363364// If the requested size happens to be 0 (more common than you might think),365// allocate MinAlignment bytes on top of the header. Then add the extra366// bytes required to fulfill the alignment requirements: we allocate enough367// to be sure that there will be an address in the block that will satisfy368// the alignment.369const uptr NeededSize =370roundUp(Size, MinAlignment) +371((Alignment > MinAlignment) ? Alignment : Chunk::getHeaderSize());372373// Takes care of extravagantly large sizes as well as integer overflows.374static_assert(MaxAllowedMallocSize < UINTPTR_MAX - MaxAlignment, "");375if (UNLIKELY(Size >= MaxAllowedMallocSize)) {376if (Options.get(OptionBit::MayReturnNull))377return nullptr;378reportAllocationSizeTooBig(Size, NeededSize, MaxAllowedMallocSize);379}380DCHECK_LE(Size, NeededSize);381382void *Block = nullptr;383uptr ClassId = 0;384uptr SecondaryBlockEnd = 0;385if (LIKELY(PrimaryT::canAllocate(NeededSize))) {386ClassId = SizeClassMap::getClassIdBySize(NeededSize);387DCHECK_NE(ClassId, 0U);388typename TSDRegistryT::ScopedTSD TSD(TSDRegistry);389Block = TSD->getCache().allocate(ClassId);390// If the allocation failed, retry in each successively larger class until391// it fits. If it fails to fit in the largest class, fallback to the392// Secondary.393if (UNLIKELY(!Block)) {394while (ClassId < SizeClassMap::LargestClassId && !Block)395Block = TSD->getCache().allocate(++ClassId);396if (!Block)397ClassId = 0;398}399}400if (UNLIKELY(ClassId == 0)) {401Block = Secondary.allocate(Options, Size, Alignment, &SecondaryBlockEnd,402FillContents);403}404405if (UNLIKELY(!Block)) {406if (Options.get(OptionBit::MayReturnNull))407return nullptr;408printStats();409reportOutOfMemory(NeededSize);410}411412const uptr UserPtr = roundUp(413reinterpret_cast<uptr>(Block) + Chunk::getHeaderSize(), Alignment);414const uptr SizeOrUnusedBytes =415ClassId ? Size : SecondaryBlockEnd - (UserPtr + Size);416417if (LIKELY(!useMemoryTagging<AllocatorConfig>(Options))) {418return initChunk(ClassId, Origin, Block, UserPtr, SizeOrUnusedBytes,419FillContents);420}421422return initChunkWithMemoryTagging(ClassId, Origin, Block, UserPtr, Size,423SizeOrUnusedBytes, FillContents);424}425426NOINLINE void deallocate(void *Ptr, Chunk::Origin Origin, uptr DeleteSize = 0,427UNUSED uptr Alignment = MinAlignment) {428if (UNLIKELY(!Ptr))429return;430431// For a deallocation, we only ensure minimal initialization, meaning thread432// local data will be left uninitialized for now (when using ELF TLS). The433// fallback cache will be used instead. This is a workaround for a situation434// where the only heap operation performed in a thread would be a free past435// the TLS destructors, ending up in initialized thread specific data never436// being destroyed properly. Any other heap operation will do a full init.437initThreadMaybe(/*MinimalInit=*/true);438439#ifdef GWP_ASAN_HOOKS440if (UNLIKELY(GuardedAlloc.pointerIsMine(Ptr))) {441GuardedAlloc.deallocate(Ptr);442Stats.lock();443Stats.add(StatFree, GuardedAllocSlotSize);444Stats.sub(StatAllocated, GuardedAllocSlotSize);445Stats.unlock();446return;447}448#endif // GWP_ASAN_HOOKS449450if (UNLIKELY(!isAligned(reinterpret_cast<uptr>(Ptr), MinAlignment)))451reportMisalignedPointer(AllocatorAction::Deallocating, Ptr);452453void *TaggedPtr = Ptr;454Ptr = getHeaderTaggedPointer(Ptr);455456Chunk::UnpackedHeader Header;457Chunk::loadHeader(Cookie, Ptr, &Header);458459if (UNLIKELY(Header.State != Chunk::State::Allocated))460reportInvalidChunkState(AllocatorAction::Deallocating, Ptr);461462const Options Options = Primary.Options.load();463if (Options.get(OptionBit::DeallocTypeMismatch)) {464if (UNLIKELY(Header.OriginOrWasZeroed != Origin)) {465// With the exception of memalign'd chunks, that can be still be free'd.466if (Header.OriginOrWasZeroed != Chunk::Origin::Memalign ||467Origin != Chunk::Origin::Malloc)468reportDeallocTypeMismatch(AllocatorAction::Deallocating, Ptr,469Header.OriginOrWasZeroed, Origin);470}471}472473const uptr Size = getSize(Ptr, &Header);474if (DeleteSize && Options.get(OptionBit::DeleteSizeMismatch)) {475if (UNLIKELY(DeleteSize != Size))476reportDeleteSizeMismatch(Ptr, DeleteSize, Size);477}478479quarantineOrDeallocateChunk(Options, TaggedPtr, &Header, Size);480}481482void *reallocate(void *OldPtr, uptr NewSize, uptr Alignment = MinAlignment) {483initThreadMaybe();484485const Options Options = Primary.Options.load();486if (UNLIKELY(NewSize >= MaxAllowedMallocSize)) {487if (Options.get(OptionBit::MayReturnNull))488return nullptr;489reportAllocationSizeTooBig(NewSize, 0, MaxAllowedMallocSize);490}491492// The following cases are handled by the C wrappers.493DCHECK_NE(OldPtr, nullptr);494DCHECK_NE(NewSize, 0);495496#ifdef GWP_ASAN_HOOKS497if (UNLIKELY(GuardedAlloc.pointerIsMine(OldPtr))) {498uptr OldSize = GuardedAlloc.getSize(OldPtr);499void *NewPtr = allocate(NewSize, Chunk::Origin::Malloc, Alignment);500if (NewPtr)501memcpy(NewPtr, OldPtr, (NewSize < OldSize) ? NewSize : OldSize);502GuardedAlloc.deallocate(OldPtr);503Stats.lock();504Stats.add(StatFree, GuardedAllocSlotSize);505Stats.sub(StatAllocated, GuardedAllocSlotSize);506Stats.unlock();507return NewPtr;508}509#endif // GWP_ASAN_HOOKS510511void *OldTaggedPtr = OldPtr;512OldPtr = getHeaderTaggedPointer(OldPtr);513514if (UNLIKELY(!isAligned(reinterpret_cast<uptr>(OldPtr), MinAlignment)))515reportMisalignedPointer(AllocatorAction::Reallocating, OldPtr);516517Chunk::UnpackedHeader Header;518Chunk::loadHeader(Cookie, OldPtr, &Header);519520if (UNLIKELY(Header.State != Chunk::State::Allocated))521reportInvalidChunkState(AllocatorAction::Reallocating, OldPtr);522523// Pointer has to be allocated with a malloc-type function. Some524// applications think that it is OK to realloc a memalign'ed pointer, which525// will trigger this check. It really isn't.526if (Options.get(OptionBit::DeallocTypeMismatch)) {527if (UNLIKELY(Header.OriginOrWasZeroed != Chunk::Origin::Malloc))528reportDeallocTypeMismatch(AllocatorAction::Reallocating, OldPtr,529Header.OriginOrWasZeroed,530Chunk::Origin::Malloc);531}532533void *BlockBegin = getBlockBegin(OldTaggedPtr, &Header);534uptr BlockEnd;535uptr OldSize;536const uptr ClassId = Header.ClassId;537if (LIKELY(ClassId)) {538BlockEnd = reinterpret_cast<uptr>(BlockBegin) +539SizeClassMap::getSizeByClassId(ClassId);540OldSize = Header.SizeOrUnusedBytes;541} else {542BlockEnd = SecondaryT::getBlockEnd(BlockBegin);543OldSize = BlockEnd - (reinterpret_cast<uptr>(OldTaggedPtr) +544Header.SizeOrUnusedBytes);545}546// If the new chunk still fits in the previously allocated block (with a547// reasonable delta), we just keep the old block, and update the chunk548// header to reflect the size change.549if (reinterpret_cast<uptr>(OldTaggedPtr) + NewSize <= BlockEnd) {550if (NewSize > OldSize || (OldSize - NewSize) < getPageSizeCached()) {551// If we have reduced the size, set the extra bytes to the fill value552// so that we are ready to grow it again in the future.553if (NewSize < OldSize) {554const FillContentsMode FillContents =555TSDRegistry.getDisableMemInit() ? NoFill556: Options.getFillContentsMode();557if (FillContents != NoFill) {558memset(reinterpret_cast<char *>(OldTaggedPtr) + NewSize,559FillContents == ZeroFill ? 0 : PatternFillByte,560OldSize - NewSize);561}562}563564Header.SizeOrUnusedBytes =565(ClassId ? NewSize566: BlockEnd -567(reinterpret_cast<uptr>(OldTaggedPtr) + NewSize)) &568Chunk::SizeOrUnusedBytesMask;569Chunk::storeHeader(Cookie, OldPtr, &Header);570if (UNLIKELY(useMemoryTagging<AllocatorConfig>(Options))) {571if (ClassId) {572resizeTaggedChunk(reinterpret_cast<uptr>(OldTaggedPtr) + OldSize,573reinterpret_cast<uptr>(OldTaggedPtr) + NewSize,574NewSize, untagPointer(BlockEnd));575storePrimaryAllocationStackMaybe(Options, OldPtr);576} else {577storeSecondaryAllocationStackMaybe(Options, OldPtr, NewSize);578}579}580return OldTaggedPtr;581}582}583584// Otherwise we allocate a new one, and deallocate the old one. Some585// allocators will allocate an even larger chunk (by a fixed factor) to586// allow for potential further in-place realloc. The gains of such a trick587// are currently unclear.588void *NewPtr = allocate(NewSize, Chunk::Origin::Malloc, Alignment);589if (LIKELY(NewPtr)) {590memcpy(NewPtr, OldTaggedPtr, Min(NewSize, OldSize));591quarantineOrDeallocateChunk(Options, OldTaggedPtr, &Header, OldSize);592}593return NewPtr;594}595596// TODO(kostyak): disable() is currently best-effort. There are some small597// windows of time when an allocation could still succeed after598// this function finishes. We will revisit that later.599void disable() NO_THREAD_SAFETY_ANALYSIS {600initThreadMaybe();601#ifdef GWP_ASAN_HOOKS602GuardedAlloc.disable();603#endif604TSDRegistry.disable();605Stats.disable();606Quarantine.disable();607Primary.disable();608Secondary.disable();609disableRingBuffer();610}611612void enable() NO_THREAD_SAFETY_ANALYSIS {613initThreadMaybe();614enableRingBuffer();615Secondary.enable();616Primary.enable();617Quarantine.enable();618Stats.enable();619TSDRegistry.enable();620#ifdef GWP_ASAN_HOOKS621GuardedAlloc.enable();622#endif623}624625// The function returns the amount of bytes required to store the statistics,626// which might be larger than the amount of bytes provided. Note that the627// statistics buffer is not necessarily constant between calls to this628// function. This can be called with a null buffer or zero size for buffer629// sizing purposes.630uptr getStats(char *Buffer, uptr Size) {631ScopedString Str;632const uptr Length = getStats(&Str) + 1;633if (Length < Size)634Size = Length;635if (Buffer && Size) {636memcpy(Buffer, Str.data(), Size);637Buffer[Size - 1] = '\0';638}639return Length;640}641642void printStats() {643ScopedString Str;644getStats(&Str);645Str.output();646}647648void printFragmentationInfo() {649ScopedString Str;650Primary.getFragmentationInfo(&Str);651// Secondary allocator dumps the fragmentation data in getStats().652Str.output();653}654655void releaseToOS(ReleaseToOS ReleaseType) {656initThreadMaybe();657if (ReleaseType == ReleaseToOS::ForceAll)658drainCaches();659Primary.releaseToOS(ReleaseType);660Secondary.releaseToOS();661}662663// Iterate over all chunks and call a callback for all busy chunks located664// within the provided memory range. Said callback must not use this allocator665// or a deadlock can ensue. This fits Android's malloc_iterate() needs.666void iterateOverChunks(uptr Base, uptr Size, iterate_callback Callback,667void *Arg) {668initThreadMaybe();669if (archSupportsMemoryTagging())670Base = untagPointer(Base);671const uptr From = Base;672const uptr To = Base + Size;673bool MayHaveTaggedPrimary =674allocatorSupportsMemoryTagging<AllocatorConfig>() &&675systemSupportsMemoryTagging();676auto Lambda = [this, From, To, MayHaveTaggedPrimary, Callback,677Arg](uptr Block) {678if (Block < From || Block >= To)679return;680uptr Chunk;681Chunk::UnpackedHeader Header;682if (MayHaveTaggedPrimary) {683// A chunk header can either have a zero tag (tagged primary) or the684// header tag (secondary, or untagged primary). We don't know which so685// try both.686ScopedDisableMemoryTagChecks x;687if (!getChunkFromBlock(Block, &Chunk, &Header) &&688!getChunkFromBlock(addHeaderTag(Block), &Chunk, &Header))689return;690} else {691if (!getChunkFromBlock(addHeaderTag(Block), &Chunk, &Header))692return;693}694if (Header.State == Chunk::State::Allocated) {695uptr TaggedChunk = Chunk;696if (allocatorSupportsMemoryTagging<AllocatorConfig>())697TaggedChunk = untagPointer(TaggedChunk);698if (useMemoryTagging<AllocatorConfig>(Primary.Options.load()))699TaggedChunk = loadTag(Chunk);700Callback(TaggedChunk, getSize(reinterpret_cast<void *>(Chunk), &Header),701Arg);702}703};704Primary.iterateOverBlocks(Lambda);705Secondary.iterateOverBlocks(Lambda);706#ifdef GWP_ASAN_HOOKS707GuardedAlloc.iterate(reinterpret_cast<void *>(Base), Size, Callback, Arg);708#endif709}710711bool canReturnNull() {712initThreadMaybe();713return Primary.Options.load().get(OptionBit::MayReturnNull);714}715716bool setOption(Option O, sptr Value) {717initThreadMaybe();718if (O == Option::MemtagTuning) {719// Enabling odd/even tags involves a tradeoff between use-after-free720// detection and buffer overflow detection. Odd/even tags make it more721// likely for buffer overflows to be detected by increasing the size of722// the guaranteed "red zone" around the allocation, but on the other hand723// use-after-free is less likely to be detected because the tag space for724// any particular chunk is cut in half. Therefore we use this tuning725// setting to control whether odd/even tags are enabled.726if (Value == M_MEMTAG_TUNING_BUFFER_OVERFLOW)727Primary.Options.set(OptionBit::UseOddEvenTags);728else if (Value == M_MEMTAG_TUNING_UAF)729Primary.Options.clear(OptionBit::UseOddEvenTags);730return true;731} else {732// We leave it to the various sub-components to decide whether or not they733// want to handle the option, but we do not want to short-circuit734// execution if one of the setOption was to return false.735const bool PrimaryResult = Primary.setOption(O, Value);736const bool SecondaryResult = Secondary.setOption(O, Value);737const bool RegistryResult = TSDRegistry.setOption(O, Value);738return PrimaryResult && SecondaryResult && RegistryResult;739}740return false;741}742743// Return the usable size for a given chunk. Technically we lie, as we just744// report the actual size of a chunk. This is done to counteract code actively745// writing past the end of a chunk (like sqlite3) when the usable size allows746// for it, which then forces realloc to copy the usable size of a chunk as747// opposed to its actual size.748uptr getUsableSize(const void *Ptr) {749if (UNLIKELY(!Ptr))750return 0;751752return getAllocSize(Ptr);753}754755uptr getAllocSize(const void *Ptr) {756initThreadMaybe();757758#ifdef GWP_ASAN_HOOKS759if (UNLIKELY(GuardedAlloc.pointerIsMine(Ptr)))760return GuardedAlloc.getSize(Ptr);761#endif // GWP_ASAN_HOOKS762763Ptr = getHeaderTaggedPointer(const_cast<void *>(Ptr));764Chunk::UnpackedHeader Header;765Chunk::loadHeader(Cookie, Ptr, &Header);766767// Getting the alloc size of a chunk only makes sense if it's allocated.768if (UNLIKELY(Header.State != Chunk::State::Allocated))769reportInvalidChunkState(AllocatorAction::Sizing, const_cast<void *>(Ptr));770771return getSize(Ptr, &Header);772}773774void getStats(StatCounters S) {775initThreadMaybe();776Stats.get(S);777}778779// Returns true if the pointer provided was allocated by the current780// allocator instance, which is compliant with tcmalloc's ownership concept.781// A corrupted chunk will not be reported as owned, which is WAI.782bool isOwned(const void *Ptr) {783initThreadMaybe();784#ifdef GWP_ASAN_HOOKS785if (GuardedAlloc.pointerIsMine(Ptr))786return true;787#endif // GWP_ASAN_HOOKS788if (!Ptr || !isAligned(reinterpret_cast<uptr>(Ptr), MinAlignment))789return false;790Ptr = getHeaderTaggedPointer(const_cast<void *>(Ptr));791Chunk::UnpackedHeader Header;792return Chunk::isValid(Cookie, Ptr, &Header) &&793Header.State == Chunk::State::Allocated;794}795796bool useMemoryTaggingTestOnly() const {797return useMemoryTagging<AllocatorConfig>(Primary.Options.load());798}799void disableMemoryTagging() {800// If we haven't been initialized yet, we need to initialize now in order to801// prevent a future call to initThreadMaybe() from enabling memory tagging802// based on feature detection. But don't call initThreadMaybe() because it803// may end up calling the allocator (via pthread_atfork, via the post-init804// callback), which may cause mappings to be created with memory tagging805// enabled.806TSDRegistry.initOnceMaybe(this);807if (allocatorSupportsMemoryTagging<AllocatorConfig>()) {808Secondary.disableMemoryTagging();809Primary.Options.clear(OptionBit::UseMemoryTagging);810}811}812813void setTrackAllocationStacks(bool Track) {814initThreadMaybe();815if (getFlags()->allocation_ring_buffer_size <= 0) {816DCHECK(!Primary.Options.load().get(OptionBit::TrackAllocationStacks));817return;818}819820if (Track) {821initRingBufferMaybe();822Primary.Options.set(OptionBit::TrackAllocationStacks);823} else824Primary.Options.clear(OptionBit::TrackAllocationStacks);825}826827void setFillContents(FillContentsMode FillContents) {828initThreadMaybe();829Primary.Options.setFillContentsMode(FillContents);830}831832void setAddLargeAllocationSlack(bool AddSlack) {833initThreadMaybe();834if (AddSlack)835Primary.Options.set(OptionBit::AddLargeAllocationSlack);836else837Primary.Options.clear(OptionBit::AddLargeAllocationSlack);838}839840const char *getStackDepotAddress() {841initThreadMaybe();842AllocationRingBuffer *RB = getRingBuffer();843return RB ? reinterpret_cast<char *>(RB->Depot) : nullptr;844}845846uptr getStackDepotSize() {847initThreadMaybe();848AllocationRingBuffer *RB = getRingBuffer();849return RB ? RB->StackDepotSize : 0;850}851852const char *getRegionInfoArrayAddress() const {853return Primary.getRegionInfoArrayAddress();854}855856static uptr getRegionInfoArraySize() {857return PrimaryT::getRegionInfoArraySize();858}859860const char *getRingBufferAddress() {861initThreadMaybe();862return reinterpret_cast<char *>(getRingBuffer());863}864865uptr getRingBufferSize() {866initThreadMaybe();867AllocationRingBuffer *RB = getRingBuffer();868return RB && RB->RingBufferElements869? ringBufferSizeInBytes(RB->RingBufferElements)870: 0;871}872873static const uptr MaxTraceSize = 64;874875static void collectTraceMaybe(const StackDepot *Depot,876uintptr_t (&Trace)[MaxTraceSize], u32 Hash) {877uptr RingPos, Size;878if (!Depot->find(Hash, &RingPos, &Size))879return;880for (unsigned I = 0; I != Size && I != MaxTraceSize; ++I)881Trace[I] = static_cast<uintptr_t>(Depot->at(RingPos + I));882}883884static void getErrorInfo(struct scudo_error_info *ErrorInfo,885uintptr_t FaultAddr, const char *DepotPtr,886size_t DepotSize, const char *RegionInfoPtr,887const char *RingBufferPtr, size_t RingBufferSize,888const char *Memory, const char *MemoryTags,889uintptr_t MemoryAddr, size_t MemorySize) {890// N.B. we need to support corrupted data in any of the buffers here. We get891// this information from an external process (the crashing process) that892// should not be able to crash the crash dumper (crash_dump on Android).893// See also the get_error_info_fuzzer.894*ErrorInfo = {};895if (!allocatorSupportsMemoryTagging<AllocatorConfig>() ||896MemoryAddr + MemorySize < MemoryAddr)897return;898899const StackDepot *Depot = nullptr;900if (DepotPtr) {901// check for corrupted StackDepot. First we need to check whether we can902// read the metadata, then whether the metadata matches the size.903if (DepotSize < sizeof(*Depot))904return;905Depot = reinterpret_cast<const StackDepot *>(DepotPtr);906if (!Depot->isValid(DepotSize))907return;908}909910size_t NextErrorReport = 0;911912// Check for OOB in the current block and the two surrounding blocks. Beyond913// that, UAF is more likely.914if (extractTag(FaultAddr) != 0)915getInlineErrorInfo(ErrorInfo, NextErrorReport, FaultAddr, Depot,916RegionInfoPtr, Memory, MemoryTags, MemoryAddr,917MemorySize, 0, 2);918919// Check the ring buffer. For primary allocations this will only find UAF;920// for secondary allocations we can find either UAF or OOB.921getRingBufferErrorInfo(ErrorInfo, NextErrorReport, FaultAddr, Depot,922RingBufferPtr, RingBufferSize);923924// Check for OOB in the 28 blocks surrounding the 3 we checked earlier.925// Beyond that we are likely to hit false positives.926if (extractTag(FaultAddr) != 0)927getInlineErrorInfo(ErrorInfo, NextErrorReport, FaultAddr, Depot,928RegionInfoPtr, Memory, MemoryTags, MemoryAddr,929MemorySize, 2, 16);930}931932private:933typedef typename PrimaryT::SizeClassMap SizeClassMap;934935static const uptr MinAlignmentLog = SCUDO_MIN_ALIGNMENT_LOG;936static const uptr MaxAlignmentLog = 24U; // 16 MB seems reasonable.937static const uptr MinAlignment = 1UL << MinAlignmentLog;938static const uptr MaxAlignment = 1UL << MaxAlignmentLog;939static const uptr MaxAllowedMallocSize =940FIRST_32_SECOND_64(1UL << 31, 1ULL << 40);941942static_assert(MinAlignment >= sizeof(Chunk::PackedHeader),943"Minimal alignment must at least cover a chunk header.");944static_assert(!allocatorSupportsMemoryTagging<AllocatorConfig>() ||945MinAlignment >= archMemoryTagGranuleSize(),946"");947948static const u32 BlockMarker = 0x44554353U;949950// These are indexes into an "array" of 32-bit values that store information951// inline with a chunk that is relevant to diagnosing memory tag faults, where952// 0 corresponds to the address of the user memory. This means that only953// negative indexes may be used. The smallest index that may be used is -2,954// which corresponds to 8 bytes before the user memory, because the chunk955// header size is 8 bytes and in allocators that support memory tagging the956// minimum alignment is at least the tag granule size (16 on aarch64).957static const sptr MemTagAllocationTraceIndex = -2;958static const sptr MemTagAllocationTidIndex = -1;959960u32 Cookie = 0;961u32 QuarantineMaxChunkSize = 0;962963GlobalStats Stats;964PrimaryT Primary;965SecondaryT Secondary;966QuarantineT Quarantine;967TSDRegistryT TSDRegistry;968pthread_once_t PostInitNonce = PTHREAD_ONCE_INIT;969970#ifdef GWP_ASAN_HOOKS971gwp_asan::GuardedPoolAllocator GuardedAlloc;972uptr GuardedAllocSlotSize = 0;973#endif // GWP_ASAN_HOOKS974975struct AllocationRingBuffer {976struct Entry {977atomic_uptr Ptr;978atomic_uptr AllocationSize;979atomic_u32 AllocationTrace;980atomic_u32 AllocationTid;981atomic_u32 DeallocationTrace;982atomic_u32 DeallocationTid;983};984StackDepot *Depot = nullptr;985uptr StackDepotSize = 0;986MemMapT RawRingBufferMap;987MemMapT RawStackDepotMap;988u32 RingBufferElements = 0;989atomic_uptr Pos;990// An array of Size (at least one) elements of type Entry is immediately991// following to this struct.992};993static_assert(sizeof(AllocationRingBuffer) %994alignof(typename AllocationRingBuffer::Entry) ==9950,996"invalid alignment");997998// Lock to initialize the RingBuffer999HybridMutex RingBufferInitLock;10001001// Pointer to memory mapped area starting with AllocationRingBuffer struct,1002// and immediately followed by Size elements of type Entry.1003atomic_uptr RingBufferAddress = {};10041005AllocationRingBuffer *getRingBuffer() {1006return reinterpret_cast<AllocationRingBuffer *>(1007atomic_load(&RingBufferAddress, memory_order_acquire));1008}10091010// The following might get optimized out by the compiler.1011NOINLINE void performSanityChecks() {1012// Verify that the header offset field can hold the maximum offset. In the1013// case of the Secondary allocator, it takes care of alignment and the1014// offset will always be small. In the case of the Primary, the worst case1015// scenario happens in the last size class, when the backend allocation1016// would already be aligned on the requested alignment, which would happen1017// to be the maximum alignment that would fit in that size class. As a1018// result, the maximum offset will be at most the maximum alignment for the1019// last size class minus the header size, in multiples of MinAlignment.1020Chunk::UnpackedHeader Header = {};1021const uptr MaxPrimaryAlignment = 1UL << getMostSignificantSetBitIndex(1022SizeClassMap::MaxSize - MinAlignment);1023const uptr MaxOffset =1024(MaxPrimaryAlignment - Chunk::getHeaderSize()) >> MinAlignmentLog;1025Header.Offset = MaxOffset & Chunk::OffsetMask;1026if (UNLIKELY(Header.Offset != MaxOffset))1027reportSanityCheckError("offset");10281029// Verify that we can fit the maximum size or amount of unused bytes in the1030// header. Given that the Secondary fits the allocation to a page, the worst1031// case scenario happens in the Primary. It will depend on the second to1032// last and last class sizes, as well as the dynamic base for the Primary.1033// The following is an over-approximation that works for our needs.1034const uptr MaxSizeOrUnusedBytes = SizeClassMap::MaxSize - 1;1035Header.SizeOrUnusedBytes = MaxSizeOrUnusedBytes;1036if (UNLIKELY(Header.SizeOrUnusedBytes != MaxSizeOrUnusedBytes))1037reportSanityCheckError("size (or unused bytes)");10381039const uptr LargestClassId = SizeClassMap::LargestClassId;1040Header.ClassId = LargestClassId;1041if (UNLIKELY(Header.ClassId != LargestClassId))1042reportSanityCheckError("class ID");1043}10441045static inline void *getBlockBegin(const void *Ptr,1046Chunk::UnpackedHeader *Header) {1047return reinterpret_cast<void *>(1048reinterpret_cast<uptr>(Ptr) - Chunk::getHeaderSize() -1049(static_cast<uptr>(Header->Offset) << MinAlignmentLog));1050}10511052// Return the size of a chunk as requested during its allocation.1053inline uptr getSize(const void *Ptr, Chunk::UnpackedHeader *Header) {1054const uptr SizeOrUnusedBytes = Header->SizeOrUnusedBytes;1055if (LIKELY(Header->ClassId))1056return SizeOrUnusedBytes;1057if (allocatorSupportsMemoryTagging<AllocatorConfig>())1058Ptr = untagPointer(const_cast<void *>(Ptr));1059return SecondaryT::getBlockEnd(getBlockBegin(Ptr, Header)) -1060reinterpret_cast<uptr>(Ptr) - SizeOrUnusedBytes;1061}10621063ALWAYS_INLINE void *initChunk(const uptr ClassId, const Chunk::Origin Origin,1064void *Block, const uptr UserPtr,1065const uptr SizeOrUnusedBytes,1066const FillContentsMode FillContents) {1067// Compute the default pointer before adding the header tag1068const uptr DefaultAlignedPtr =1069reinterpret_cast<uptr>(Block) + Chunk::getHeaderSize();10701071Block = addHeaderTag(Block);1072// Only do content fill when it's from primary allocator because secondary1073// allocator has filled the content.1074if (ClassId != 0 && UNLIKELY(FillContents != NoFill)) {1075// This condition is not necessarily unlikely, but since memset is1076// costly, we might as well mark it as such.1077memset(Block, FillContents == ZeroFill ? 0 : PatternFillByte,1078PrimaryT::getSizeByClassId(ClassId));1079}10801081Chunk::UnpackedHeader Header = {};10821083if (UNLIKELY(DefaultAlignedPtr != UserPtr)) {1084const uptr Offset = UserPtr - DefaultAlignedPtr;1085DCHECK_GE(Offset, 2 * sizeof(u32));1086// The BlockMarker has no security purpose, but is specifically meant for1087// the chunk iteration function that can be used in debugging situations.1088// It is the only situation where we have to locate the start of a chunk1089// based on its block address.1090reinterpret_cast<u32 *>(Block)[0] = BlockMarker;1091reinterpret_cast<u32 *>(Block)[1] = static_cast<u32>(Offset);1092Header.Offset = (Offset >> MinAlignmentLog) & Chunk::OffsetMask;1093}10941095Header.ClassId = ClassId & Chunk::ClassIdMask;1096Header.State = Chunk::State::Allocated;1097Header.OriginOrWasZeroed = Origin & Chunk::OriginMask;1098Header.SizeOrUnusedBytes = SizeOrUnusedBytes & Chunk::SizeOrUnusedBytesMask;1099Chunk::storeHeader(Cookie, reinterpret_cast<void *>(addHeaderTag(UserPtr)),1100&Header);11011102return reinterpret_cast<void *>(UserPtr);1103}11041105NOINLINE void *1106initChunkWithMemoryTagging(const uptr ClassId, const Chunk::Origin Origin,1107void *Block, const uptr UserPtr, const uptr Size,1108const uptr SizeOrUnusedBytes,1109const FillContentsMode FillContents) {1110const Options Options = Primary.Options.load();1111DCHECK(useMemoryTagging<AllocatorConfig>(Options));11121113// Compute the default pointer before adding the header tag1114const uptr DefaultAlignedPtr =1115reinterpret_cast<uptr>(Block) + Chunk::getHeaderSize();11161117void *Ptr = reinterpret_cast<void *>(UserPtr);1118void *TaggedPtr = Ptr;11191120if (LIKELY(ClassId)) {1121// Init the primary chunk.1122//1123// We only need to zero or tag the contents for Primary backed1124// allocations. We only set tags for primary allocations in order to avoid1125// faulting potentially large numbers of pages for large secondary1126// allocations. We assume that guard pages are enough to protect these1127// allocations.1128//1129// FIXME: When the kernel provides a way to set the background tag of a1130// mapping, we should be able to tag secondary allocations as well.1131//1132// When memory tagging is enabled, zeroing the contents is done as part of1133// setting the tag.11341135Chunk::UnpackedHeader Header;1136const uptr BlockSize = PrimaryT::getSizeByClassId(ClassId);1137const uptr BlockUptr = reinterpret_cast<uptr>(Block);1138const uptr BlockEnd = BlockUptr + BlockSize;1139// If possible, try to reuse the UAF tag that was set by deallocate().1140// For simplicity, only reuse tags if we have the same start address as1141// the previous allocation. This handles the majority of cases since1142// most allocations will not be more aligned than the minimum alignment.1143//1144// We need to handle situations involving reclaimed chunks, and retag1145// the reclaimed portions if necessary. In the case where the chunk is1146// fully reclaimed, the chunk's header will be zero, which will trigger1147// the code path for new mappings and invalid chunks that prepares the1148// chunk from scratch. There are three possibilities for partial1149// reclaiming:1150//1151// (1) Header was reclaimed, data was partially reclaimed.1152// (2) Header was not reclaimed, all data was reclaimed (e.g. because1153// data started on a page boundary).1154// (3) Header was not reclaimed, data was partially reclaimed.1155//1156// Case (1) will be handled in the same way as for full reclaiming,1157// since the header will be zero.1158//1159// We can detect case (2) by loading the tag from the start1160// of the chunk. If it is zero, it means that either all data was1161// reclaimed (since we never use zero as the chunk tag), or that the1162// previous allocation was of size zero. Either way, we need to prepare1163// a new chunk from scratch.1164//1165// We can detect case (3) by moving to the next page (if covered by the1166// chunk) and loading the tag of its first granule. If it is zero, it1167// means that all following pages may need to be retagged. On the other1168// hand, if it is nonzero, we can assume that all following pages are1169// still tagged, according to the logic that if any of the pages1170// following the next page were reclaimed, the next page would have been1171// reclaimed as well.1172uptr TaggedUserPtr;1173uptr PrevUserPtr;1174if (getChunkFromBlock(BlockUptr, &PrevUserPtr, &Header) &&1175PrevUserPtr == UserPtr &&1176(TaggedUserPtr = loadTag(UserPtr)) != UserPtr) {1177uptr PrevEnd = TaggedUserPtr + Header.SizeOrUnusedBytes;1178const uptr NextPage = roundUp(TaggedUserPtr, getPageSizeCached());1179if (NextPage < PrevEnd && loadTag(NextPage) != NextPage)1180PrevEnd = NextPage;1181TaggedPtr = reinterpret_cast<void *>(TaggedUserPtr);1182resizeTaggedChunk(PrevEnd, TaggedUserPtr + Size, Size, BlockEnd);1183if (UNLIKELY(FillContents != NoFill && !Header.OriginOrWasZeroed)) {1184// If an allocation needs to be zeroed (i.e. calloc) we can normally1185// avoid zeroing the memory now since we can rely on memory having1186// been zeroed on free, as this is normally done while setting the1187// UAF tag. But if tagging was disabled per-thread when the memory1188// was freed, it would not have been retagged and thus zeroed, and1189// therefore it needs to be zeroed now.1190memset(TaggedPtr, 0,1191Min(Size, roundUp(PrevEnd - TaggedUserPtr,1192archMemoryTagGranuleSize())));1193} else if (Size) {1194// Clear any stack metadata that may have previously been stored in1195// the chunk data.1196memset(TaggedPtr, 0, archMemoryTagGranuleSize());1197}1198} else {1199const uptr OddEvenMask =1200computeOddEvenMaskForPointerMaybe(Options, BlockUptr, ClassId);1201TaggedPtr = prepareTaggedChunk(Ptr, Size, OddEvenMask, BlockEnd);1202}1203storePrimaryAllocationStackMaybe(Options, Ptr);1204} else {1205// Init the secondary chunk.12061207Block = addHeaderTag(Block);1208Ptr = addHeaderTag(Ptr);1209storeTags(reinterpret_cast<uptr>(Block), reinterpret_cast<uptr>(Ptr));1210storeSecondaryAllocationStackMaybe(Options, Ptr, Size);1211}12121213Chunk::UnpackedHeader Header = {};12141215if (UNLIKELY(DefaultAlignedPtr != UserPtr)) {1216const uptr Offset = UserPtr - DefaultAlignedPtr;1217DCHECK_GE(Offset, 2 * sizeof(u32));1218// The BlockMarker has no security purpose, but is specifically meant for1219// the chunk iteration function that can be used in debugging situations.1220// It is the only situation where we have to locate the start of a chunk1221// based on its block address.1222reinterpret_cast<u32 *>(Block)[0] = BlockMarker;1223reinterpret_cast<u32 *>(Block)[1] = static_cast<u32>(Offset);1224Header.Offset = (Offset >> MinAlignmentLog) & Chunk::OffsetMask;1225}12261227Header.ClassId = ClassId & Chunk::ClassIdMask;1228Header.State = Chunk::State::Allocated;1229Header.OriginOrWasZeroed = Origin & Chunk::OriginMask;1230Header.SizeOrUnusedBytes = SizeOrUnusedBytes & Chunk::SizeOrUnusedBytesMask;1231Chunk::storeHeader(Cookie, Ptr, &Header);12321233return TaggedPtr;1234}12351236void quarantineOrDeallocateChunk(const Options &Options, void *TaggedPtr,1237Chunk::UnpackedHeader *Header,1238uptr Size) NO_THREAD_SAFETY_ANALYSIS {1239void *Ptr = getHeaderTaggedPointer(TaggedPtr);1240// If the quarantine is disabled, the actual size of a chunk is 0 or larger1241// than the maximum allowed, we return a chunk directly to the backend.1242// This purposefully underflows for Size == 0.1243const bool BypassQuarantine = !Quarantine.getCacheSize() ||1244((Size - 1) >= QuarantineMaxChunkSize) ||1245!Header->ClassId;1246if (BypassQuarantine)1247Header->State = Chunk::State::Available;1248else1249Header->State = Chunk::State::Quarantined;12501251void *BlockBegin;1252if (LIKELY(!useMemoryTagging<AllocatorConfig>(Options))) {1253Header->OriginOrWasZeroed = 0U;1254if (BypassQuarantine && allocatorSupportsMemoryTagging<AllocatorConfig>())1255Ptr = untagPointer(Ptr);1256BlockBegin = getBlockBegin(Ptr, Header);1257} else {1258Header->OriginOrWasZeroed =1259Header->ClassId && !TSDRegistry.getDisableMemInit();1260BlockBegin =1261retagBlock(Options, TaggedPtr, Ptr, Header, Size, BypassQuarantine);1262}12631264Chunk::storeHeader(Cookie, Ptr, Header);12651266if (BypassQuarantine) {1267const uptr ClassId = Header->ClassId;1268if (LIKELY(ClassId)) {1269bool CacheDrained;1270{1271typename TSDRegistryT::ScopedTSD TSD(TSDRegistry);1272CacheDrained = TSD->getCache().deallocate(ClassId, BlockBegin);1273}1274// When we have drained some blocks back to the Primary from TSD, that1275// implies that we may have the chance to release some pages as well.1276// Note that in order not to block other thread's accessing the TSD,1277// release the TSD first then try the page release.1278if (CacheDrained)1279Primary.tryReleaseToOS(ClassId, ReleaseToOS::Normal);1280} else {1281Secondary.deallocate(Options, BlockBegin);1282}1283} else {1284typename TSDRegistryT::ScopedTSD TSD(TSDRegistry);1285Quarantine.put(&TSD->getQuarantineCache(),1286QuarantineCallback(*this, TSD->getCache()), Ptr, Size);1287}1288}12891290NOINLINE void *retagBlock(const Options &Options, void *TaggedPtr, void *&Ptr,1291Chunk::UnpackedHeader *Header, const uptr Size,1292bool BypassQuarantine) {1293DCHECK(useMemoryTagging<AllocatorConfig>(Options));12941295const u8 PrevTag = extractTag(reinterpret_cast<uptr>(TaggedPtr));1296storeDeallocationStackMaybe(Options, Ptr, PrevTag, Size);1297if (Header->ClassId && !TSDRegistry.getDisableMemInit()) {1298uptr TaggedBegin, TaggedEnd;1299const uptr OddEvenMask = computeOddEvenMaskForPointerMaybe(1300Options, reinterpret_cast<uptr>(getBlockBegin(Ptr, Header)),1301Header->ClassId);1302// Exclude the previous tag so that immediate use after free is1303// detected 100% of the time.1304setRandomTag(Ptr, Size, OddEvenMask | (1UL << PrevTag), &TaggedBegin,1305&TaggedEnd);1306}13071308Ptr = untagPointer(Ptr);1309void *BlockBegin = getBlockBegin(Ptr, Header);1310if (BypassQuarantine && !Header->ClassId) {1311storeTags(reinterpret_cast<uptr>(BlockBegin),1312reinterpret_cast<uptr>(Ptr));1313}13141315return BlockBegin;1316}13171318bool getChunkFromBlock(uptr Block, uptr *Chunk,1319Chunk::UnpackedHeader *Header) {1320*Chunk =1321Block + getChunkOffsetFromBlock(reinterpret_cast<const char *>(Block));1322return Chunk::isValid(Cookie, reinterpret_cast<void *>(*Chunk), Header);1323}13241325static uptr getChunkOffsetFromBlock(const char *Block) {1326u32 Offset = 0;1327if (reinterpret_cast<const u32 *>(Block)[0] == BlockMarker)1328Offset = reinterpret_cast<const u32 *>(Block)[1];1329return Offset + Chunk::getHeaderSize();1330}13311332// Set the tag of the granule past the end of the allocation to 0, to catch1333// linear overflows even if a previous larger allocation used the same block1334// and tag. Only do this if the granule past the end is in our block, because1335// this would otherwise lead to a SEGV if the allocation covers the entire1336// block and our block is at the end of a mapping. The tag of the next block's1337// header granule will be set to 0, so it will serve the purpose of catching1338// linear overflows in this case.1339//1340// For allocations of size 0 we do not end up storing the address tag to the1341// memory tag space, which getInlineErrorInfo() normally relies on to match1342// address tags against chunks. To allow matching in this case we store the1343// address tag in the first byte of the chunk.1344void storeEndMarker(uptr End, uptr Size, uptr BlockEnd) {1345DCHECK_EQ(BlockEnd, untagPointer(BlockEnd));1346uptr UntaggedEnd = untagPointer(End);1347if (UntaggedEnd != BlockEnd) {1348storeTag(UntaggedEnd);1349if (Size == 0)1350*reinterpret_cast<u8 *>(UntaggedEnd) = extractTag(End);1351}1352}13531354void *prepareTaggedChunk(void *Ptr, uptr Size, uptr ExcludeMask,1355uptr BlockEnd) {1356// Prepare the granule before the chunk to store the chunk header by setting1357// its tag to 0. Normally its tag will already be 0, but in the case where a1358// chunk holding a low alignment allocation is reused for a higher alignment1359// allocation, the chunk may already have a non-zero tag from the previous1360// allocation.1361storeTag(reinterpret_cast<uptr>(Ptr) - archMemoryTagGranuleSize());13621363uptr TaggedBegin, TaggedEnd;1364setRandomTag(Ptr, Size, ExcludeMask, &TaggedBegin, &TaggedEnd);13651366storeEndMarker(TaggedEnd, Size, BlockEnd);1367return reinterpret_cast<void *>(TaggedBegin);1368}13691370void resizeTaggedChunk(uptr OldPtr, uptr NewPtr, uptr NewSize,1371uptr BlockEnd) {1372uptr RoundOldPtr = roundUp(OldPtr, archMemoryTagGranuleSize());1373uptr RoundNewPtr;1374if (RoundOldPtr >= NewPtr) {1375// If the allocation is shrinking we just need to set the tag past the end1376// of the allocation to 0. See explanation in storeEndMarker() above.1377RoundNewPtr = roundUp(NewPtr, archMemoryTagGranuleSize());1378} else {1379// Set the memory tag of the region1380// [RoundOldPtr, roundUp(NewPtr, archMemoryTagGranuleSize()))1381// to the pointer tag stored in OldPtr.1382RoundNewPtr = storeTags(RoundOldPtr, NewPtr);1383}1384storeEndMarker(RoundNewPtr, NewSize, BlockEnd);1385}13861387void storePrimaryAllocationStackMaybe(const Options &Options, void *Ptr) {1388if (!UNLIKELY(Options.get(OptionBit::TrackAllocationStacks)))1389return;1390AllocationRingBuffer *RB = getRingBuffer();1391if (!RB)1392return;1393auto *Ptr32 = reinterpret_cast<u32 *>(Ptr);1394Ptr32[MemTagAllocationTraceIndex] = collectStackTrace(RB->Depot);1395Ptr32[MemTagAllocationTidIndex] = getThreadID();1396}13971398void storeRingBufferEntry(AllocationRingBuffer *RB, void *Ptr,1399u32 AllocationTrace, u32 AllocationTid,1400uptr AllocationSize, u32 DeallocationTrace,1401u32 DeallocationTid) {1402uptr Pos = atomic_fetch_add(&RB->Pos, 1, memory_order_relaxed);1403typename AllocationRingBuffer::Entry *Entry =1404getRingBufferEntry(RB, Pos % RB->RingBufferElements);14051406// First invalidate our entry so that we don't attempt to interpret a1407// partially written state in getSecondaryErrorInfo(). The fences below1408// ensure that the compiler does not move the stores to Ptr in between the1409// stores to the other fields.1410atomic_store_relaxed(&Entry->Ptr, 0);14111412__atomic_signal_fence(__ATOMIC_SEQ_CST);1413atomic_store_relaxed(&Entry->AllocationTrace, AllocationTrace);1414atomic_store_relaxed(&Entry->AllocationTid, AllocationTid);1415atomic_store_relaxed(&Entry->AllocationSize, AllocationSize);1416atomic_store_relaxed(&Entry->DeallocationTrace, DeallocationTrace);1417atomic_store_relaxed(&Entry->DeallocationTid, DeallocationTid);1418__atomic_signal_fence(__ATOMIC_SEQ_CST);14191420atomic_store_relaxed(&Entry->Ptr, reinterpret_cast<uptr>(Ptr));1421}14221423void storeSecondaryAllocationStackMaybe(const Options &Options, void *Ptr,1424uptr Size) {1425if (!UNLIKELY(Options.get(OptionBit::TrackAllocationStacks)))1426return;1427AllocationRingBuffer *RB = getRingBuffer();1428if (!RB)1429return;1430u32 Trace = collectStackTrace(RB->Depot);1431u32 Tid = getThreadID();14321433auto *Ptr32 = reinterpret_cast<u32 *>(Ptr);1434Ptr32[MemTagAllocationTraceIndex] = Trace;1435Ptr32[MemTagAllocationTidIndex] = Tid;14361437storeRingBufferEntry(RB, untagPointer(Ptr), Trace, Tid, Size, 0, 0);1438}14391440void storeDeallocationStackMaybe(const Options &Options, void *Ptr,1441u8 PrevTag, uptr Size) {1442if (!UNLIKELY(Options.get(OptionBit::TrackAllocationStacks)))1443return;1444AllocationRingBuffer *RB = getRingBuffer();1445if (!RB)1446return;1447auto *Ptr32 = reinterpret_cast<u32 *>(Ptr);1448u32 AllocationTrace = Ptr32[MemTagAllocationTraceIndex];1449u32 AllocationTid = Ptr32[MemTagAllocationTidIndex];14501451u32 DeallocationTrace = collectStackTrace(RB->Depot);1452u32 DeallocationTid = getThreadID();14531454storeRingBufferEntry(RB, addFixedTag(untagPointer(Ptr), PrevTag),1455AllocationTrace, AllocationTid, Size,1456DeallocationTrace, DeallocationTid);1457}14581459static const size_t NumErrorReports =1460sizeof(((scudo_error_info *)nullptr)->reports) /1461sizeof(((scudo_error_info *)nullptr)->reports[0]);14621463static void getInlineErrorInfo(struct scudo_error_info *ErrorInfo,1464size_t &NextErrorReport, uintptr_t FaultAddr,1465const StackDepot *Depot,1466const char *RegionInfoPtr, const char *Memory,1467const char *MemoryTags, uintptr_t MemoryAddr,1468size_t MemorySize, size_t MinDistance,1469size_t MaxDistance) {1470uptr UntaggedFaultAddr = untagPointer(FaultAddr);1471u8 FaultAddrTag = extractTag(FaultAddr);1472BlockInfo Info =1473PrimaryT::findNearestBlock(RegionInfoPtr, UntaggedFaultAddr);14741475auto GetGranule = [&](uptr Addr, const char **Data, uint8_t *Tag) -> bool {1476if (Addr < MemoryAddr || Addr + archMemoryTagGranuleSize() < Addr ||1477Addr + archMemoryTagGranuleSize() > MemoryAddr + MemorySize)1478return false;1479*Data = &Memory[Addr - MemoryAddr];1480*Tag = static_cast<u8>(1481MemoryTags[(Addr - MemoryAddr) / archMemoryTagGranuleSize()]);1482return true;1483};14841485auto ReadBlock = [&](uptr Addr, uptr *ChunkAddr,1486Chunk::UnpackedHeader *Header, const u32 **Data,1487u8 *Tag) {1488const char *BlockBegin;1489u8 BlockBeginTag;1490if (!GetGranule(Addr, &BlockBegin, &BlockBeginTag))1491return false;1492uptr ChunkOffset = getChunkOffsetFromBlock(BlockBegin);1493*ChunkAddr = Addr + ChunkOffset;14941495const char *ChunkBegin;1496if (!GetGranule(*ChunkAddr, &ChunkBegin, Tag))1497return false;1498*Header = *reinterpret_cast<const Chunk::UnpackedHeader *>(1499ChunkBegin - Chunk::getHeaderSize());1500*Data = reinterpret_cast<const u32 *>(ChunkBegin);15011502// Allocations of size 0 will have stashed the tag in the first byte of1503// the chunk, see storeEndMarker().1504if (Header->SizeOrUnusedBytes == 0)1505*Tag = static_cast<u8>(*ChunkBegin);15061507return true;1508};15091510if (NextErrorReport == NumErrorReports)1511return;15121513auto CheckOOB = [&](uptr BlockAddr) {1514if (BlockAddr < Info.RegionBegin || BlockAddr >= Info.RegionEnd)1515return false;15161517uptr ChunkAddr;1518Chunk::UnpackedHeader Header;1519const u32 *Data;1520uint8_t Tag;1521if (!ReadBlock(BlockAddr, &ChunkAddr, &Header, &Data, &Tag) ||1522Header.State != Chunk::State::Allocated || Tag != FaultAddrTag)1523return false;15241525auto *R = &ErrorInfo->reports[NextErrorReport++];1526R->error_type =1527UntaggedFaultAddr < ChunkAddr ? BUFFER_UNDERFLOW : BUFFER_OVERFLOW;1528R->allocation_address = ChunkAddr;1529R->allocation_size = Header.SizeOrUnusedBytes;1530if (Depot) {1531collectTraceMaybe(Depot, R->allocation_trace,1532Data[MemTagAllocationTraceIndex]);1533}1534R->allocation_tid = Data[MemTagAllocationTidIndex];1535return NextErrorReport == NumErrorReports;1536};15371538if (MinDistance == 0 && CheckOOB(Info.BlockBegin))1539return;15401541for (size_t I = Max<size_t>(MinDistance, 1); I != MaxDistance; ++I)1542if (CheckOOB(Info.BlockBegin + I * Info.BlockSize) ||1543CheckOOB(Info.BlockBegin - I * Info.BlockSize))1544return;1545}15461547static void getRingBufferErrorInfo(struct scudo_error_info *ErrorInfo,1548size_t &NextErrorReport,1549uintptr_t FaultAddr,1550const StackDepot *Depot,1551const char *RingBufferPtr,1552size_t RingBufferSize) {1553auto *RingBuffer =1554reinterpret_cast<const AllocationRingBuffer *>(RingBufferPtr);1555size_t RingBufferElements = ringBufferElementsFromBytes(RingBufferSize);1556if (!RingBuffer || RingBufferElements == 0 || !Depot)1557return;1558uptr Pos = atomic_load_relaxed(&RingBuffer->Pos);15591560for (uptr I = Pos - 1; I != Pos - 1 - RingBufferElements &&1561NextErrorReport != NumErrorReports;1562--I) {1563auto *Entry = getRingBufferEntry(RingBuffer, I % RingBufferElements);1564uptr EntryPtr = atomic_load_relaxed(&Entry->Ptr);1565if (!EntryPtr)1566continue;15671568uptr UntaggedEntryPtr = untagPointer(EntryPtr);1569uptr EntrySize = atomic_load_relaxed(&Entry->AllocationSize);1570u32 AllocationTrace = atomic_load_relaxed(&Entry->AllocationTrace);1571u32 AllocationTid = atomic_load_relaxed(&Entry->AllocationTid);1572u32 DeallocationTrace = atomic_load_relaxed(&Entry->DeallocationTrace);1573u32 DeallocationTid = atomic_load_relaxed(&Entry->DeallocationTid);15741575if (DeallocationTid) {1576// For UAF we only consider in-bounds fault addresses because1577// out-of-bounds UAF is rare and attempting to detect it is very likely1578// to result in false positives.1579if (FaultAddr < EntryPtr || FaultAddr >= EntryPtr + EntrySize)1580continue;1581} else {1582// Ring buffer OOB is only possible with secondary allocations. In this1583// case we are guaranteed a guard region of at least a page on either1584// side of the allocation (guard page on the right, guard page + tagged1585// region on the left), so ignore any faults outside of that range.1586if (FaultAddr < EntryPtr - getPageSizeCached() ||1587FaultAddr >= EntryPtr + EntrySize + getPageSizeCached())1588continue;15891590// For UAF the ring buffer will contain two entries, one for the1591// allocation and another for the deallocation. Don't report buffer1592// overflow/underflow using the allocation entry if we have already1593// collected a report from the deallocation entry.1594bool Found = false;1595for (uptr J = 0; J != NextErrorReport; ++J) {1596if (ErrorInfo->reports[J].allocation_address == UntaggedEntryPtr) {1597Found = true;1598break;1599}1600}1601if (Found)1602continue;1603}16041605auto *R = &ErrorInfo->reports[NextErrorReport++];1606if (DeallocationTid)1607R->error_type = USE_AFTER_FREE;1608else if (FaultAddr < EntryPtr)1609R->error_type = BUFFER_UNDERFLOW;1610else1611R->error_type = BUFFER_OVERFLOW;16121613R->allocation_address = UntaggedEntryPtr;1614R->allocation_size = EntrySize;1615collectTraceMaybe(Depot, R->allocation_trace, AllocationTrace);1616R->allocation_tid = AllocationTid;1617collectTraceMaybe(Depot, R->deallocation_trace, DeallocationTrace);1618R->deallocation_tid = DeallocationTid;1619}1620}16211622uptr getStats(ScopedString *Str) {1623Primary.getStats(Str);1624Secondary.getStats(Str);1625Quarantine.getStats(Str);1626TSDRegistry.getStats(Str);1627return Str->length();1628}16291630static typename AllocationRingBuffer::Entry *1631getRingBufferEntry(AllocationRingBuffer *RB, uptr N) {1632char *RBEntryStart =1633&reinterpret_cast<char *>(RB)[sizeof(AllocationRingBuffer)];1634return &reinterpret_cast<typename AllocationRingBuffer::Entry *>(1635RBEntryStart)[N];1636}1637static const typename AllocationRingBuffer::Entry *1638getRingBufferEntry(const AllocationRingBuffer *RB, uptr N) {1639const char *RBEntryStart =1640&reinterpret_cast<const char *>(RB)[sizeof(AllocationRingBuffer)];1641return &reinterpret_cast<const typename AllocationRingBuffer::Entry *>(1642RBEntryStart)[N];1643}16441645void initRingBufferMaybe() {1646ScopedLock L(RingBufferInitLock);1647if (getRingBuffer() != nullptr)1648return;16491650int ring_buffer_size = getFlags()->allocation_ring_buffer_size;1651if (ring_buffer_size <= 0)1652return;16531654u32 AllocationRingBufferSize = static_cast<u32>(ring_buffer_size);16551656// We store alloc and free stacks for each entry.1657constexpr u32 kStacksPerRingBufferEntry = 2;1658constexpr u32 kMaxU32Pow2 = ~(UINT32_MAX >> 1);1659static_assert(isPowerOfTwo(kMaxU32Pow2));1660// On Android we always have 3 frames at the bottom: __start_main,1661// __libc_init, main, and 3 at the top: malloc, scudo_malloc and1662// Allocator::allocate. This leaves 10 frames for the user app. The next1663// smallest power of two (8) would only leave 2, which is clearly too1664// little.1665constexpr u32 kFramesPerStack = 16;1666static_assert(isPowerOfTwo(kFramesPerStack));16671668if (AllocationRingBufferSize > kMaxU32Pow2 / kStacksPerRingBufferEntry)1669return;1670u32 TabSize = static_cast<u32>(roundUpPowerOfTwo(kStacksPerRingBufferEntry *1671AllocationRingBufferSize));1672if (TabSize > UINT32_MAX / kFramesPerStack)1673return;1674u32 RingSize = static_cast<u32>(TabSize * kFramesPerStack);16751676uptr StackDepotSize = sizeof(StackDepot) + sizeof(atomic_u64) * RingSize +1677sizeof(atomic_u32) * TabSize;1678MemMapT DepotMap;1679DepotMap.map(1680/*Addr=*/0U, roundUp(StackDepotSize, getPageSizeCached()),1681"scudo:stack_depot");1682auto *Depot = reinterpret_cast<StackDepot *>(DepotMap.getBase());1683Depot->init(RingSize, TabSize);16841685MemMapT MemMap;1686MemMap.map(1687/*Addr=*/0U,1688roundUp(ringBufferSizeInBytes(AllocationRingBufferSize),1689getPageSizeCached()),1690"scudo:ring_buffer");1691auto *RB = reinterpret_cast<AllocationRingBuffer *>(MemMap.getBase());1692RB->RawRingBufferMap = MemMap;1693RB->RingBufferElements = AllocationRingBufferSize;1694RB->Depot = Depot;1695RB->StackDepotSize = StackDepotSize;1696RB->RawStackDepotMap = DepotMap;16971698atomic_store(&RingBufferAddress, reinterpret_cast<uptr>(RB),1699memory_order_release);1700}17011702void unmapRingBuffer() {1703AllocationRingBuffer *RB = getRingBuffer();1704if (RB == nullptr)1705return;1706// N.B. because RawStackDepotMap is part of RawRingBufferMap, the order1707// is very important.1708RB->RawStackDepotMap.unmap(RB->RawStackDepotMap.getBase(),1709RB->RawStackDepotMap.getCapacity());1710// Note that the `RB->RawRingBufferMap` is stored on the pages managed by1711// itself. Take over the ownership before calling unmap() so that any1712// operation along with unmap() won't touch inaccessible pages.1713MemMapT RawRingBufferMap = RB->RawRingBufferMap;1714RawRingBufferMap.unmap(RawRingBufferMap.getBase(),1715RawRingBufferMap.getCapacity());1716atomic_store(&RingBufferAddress, 0, memory_order_release);1717}17181719static constexpr size_t ringBufferSizeInBytes(u32 RingBufferElements) {1720return sizeof(AllocationRingBuffer) +1721RingBufferElements * sizeof(typename AllocationRingBuffer::Entry);1722}17231724static constexpr size_t ringBufferElementsFromBytes(size_t Bytes) {1725if (Bytes < sizeof(AllocationRingBuffer)) {1726return 0;1727}1728return (Bytes - sizeof(AllocationRingBuffer)) /1729sizeof(typename AllocationRingBuffer::Entry);1730}1731};17321733} // namespace scudo17341735#endif // SCUDO_COMBINED_H_173617371738