1 //===-- scudo_allocator.cpp -------------------------------------*- C++ -*-===// 2 // 3 // The LLVM Compiler Infrastructure 4 // 5 // This file is distributed under the University of Illinois Open Source 6 // License. See LICENSE.TXT for details. 7 // 8 //===----------------------------------------------------------------------===// 9 /// 10 /// Scudo Hardened Allocator implementation. 11 /// It uses the sanitizer_common allocator as a base and aims at mitigating 12 /// heap corruption vulnerabilities. It provides a checksum-guarded chunk 13 /// header, a delayed free list, and additional sanity checks. 14 /// 15 //===----------------------------------------------------------------------===// 16 17 #include "scudo_allocator.h" 18 #include "scudo_utils.h" 19 20 #include "sanitizer_common/sanitizer_allocator_interface.h" 21 #include "sanitizer_common/sanitizer_quarantine.h" 22 23 #include <limits.h> 24 #include <pthread.h> 25 #include <string.h> 26 27 namespace __scudo { 28 29 #if SANITIZER_CAN_USE_ALLOCATOR64 30 const uptr AllocatorSpace = ~0ULL; 31 const uptr AllocatorSize = 0x40000000000ULL; 32 typedef DefaultSizeClassMap SizeClassMap; 33 struct AP { 34 static const uptr kSpaceBeg = AllocatorSpace; 35 static const uptr kSpaceSize = AllocatorSize; 36 static const uptr kMetadataSize = 0; 37 typedef __scudo::SizeClassMap SizeClassMap; 38 typedef NoOpMapUnmapCallback MapUnmapCallback; 39 static const uptr kFlags = 40 SizeClassAllocator64FlagMasks::kRandomShuffleChunks; 41 }; 42 typedef SizeClassAllocator64<AP> PrimaryAllocator; 43 #else 44 // Currently, the 32-bit Sanitizer allocator has not yet benefited from all the 45 // security improvements brought to the 64-bit one. This makes the 32-bit 46 // version of Scudo slightly less toughened. 47 static const uptr RegionSizeLog = 20; 48 static const uptr NumRegions = SANITIZER_MMAP_RANGE_SIZE >> RegionSizeLog; 49 # if SANITIZER_WORDSIZE == 32 50 typedef FlatByteMap<NumRegions> ByteMap; 51 # elif SANITIZER_WORDSIZE == 64 52 typedef TwoLevelByteMap<(NumRegions >> 12), 1 << 12> ByteMap; 53 # endif // SANITIZER_WORDSIZE 54 typedef DefaultSizeClassMap SizeClassMap; 55 typedef SizeClassAllocator32<0, SANITIZER_MMAP_RANGE_SIZE, 0, SizeClassMap, 56 RegionSizeLog, ByteMap> PrimaryAllocator; 57 #endif // SANITIZER_CAN_USE_ALLOCATOR64 58 59 typedef SizeClassAllocatorLocalCache<PrimaryAllocator> AllocatorCache; 60 typedef ScudoLargeMmapAllocator SecondaryAllocator; 61 typedef CombinedAllocator<PrimaryAllocator, AllocatorCache, SecondaryAllocator> 62 ScudoBackendAllocator; 63 64 static ScudoBackendAllocator &getBackendAllocator(); 65 66 static thread_local Xorshift128Plus Prng; 67 // Global static cookie, initialized at start-up. 68 static uptr Cookie; 69 70 // We default to software CRC32 if the alternatives are not supported, either 71 // at compilation or at runtime. 72 static atomic_uint8_t HashAlgorithm = { CRC32Software }; 73 74 SANITIZER_WEAK_ATTRIBUTE u32 computeHardwareCRC32(u32 Crc, uptr Data); 75 76 INLINE u32 computeCRC32(u32 Crc, uptr Data, u8 HashType) { 77 // If SSE4.2 is defined here, it was enabled everywhere, as opposed to only 78 // for scudo_crc32.cpp. This means that other SSE instructions were likely 79 // emitted at other places, and as a result there is no reason to not use 80 // the hardware version of the CRC32. 81 #if defined(__SSE4_2__) || defined(__ARM_FEATURE_CRC32) 82 return computeHardwareCRC32(Crc, Data); 83 #else 84 if (computeHardwareCRC32 && HashType == CRC32Hardware) 85 return computeHardwareCRC32(Crc, Data); 86 else 87 return computeSoftwareCRC32(Crc, Data); 88 #endif // defined(__SSE4_2__) 89 } 90 91 struct ScudoChunk : UnpackedHeader { 92 // We can't use the offset member of the chunk itself, as we would double 93 // fetch it without any warranty that it wouldn't have been tampered. To 94 // prevent this, we work with a local copy of the header. 95 void *getAllocBeg(UnpackedHeader *Header) { 96 return reinterpret_cast<void *>( 97 reinterpret_cast<uptr>(this) - (Header->Offset << MinAlignmentLog)); 98 } 99 100 // Returns the usable size for a chunk, meaning the amount of bytes from the 101 // beginning of the user data to the end of the backend allocated chunk. 102 uptr getUsableSize(UnpackedHeader *Header) { 103 uptr Size = getBackendAllocator().GetActuallyAllocatedSize( 104 getAllocBeg(Header)); 105 if (Size == 0) 106 return 0; 107 return Size - AlignedChunkHeaderSize - (Header->Offset << MinAlignmentLog); 108 } 109 110 // Compute the checksum of the Chunk pointer and its ChunkHeader. 111 u16 computeChecksum(UnpackedHeader *Header) const { 112 UnpackedHeader ZeroChecksumHeader = *Header; 113 ZeroChecksumHeader.Checksum = 0; 114 uptr HeaderHolder[sizeof(UnpackedHeader) / sizeof(uptr)]; 115 memcpy(&HeaderHolder, &ZeroChecksumHeader, sizeof(HeaderHolder)); 116 u8 HashType = atomic_load_relaxed(&HashAlgorithm); 117 u32 Crc = computeCRC32(Cookie, reinterpret_cast<uptr>(this), HashType); 118 for (uptr i = 0; i < ARRAY_SIZE(HeaderHolder); i++) 119 Crc = computeCRC32(Crc, HeaderHolder[i], HashType); 120 return static_cast<u16>(Crc); 121 } 122 123 // Checks the validity of a chunk by verifying its checksum. It doesn't 124 // incur termination in the event of an invalid chunk. 125 bool isValid() { 126 UnpackedHeader NewUnpackedHeader; 127 const AtomicPackedHeader *AtomicHeader = 128 reinterpret_cast<const AtomicPackedHeader *>(this); 129 PackedHeader NewPackedHeader = atomic_load_relaxed(AtomicHeader); 130 NewUnpackedHeader = bit_cast<UnpackedHeader>(NewPackedHeader); 131 return (NewUnpackedHeader.Checksum == computeChecksum(&NewUnpackedHeader)); 132 } 133 134 // Nulls out a chunk header. When returning the chunk to the backend, there 135 // is no need to store a valid ChunkAvailable header, as this would be 136 // computationally expensive. Zeroing out serves the same purpose by making 137 // the header invalid. In the extremely rare event where 0 would be a valid 138 // checksum for the chunk, the state of the chunk is ChunkAvailable anyway. 139 COMPILER_CHECK(ChunkAvailable == 0); 140 void eraseHeader() { 141 PackedHeader NullPackedHeader = 0; 142 AtomicPackedHeader *AtomicHeader = 143 reinterpret_cast<AtomicPackedHeader *>(this); 144 atomic_store_relaxed(AtomicHeader, NullPackedHeader); 145 } 146 147 // Loads and unpacks the header, verifying the checksum in the process. 148 void loadHeader(UnpackedHeader *NewUnpackedHeader) const { 149 const AtomicPackedHeader *AtomicHeader = 150 reinterpret_cast<const AtomicPackedHeader *>(this); 151 PackedHeader NewPackedHeader = atomic_load_relaxed(AtomicHeader); 152 *NewUnpackedHeader = bit_cast<UnpackedHeader>(NewPackedHeader); 153 if (UNLIKELY(NewUnpackedHeader->Checksum != 154 computeChecksum(NewUnpackedHeader))) { 155 dieWithMessage("ERROR: corrupted chunk header at address %p\n", this); 156 } 157 } 158 159 // Packs and stores the header, computing the checksum in the process. 160 void storeHeader(UnpackedHeader *NewUnpackedHeader) { 161 NewUnpackedHeader->Checksum = computeChecksum(NewUnpackedHeader); 162 PackedHeader NewPackedHeader = bit_cast<PackedHeader>(*NewUnpackedHeader); 163 AtomicPackedHeader *AtomicHeader = 164 reinterpret_cast<AtomicPackedHeader *>(this); 165 atomic_store_relaxed(AtomicHeader, NewPackedHeader); 166 } 167 168 // Packs and stores the header, computing the checksum in the process. We 169 // compare the current header with the expected provided one to ensure that 170 // we are not being raced by a corruption occurring in another thread. 171 void compareExchangeHeader(UnpackedHeader *NewUnpackedHeader, 172 UnpackedHeader *OldUnpackedHeader) { 173 NewUnpackedHeader->Checksum = computeChecksum(NewUnpackedHeader); 174 PackedHeader NewPackedHeader = bit_cast<PackedHeader>(*NewUnpackedHeader); 175 PackedHeader OldPackedHeader = bit_cast<PackedHeader>(*OldUnpackedHeader); 176 AtomicPackedHeader *AtomicHeader = 177 reinterpret_cast<AtomicPackedHeader *>(this); 178 if (UNLIKELY(!atomic_compare_exchange_strong(AtomicHeader, 179 &OldPackedHeader, 180 NewPackedHeader, 181 memory_order_relaxed))) { 182 dieWithMessage("ERROR: race on chunk header at address %p\n", this); 183 } 184 } 185 }; 186 187 ScudoChunk *getScudoChunk(uptr UserBeg) { 188 return reinterpret_cast<ScudoChunk *>(UserBeg - AlignedChunkHeaderSize); 189 } 190 191 static bool ScudoInitIsRunning = false; 192 193 static pthread_once_t GlobalInited = PTHREAD_ONCE_INIT; 194 static pthread_key_t PThreadKey; 195 196 static thread_local bool ThreadInited = false; 197 static thread_local bool ThreadTornDown = false; 198 static thread_local AllocatorCache Cache; 199 200 static void teardownThread(void *p) { 201 uptr v = reinterpret_cast<uptr>(p); 202 // The glibc POSIX thread-local-storage deallocation routine calls user 203 // provided destructors in a loop of PTHREAD_DESTRUCTOR_ITERATIONS. 204 // We want to be called last since other destructors might call free and the 205 // like, so we wait until PTHREAD_DESTRUCTOR_ITERATIONS before draining the 206 // quarantine and swallowing the cache. 207 if (v < PTHREAD_DESTRUCTOR_ITERATIONS) { 208 pthread_setspecific(PThreadKey, reinterpret_cast<void *>(v + 1)); 209 return; 210 } 211 drainQuarantine(); 212 getBackendAllocator().DestroyCache(&Cache); 213 ThreadTornDown = true; 214 } 215 216 static void initInternal() { 217 SanitizerToolName = "Scudo"; 218 CHECK(!ScudoInitIsRunning && "Scudo init calls itself!"); 219 ScudoInitIsRunning = true; 220 221 // Check is SSE4.2 is supported, if so, opt for the CRC32 hardware version. 222 if (testCPUFeature(CRC32CPUFeature)) { 223 atomic_store_relaxed(&HashAlgorithm, CRC32Hardware); 224 } 225 226 initFlags(); 227 228 AllocatorOptions Options; 229 Options.setFrom(getFlags(), common_flags()); 230 initAllocator(Options); 231 232 MaybeStartBackgroudThread(); 233 234 ScudoInitIsRunning = false; 235 } 236 237 static void initGlobal() { 238 pthread_key_create(&PThreadKey, teardownThread); 239 initInternal(); 240 } 241 242 static void NOINLINE initThread() { 243 pthread_once(&GlobalInited, initGlobal); 244 pthread_setspecific(PThreadKey, reinterpret_cast<void *>(1)); 245 getBackendAllocator().InitCache(&Cache); 246 ThreadInited = true; 247 } 248 249 struct QuarantineCallback { 250 explicit QuarantineCallback(AllocatorCache *Cache) 251 : Cache_(Cache) {} 252 253 // Chunk recycling function, returns a quarantined chunk to the backend. 254 void Recycle(ScudoChunk *Chunk) { 255 UnpackedHeader Header; 256 Chunk->loadHeader(&Header); 257 if (UNLIKELY(Header.State != ChunkQuarantine)) { 258 dieWithMessage("ERROR: invalid chunk state when recycling address %p\n", 259 Chunk); 260 } 261 Chunk->eraseHeader(); 262 void *Ptr = Chunk->getAllocBeg(&Header); 263 getBackendAllocator().Deallocate(Cache_, Ptr); 264 } 265 266 /// Internal quarantine allocation and deallocation functions. 267 void *Allocate(uptr Size) { 268 // TODO(kostyak): figure out the best way to protect the batches. 269 return getBackendAllocator().Allocate(Cache_, Size, MinAlignment); 270 } 271 272 void Deallocate(void *Ptr) { 273 getBackendAllocator().Deallocate(Cache_, Ptr); 274 } 275 276 AllocatorCache *Cache_; 277 }; 278 279 typedef Quarantine<QuarantineCallback, ScudoChunk> ScudoQuarantine; 280 typedef ScudoQuarantine::Cache ScudoQuarantineCache; 281 static thread_local ScudoQuarantineCache ThreadQuarantineCache; 282 283 void AllocatorOptions::setFrom(const Flags *f, const CommonFlags *cf) { 284 MayReturnNull = cf->allocator_may_return_null; 285 ReleaseToOSIntervalMs = cf->allocator_release_to_os_interval_ms; 286 QuarantineSizeMb = f->QuarantineSizeMb; 287 ThreadLocalQuarantineSizeKb = f->ThreadLocalQuarantineSizeKb; 288 DeallocationTypeMismatch = f->DeallocationTypeMismatch; 289 DeleteSizeMismatch = f->DeleteSizeMismatch; 290 ZeroContents = f->ZeroContents; 291 } 292 293 void AllocatorOptions::copyTo(Flags *f, CommonFlags *cf) const { 294 cf->allocator_may_return_null = MayReturnNull; 295 cf->allocator_release_to_os_interval_ms = ReleaseToOSIntervalMs; 296 f->QuarantineSizeMb = QuarantineSizeMb; 297 f->ThreadLocalQuarantineSizeKb = ThreadLocalQuarantineSizeKb; 298 f->DeallocationTypeMismatch = DeallocationTypeMismatch; 299 f->DeleteSizeMismatch = DeleteSizeMismatch; 300 f->ZeroContents = ZeroContents; 301 } 302 303 struct ScudoAllocator { 304 static const uptr MaxAllowedMallocSize = 305 FIRST_32_SECOND_64(2UL << 30, 1ULL << 40); 306 307 ScudoBackendAllocator BackendAllocator; 308 ScudoQuarantine AllocatorQuarantine; 309 310 // The fallback caches are used when the thread local caches have been 311 // 'detroyed' on thread tear-down. They are protected by a Mutex as they can 312 // be accessed by different threads. 313 StaticSpinMutex FallbackMutex; 314 AllocatorCache FallbackAllocatorCache; 315 ScudoQuarantineCache FallbackQuarantineCache; 316 317 bool DeallocationTypeMismatch; 318 bool ZeroContents; 319 bool DeleteSizeMismatch; 320 321 explicit ScudoAllocator(LinkerInitialized) 322 : AllocatorQuarantine(LINKER_INITIALIZED), 323 FallbackQuarantineCache(LINKER_INITIALIZED) {} 324 325 void init(const AllocatorOptions &Options) { 326 // Verify that the header offset field can hold the maximum offset. In the 327 // case of the Secondary allocator, it takes care of alignment and the 328 // offset will always be 0. In the case of the Primary, the worst case 329 // scenario happens in the last size class, when the backend allocation 330 // would already be aligned on the requested alignment, which would happen 331 // to be the maximum alignment that would fit in that size class. As a 332 // result, the maximum offset will be at most the maximum alignment for the 333 // last size class minus the header size, in multiples of MinAlignment. 334 UnpackedHeader Header = {}; 335 uptr MaxPrimaryAlignment = 1 << MostSignificantSetBitIndex( 336 SizeClassMap::kMaxSize - MinAlignment); 337 uptr MaxOffset = (MaxPrimaryAlignment - AlignedChunkHeaderSize) >> 338 MinAlignmentLog; 339 Header.Offset = MaxOffset; 340 if (Header.Offset != MaxOffset) { 341 dieWithMessage("ERROR: the maximum possible offset doesn't fit in the " 342 "header\n"); 343 } 344 // Verify that we can fit the maximum size or amount of unused bytes in the 345 // header. Given that the Secondary fits the allocation to a page, the worst 346 // case scenario happens in the Primary. It will depend on the second to 347 // last and last class sizes, as well as the dynamic base for the Primary. 348 // The following is an over-approximation that works for our needs. 349 uptr MaxSizeOrUnusedBytes = SizeClassMap::kMaxSize - 1; 350 Header.SizeOrUnusedBytes = MaxSizeOrUnusedBytes; 351 if (Header.SizeOrUnusedBytes != MaxSizeOrUnusedBytes) { 352 dieWithMessage("ERROR: the maximum possible unused bytes doesn't fit in " 353 "the header\n"); 354 } 355 356 DeallocationTypeMismatch = Options.DeallocationTypeMismatch; 357 DeleteSizeMismatch = Options.DeleteSizeMismatch; 358 ZeroContents = Options.ZeroContents; 359 BackendAllocator.Init(Options.MayReturnNull, Options.ReleaseToOSIntervalMs); 360 AllocatorQuarantine.Init( 361 static_cast<uptr>(Options.QuarantineSizeMb) << 20, 362 static_cast<uptr>(Options.ThreadLocalQuarantineSizeKb) << 10); 363 BackendAllocator.InitCache(&FallbackAllocatorCache); 364 Cookie = Prng.getNext(); 365 } 366 367 // Helper function that checks for a valid Scudo chunk. nullptr isn't. 368 bool isValidPointer(const void *UserPtr) { 369 if (UNLIKELY(!ThreadInited)) 370 initThread(); 371 if (!UserPtr) 372 return false; 373 uptr UserBeg = reinterpret_cast<uptr>(UserPtr); 374 if (!IsAligned(UserBeg, MinAlignment)) 375 return false; 376 return getScudoChunk(UserBeg)->isValid(); 377 } 378 379 // Allocates a chunk. 380 void *allocate(uptr Size, uptr Alignment, AllocType Type, 381 bool ForceZeroContents = false) { 382 if (UNLIKELY(!ThreadInited)) 383 initThread(); 384 if (UNLIKELY(!IsPowerOfTwo(Alignment))) { 385 dieWithMessage("ERROR: alignment is not a power of 2\n"); 386 } 387 if (Alignment > MaxAlignment) 388 return BackendAllocator.ReturnNullOrDieOnBadRequest(); 389 if (Alignment < MinAlignment) 390 Alignment = MinAlignment; 391 if (Size >= MaxAllowedMallocSize) 392 return BackendAllocator.ReturnNullOrDieOnBadRequest(); 393 if (Size == 0) 394 Size = 1; 395 396 uptr NeededSize = RoundUpTo(Size, MinAlignment) + AlignedChunkHeaderSize; 397 if (Alignment > MinAlignment) 398 NeededSize += Alignment; 399 if (NeededSize >= MaxAllowedMallocSize) 400 return BackendAllocator.ReturnNullOrDieOnBadRequest(); 401 402 // Primary backed and Secondary backed allocations have a different 403 // treatment. We deal with alignment requirements of Primary serviced 404 // allocations here, but the Secondary will take care of its own alignment 405 // needs, which means we also have to work around some limitations of the 406 // combined allocator to accommodate the situation. 407 bool FromPrimary = PrimaryAllocator::CanAllocate(NeededSize, MinAlignment); 408 409 void *Ptr; 410 uptr AllocationAlignment = FromPrimary ? MinAlignment : Alignment; 411 if (LIKELY(!ThreadTornDown)) { 412 Ptr = BackendAllocator.Allocate(&Cache, NeededSize, AllocationAlignment); 413 } else { 414 SpinMutexLock l(&FallbackMutex); 415 Ptr = BackendAllocator.Allocate(&FallbackAllocatorCache, NeededSize, 416 AllocationAlignment); 417 } 418 if (!Ptr) 419 return BackendAllocator.ReturnNullOrDieOnOOM(); 420 421 uptr AllocBeg = reinterpret_cast<uptr>(Ptr); 422 // If the allocation was serviced by the secondary, the returned pointer 423 // accounts for ChunkHeaderSize to pass the alignment check of the combined 424 // allocator. Adjust it here. 425 if (!FromPrimary) { 426 AllocBeg -= AlignedChunkHeaderSize; 427 if (Alignment > MinAlignment) 428 NeededSize -= Alignment; 429 } 430 431 // If requested, we will zero out the entire contents of the returned chunk. 432 if ((ForceZeroContents || ZeroContents) && FromPrimary) 433 memset(Ptr, 0, BackendAllocator.GetActuallyAllocatedSize(Ptr)); 434 435 uptr UserBeg = AllocBeg + AlignedChunkHeaderSize; 436 if (!IsAligned(UserBeg, Alignment)) 437 UserBeg = RoundUpTo(UserBeg, Alignment); 438 CHECK_LE(UserBeg + Size, AllocBeg + NeededSize); 439 UnpackedHeader Header = {}; 440 Header.State = ChunkAllocated; 441 uptr Offset = UserBeg - AlignedChunkHeaderSize - AllocBeg; 442 Header.Offset = Offset >> MinAlignmentLog; 443 Header.AllocType = Type; 444 if (FromPrimary) { 445 Header.FromPrimary = FromPrimary; 446 Header.SizeOrUnusedBytes = Size; 447 } else { 448 // The secondary fits the allocations to a page, so the amount of unused 449 // bytes is the difference between the end of the user allocation and the 450 // next page boundary. 451 uptr PageSize = GetPageSizeCached(); 452 uptr TrailingBytes = (UserBeg + Size) & (PageSize - 1); 453 if (TrailingBytes) 454 Header.SizeOrUnusedBytes = PageSize - TrailingBytes; 455 } 456 Header.Salt = static_cast<u8>(Prng.getNext()); 457 getScudoChunk(UserBeg)->storeHeader(&Header); 458 void *UserPtr = reinterpret_cast<void *>(UserBeg); 459 // if (&__sanitizer_malloc_hook) __sanitizer_malloc_hook(UserPtr, Size); 460 return UserPtr; 461 } 462 463 // Deallocates a Chunk, which means adding it to the delayed free list (or 464 // Quarantine). 465 void deallocate(void *UserPtr, uptr DeleteSize, AllocType Type) { 466 if (UNLIKELY(!ThreadInited)) 467 initThread(); 468 // if (&__sanitizer_free_hook) __sanitizer_free_hook(UserPtr); 469 if (!UserPtr) 470 return; 471 uptr UserBeg = reinterpret_cast<uptr>(UserPtr); 472 if (UNLIKELY(!IsAligned(UserBeg, MinAlignment))) { 473 dieWithMessage("ERROR: attempted to deallocate a chunk not properly " 474 "aligned at address %p\n", UserPtr); 475 } 476 ScudoChunk *Chunk = getScudoChunk(UserBeg); 477 UnpackedHeader OldHeader; 478 Chunk->loadHeader(&OldHeader); 479 if (UNLIKELY(OldHeader.State != ChunkAllocated)) { 480 dieWithMessage("ERROR: invalid chunk state when deallocating address " 481 "%p\n", UserPtr); 482 } 483 if (DeallocationTypeMismatch) { 484 // The deallocation type has to match the allocation one. 485 if (OldHeader.AllocType != Type) { 486 // With the exception of memalign'd Chunks, that can be still be free'd. 487 if (OldHeader.AllocType != FromMemalign || Type != FromMalloc) { 488 dieWithMessage("ERROR: allocation type mismatch on address %p\n", 489 UserPtr); 490 } 491 } 492 } 493 uptr Size = OldHeader.FromPrimary ? OldHeader.SizeOrUnusedBytes : 494 Chunk->getUsableSize(&OldHeader) - OldHeader.SizeOrUnusedBytes; 495 if (DeleteSizeMismatch) { 496 if (DeleteSize && DeleteSize != Size) { 497 dieWithMessage("ERROR: invalid sized delete on chunk at address %p\n", 498 UserPtr); 499 } 500 } 501 502 UnpackedHeader NewHeader = OldHeader; 503 NewHeader.State = ChunkQuarantine; 504 Chunk->compareExchangeHeader(&NewHeader, &OldHeader); 505 506 // If a small memory amount was allocated with a larger alignment, we want 507 // to take that into account. Otherwise the Quarantine would be filled with 508 // tiny chunks, taking a lot of VA memory. This an approximation of the 509 // usable size, that allows us to not call GetActuallyAllocatedSize. 510 uptr LiableSize = Size + (OldHeader.Offset << MinAlignment); 511 if (LIKELY(!ThreadTornDown)) { 512 AllocatorQuarantine.Put(&ThreadQuarantineCache, 513 QuarantineCallback(&Cache), Chunk, LiableSize); 514 } else { 515 SpinMutexLock l(&FallbackMutex); 516 AllocatorQuarantine.Put(&FallbackQuarantineCache, 517 QuarantineCallback(&FallbackAllocatorCache), 518 Chunk, LiableSize); 519 } 520 } 521 522 // Reallocates a chunk. We can save on a new allocation if the new requested 523 // size still fits in the chunk. 524 void *reallocate(void *OldPtr, uptr NewSize) { 525 if (UNLIKELY(!ThreadInited)) 526 initThread(); 527 uptr UserBeg = reinterpret_cast<uptr>(OldPtr); 528 if (UNLIKELY(!IsAligned(UserBeg, MinAlignment))) { 529 dieWithMessage("ERROR: attempted to reallocate a chunk not properly " 530 "aligned at address %p\n", OldPtr); 531 } 532 ScudoChunk *Chunk = getScudoChunk(UserBeg); 533 UnpackedHeader OldHeader; 534 Chunk->loadHeader(&OldHeader); 535 if (UNLIKELY(OldHeader.State != ChunkAllocated)) { 536 dieWithMessage("ERROR: invalid chunk state when reallocating address " 537 "%p\n", OldPtr); 538 } 539 if (UNLIKELY(OldHeader.AllocType != FromMalloc)) { 540 dieWithMessage("ERROR: invalid chunk type when reallocating address %p\n", 541 OldPtr); 542 } 543 uptr UsableSize = Chunk->getUsableSize(&OldHeader); 544 UnpackedHeader NewHeader = OldHeader; 545 // The new size still fits in the current chunk, and the size difference 546 // is reasonable. 547 if (NewSize <= UsableSize && 548 (UsableSize - NewSize) < (SizeClassMap::kMaxSize / 2)) { 549 NewHeader.SizeOrUnusedBytes = 550 OldHeader.FromPrimary ? NewSize : UsableSize - NewSize; 551 Chunk->compareExchangeHeader(&NewHeader, &OldHeader); 552 return OldPtr; 553 } 554 // Otherwise, we have to allocate a new chunk and copy the contents of the 555 // old one. 556 void *NewPtr = allocate(NewSize, MinAlignment, FromMalloc); 557 if (NewPtr) { 558 uptr OldSize = OldHeader.FromPrimary ? OldHeader.SizeOrUnusedBytes : 559 UsableSize - OldHeader.SizeOrUnusedBytes; 560 memcpy(NewPtr, OldPtr, Min(NewSize, OldSize)); 561 NewHeader.State = ChunkQuarantine; 562 Chunk->compareExchangeHeader(&NewHeader, &OldHeader); 563 if (LIKELY(!ThreadTornDown)) { 564 AllocatorQuarantine.Put(&ThreadQuarantineCache, 565 QuarantineCallback(&Cache), Chunk, UsableSize); 566 } else { 567 SpinMutexLock l(&FallbackMutex); 568 AllocatorQuarantine.Put(&FallbackQuarantineCache, 569 QuarantineCallback(&FallbackAllocatorCache), 570 Chunk, UsableSize); 571 } 572 } 573 return NewPtr; 574 } 575 576 // Helper function that returns the actual usable size of a chunk. 577 uptr getUsableSize(const void *Ptr) { 578 if (UNLIKELY(!ThreadInited)) 579 initThread(); 580 if (!Ptr) 581 return 0; 582 uptr UserBeg = reinterpret_cast<uptr>(Ptr); 583 ScudoChunk *Chunk = getScudoChunk(UserBeg); 584 UnpackedHeader Header; 585 Chunk->loadHeader(&Header); 586 // Getting the usable size of a chunk only makes sense if it's allocated. 587 if (UNLIKELY(Header.State != ChunkAllocated)) { 588 dieWithMessage("ERROR: invalid chunk state when sizing address %p\n", 589 Ptr); 590 } 591 return Chunk->getUsableSize(&Header); 592 } 593 594 void *calloc(uptr NMemB, uptr Size) { 595 if (UNLIKELY(!ThreadInited)) 596 initThread(); 597 uptr Total = NMemB * Size; 598 if (Size != 0 && Total / Size != NMemB) // Overflow check 599 return BackendAllocator.ReturnNullOrDieOnBadRequest(); 600 return allocate(Total, MinAlignment, FromMalloc, true); 601 } 602 603 void drainQuarantine() { 604 AllocatorQuarantine.Drain(&ThreadQuarantineCache, 605 QuarantineCallback(&Cache)); 606 } 607 608 uptr getStats(AllocatorStat StatType) { 609 if (UNLIKELY(!ThreadInited)) 610 initThread(); 611 uptr stats[AllocatorStatCount]; 612 BackendAllocator.GetStats(stats); 613 return stats[StatType]; 614 } 615 }; 616 617 static ScudoAllocator Instance(LINKER_INITIALIZED); 618 619 static ScudoBackendAllocator &getBackendAllocator() { 620 return Instance.BackendAllocator; 621 } 622 623 void initAllocator(const AllocatorOptions &Options) { 624 Instance.init(Options); 625 } 626 627 void drainQuarantine() { 628 Instance.drainQuarantine(); 629 } 630 631 void *scudoMalloc(uptr Size, AllocType Type) { 632 return Instance.allocate(Size, MinAlignment, Type); 633 } 634 635 void scudoFree(void *Ptr, AllocType Type) { 636 Instance.deallocate(Ptr, 0, Type); 637 } 638 639 void scudoSizedFree(void *Ptr, uptr Size, AllocType Type) { 640 Instance.deallocate(Ptr, Size, Type); 641 } 642 643 void *scudoRealloc(void *Ptr, uptr Size) { 644 if (!Ptr) 645 return Instance.allocate(Size, MinAlignment, FromMalloc); 646 if (Size == 0) { 647 Instance.deallocate(Ptr, 0, FromMalloc); 648 return nullptr; 649 } 650 return Instance.reallocate(Ptr, Size); 651 } 652 653 void *scudoCalloc(uptr NMemB, uptr Size) { 654 return Instance.calloc(NMemB, Size); 655 } 656 657 void *scudoValloc(uptr Size) { 658 return Instance.allocate(Size, GetPageSizeCached(), FromMemalign); 659 } 660 661 void *scudoMemalign(uptr Alignment, uptr Size) { 662 return Instance.allocate(Size, Alignment, FromMemalign); 663 } 664 665 void *scudoPvalloc(uptr Size) { 666 uptr PageSize = GetPageSizeCached(); 667 Size = RoundUpTo(Size, PageSize); 668 if (Size == 0) { 669 // pvalloc(0) should allocate one page. 670 Size = PageSize; 671 } 672 return Instance.allocate(Size, PageSize, FromMemalign); 673 } 674 675 int scudoPosixMemalign(void **MemPtr, uptr Alignment, uptr Size) { 676 *MemPtr = Instance.allocate(Size, Alignment, FromMemalign); 677 return 0; 678 } 679 680 void *scudoAlignedAlloc(uptr Alignment, uptr Size) { 681 // size must be a multiple of the alignment. To avoid a division, we first 682 // make sure that alignment is a power of 2. 683 CHECK(IsPowerOfTwo(Alignment)); 684 CHECK_EQ((Size & (Alignment - 1)), 0); 685 return Instance.allocate(Size, Alignment, FromMalloc); 686 } 687 688 uptr scudoMallocUsableSize(void *Ptr) { 689 return Instance.getUsableSize(Ptr); 690 } 691 692 } // namespace __scudo 693 694 using namespace __scudo; 695 696 // MallocExtension helper functions 697 698 uptr __sanitizer_get_current_allocated_bytes() { 699 return Instance.getStats(AllocatorStatAllocated); 700 } 701 702 uptr __sanitizer_get_heap_size() { 703 return Instance.getStats(AllocatorStatMapped); 704 } 705 706 uptr __sanitizer_get_free_bytes() { 707 return 1; 708 } 709 710 uptr __sanitizer_get_unmapped_bytes() { 711 return 1; 712 } 713 714 uptr __sanitizer_get_estimated_allocated_size(uptr size) { 715 return size; 716 } 717 718 int __sanitizer_get_ownership(const void *Ptr) { 719 return Instance.isValidPointer(Ptr); 720 } 721 722 uptr __sanitizer_get_allocated_size(const void *Ptr) { 723 return Instance.getUsableSize(Ptr); 724 } 725