1 //===-- scudo_allocator.cpp -------------------------------------*- C++ -*-===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 /// 9 /// Scudo Hardened Allocator implementation. 10 /// It uses the sanitizer_common allocator as a base and aims at mitigating 11 /// heap corruption vulnerabilities. It provides a checksum-guarded chunk 12 /// header, a delayed free list, and additional sanity checks. 13 /// 14 //===----------------------------------------------------------------------===// 15 16 #include "scudo_allocator.h" 17 #include "scudo_crc32.h" 18 #include "scudo_errors.h" 19 #include "scudo_flags.h" 20 #include "scudo_interface_internal.h" 21 #include "scudo_tsd.h" 22 #include "scudo_utils.h" 23 24 #include "sanitizer_common/sanitizer_allocator_checks.h" 25 #include "sanitizer_common/sanitizer_allocator_interface.h" 26 #include "sanitizer_common/sanitizer_quarantine.h" 27 28 #include <errno.h> 29 #include <string.h> 30 31 namespace __scudo { 32 33 // Global static cookie, initialized at start-up. 34 static u32 Cookie; 35 36 // We default to software CRC32 if the alternatives are not supported, either 37 // at compilation or at runtime. 38 static atomic_uint8_t HashAlgorithm = { CRC32Software }; 39 40 INLINE u32 computeCRC32(u32 Crc, uptr Value, uptr *Array, uptr ArraySize) { 41 // If the hardware CRC32 feature is defined here, it was enabled everywhere, 42 // as opposed to only for scudo_crc32.cpp. This means that other hardware 43 // specific instructions were likely emitted at other places, and as a 44 // result there is no reason to not use it here. 45 #if defined(__SSE4_2__) || defined(__ARM_FEATURE_CRC32) 46 Crc = CRC32_INTRINSIC(Crc, Value); 47 for (uptr i = 0; i < ArraySize; i++) 48 Crc = CRC32_INTRINSIC(Crc, Array[i]); 49 return Crc; 50 #else 51 if (atomic_load_relaxed(&HashAlgorithm) == CRC32Hardware) { 52 Crc = computeHardwareCRC32(Crc, Value); 53 for (uptr i = 0; i < ArraySize; i++) 54 Crc = computeHardwareCRC32(Crc, Array[i]); 55 return Crc; 56 } 57 Crc = computeSoftwareCRC32(Crc, Value); 58 for (uptr i = 0; i < ArraySize; i++) 59 Crc = computeSoftwareCRC32(Crc, Array[i]); 60 return Crc; 61 #endif // defined(__SSE4_2__) || defined(__ARM_FEATURE_CRC32) 62 } 63 64 static BackendT &getBackend(); 65 66 namespace Chunk { 67 static INLINE AtomicPackedHeader *getAtomicHeader(void *Ptr) { 68 return reinterpret_cast<AtomicPackedHeader *>(reinterpret_cast<uptr>(Ptr) - 69 getHeaderSize()); 70 } 71 static INLINE 72 const AtomicPackedHeader *getConstAtomicHeader(const void *Ptr) { 73 return reinterpret_cast<const AtomicPackedHeader *>( 74 reinterpret_cast<uptr>(Ptr) - getHeaderSize()); 75 } 76 77 static INLINE bool isAligned(const void *Ptr) { 78 return IsAligned(reinterpret_cast<uptr>(Ptr), MinAlignment); 79 } 80 81 // We can't use the offset member of the chunk itself, as we would double 82 // fetch it without any warranty that it wouldn't have been tampered. To 83 // prevent this, we work with a local copy of the header. 84 static INLINE void *getBackendPtr(const void *Ptr, UnpackedHeader *Header) { 85 return reinterpret_cast<void *>(reinterpret_cast<uptr>(Ptr) - 86 getHeaderSize() - (Header->Offset << MinAlignmentLog)); 87 } 88 89 // Returns the usable size for a chunk, meaning the amount of bytes from the 90 // beginning of the user data to the end of the backend allocated chunk. 91 static INLINE uptr getUsableSize(const void *Ptr, UnpackedHeader *Header) { 92 const uptr ClassId = Header->ClassId; 93 if (ClassId) 94 return PrimaryT::ClassIdToSize(ClassId) - getHeaderSize() - 95 (Header->Offset << MinAlignmentLog); 96 return SecondaryT::GetActuallyAllocatedSize( 97 getBackendPtr(Ptr, Header)) - getHeaderSize(); 98 } 99 100 // Returns the size the user requested when allocating the chunk. 101 static INLINE uptr getSize(const void *Ptr, UnpackedHeader *Header) { 102 const uptr SizeOrUnusedBytes = Header->SizeOrUnusedBytes; 103 if (Header->ClassId) 104 return SizeOrUnusedBytes; 105 return SecondaryT::GetActuallyAllocatedSize( 106 getBackendPtr(Ptr, Header)) - getHeaderSize() - SizeOrUnusedBytes; 107 } 108 109 // Compute the checksum of the chunk pointer and its header. 110 static INLINE u16 computeChecksum(const void *Ptr, UnpackedHeader *Header) { 111 UnpackedHeader ZeroChecksumHeader = *Header; 112 ZeroChecksumHeader.Checksum = 0; 113 uptr HeaderHolder[sizeof(UnpackedHeader) / sizeof(uptr)]; 114 memcpy(&HeaderHolder, &ZeroChecksumHeader, sizeof(HeaderHolder)); 115 const u32 Crc = computeCRC32(Cookie, reinterpret_cast<uptr>(Ptr), 116 HeaderHolder, ARRAY_SIZE(HeaderHolder)); 117 return static_cast<u16>(Crc); 118 } 119 120 // Checks the validity of a chunk by verifying its checksum. It doesn't 121 // incur termination in the event of an invalid chunk. 122 static INLINE bool isValid(const void *Ptr) { 123 PackedHeader NewPackedHeader = 124 atomic_load_relaxed(getConstAtomicHeader(Ptr)); 125 UnpackedHeader NewUnpackedHeader = 126 bit_cast<UnpackedHeader>(NewPackedHeader); 127 return (NewUnpackedHeader.Checksum == 128 computeChecksum(Ptr, &NewUnpackedHeader)); 129 } 130 131 // Ensure that ChunkAvailable is 0, so that if a 0 checksum is ever valid 132 // for a fully nulled out header, its state will be available anyway. 133 COMPILER_CHECK(ChunkAvailable == 0); 134 135 // Loads and unpacks the header, verifying the checksum in the process. 136 static INLINE 137 void loadHeader(const void *Ptr, UnpackedHeader *NewUnpackedHeader) { 138 PackedHeader NewPackedHeader = 139 atomic_load_relaxed(getConstAtomicHeader(Ptr)); 140 *NewUnpackedHeader = bit_cast<UnpackedHeader>(NewPackedHeader); 141 if (UNLIKELY(NewUnpackedHeader->Checksum != 142 computeChecksum(Ptr, NewUnpackedHeader))) 143 dieWithMessage("corrupted chunk header at address %p\n", Ptr); 144 } 145 146 // Packs and stores the header, computing the checksum in the process. 147 static INLINE void storeHeader(void *Ptr, UnpackedHeader *NewUnpackedHeader) { 148 NewUnpackedHeader->Checksum = computeChecksum(Ptr, NewUnpackedHeader); 149 PackedHeader NewPackedHeader = bit_cast<PackedHeader>(*NewUnpackedHeader); 150 atomic_store_relaxed(getAtomicHeader(Ptr), NewPackedHeader); 151 } 152 153 // Packs and stores the header, computing the checksum in the process. We 154 // compare the current header with the expected provided one to ensure that 155 // we are not being raced by a corruption occurring in another thread. 156 static INLINE void compareExchangeHeader(void *Ptr, 157 UnpackedHeader *NewUnpackedHeader, 158 UnpackedHeader *OldUnpackedHeader) { 159 NewUnpackedHeader->Checksum = computeChecksum(Ptr, NewUnpackedHeader); 160 PackedHeader NewPackedHeader = bit_cast<PackedHeader>(*NewUnpackedHeader); 161 PackedHeader OldPackedHeader = bit_cast<PackedHeader>(*OldUnpackedHeader); 162 if (UNLIKELY(!atomic_compare_exchange_strong( 163 getAtomicHeader(Ptr), &OldPackedHeader, NewPackedHeader, 164 memory_order_relaxed))) 165 dieWithMessage("race on chunk header at address %p\n", Ptr); 166 } 167 } // namespace Chunk 168 169 struct QuarantineCallback { 170 explicit QuarantineCallback(AllocatorCacheT *Cache) 171 : Cache_(Cache) {} 172 173 // Chunk recycling function, returns a quarantined chunk to the backend, 174 // first making sure it hasn't been tampered with. 175 void Recycle(void *Ptr) { 176 UnpackedHeader Header; 177 Chunk::loadHeader(Ptr, &Header); 178 if (UNLIKELY(Header.State != ChunkQuarantine)) 179 dieWithMessage("invalid chunk state when recycling address %p\n", Ptr); 180 UnpackedHeader NewHeader = Header; 181 NewHeader.State = ChunkAvailable; 182 Chunk::compareExchangeHeader(Ptr, &NewHeader, &Header); 183 void *BackendPtr = Chunk::getBackendPtr(Ptr, &Header); 184 if (Header.ClassId) 185 getBackend().deallocatePrimary(Cache_, BackendPtr, Header.ClassId); 186 else 187 getBackend().deallocateSecondary(BackendPtr); 188 } 189 190 // Internal quarantine allocation and deallocation functions. We first check 191 // that the batches are indeed serviced by the Primary. 192 // TODO(kostyak): figure out the best way to protect the batches. 193 void *Allocate(uptr Size) { 194 const uptr BatchClassId = SizeClassMap::ClassID(sizeof(QuarantineBatch)); 195 return getBackend().allocatePrimary(Cache_, BatchClassId); 196 } 197 198 void Deallocate(void *Ptr) { 199 const uptr BatchClassId = SizeClassMap::ClassID(sizeof(QuarantineBatch)); 200 getBackend().deallocatePrimary(Cache_, Ptr, BatchClassId); 201 } 202 203 AllocatorCacheT *Cache_; 204 COMPILER_CHECK(sizeof(QuarantineBatch) < SizeClassMap::kMaxSize); 205 }; 206 207 typedef Quarantine<QuarantineCallback, void> QuarantineT; 208 typedef QuarantineT::Cache QuarantineCacheT; 209 COMPILER_CHECK(sizeof(QuarantineCacheT) <= 210 sizeof(ScudoTSD::QuarantineCachePlaceHolder)); 211 212 QuarantineCacheT *getQuarantineCache(ScudoTSD *TSD) { 213 return reinterpret_cast<QuarantineCacheT *>(TSD->QuarantineCachePlaceHolder); 214 } 215 216 struct Allocator { 217 static const uptr MaxAllowedMallocSize = 218 FIRST_32_SECOND_64(2UL << 30, 1ULL << 40); 219 220 BackendT Backend; 221 QuarantineT Quarantine; 222 223 u32 QuarantineChunksUpToSize; 224 225 bool DeallocationTypeMismatch; 226 bool ZeroContents; 227 bool DeleteSizeMismatch; 228 229 bool CheckRssLimit; 230 uptr HardRssLimitMb; 231 uptr SoftRssLimitMb; 232 atomic_uint8_t RssLimitExceeded; 233 atomic_uint64_t RssLastCheckedAtNS; 234 235 explicit Allocator(LinkerInitialized) 236 : Quarantine(LINKER_INITIALIZED) {} 237 238 NOINLINE void performSanityChecks(); 239 240 void init() { 241 SanitizerToolName = "Scudo"; 242 PrimaryAllocatorName = "ScudoPrimary"; 243 SecondaryAllocatorName = "ScudoSecondary"; 244 245 initFlags(); 246 247 performSanityChecks(); 248 249 // Check if hardware CRC32 is supported in the binary and by the platform, 250 // if so, opt for the CRC32 hardware version of the checksum. 251 if (&computeHardwareCRC32 && hasHardwareCRC32()) 252 atomic_store_relaxed(&HashAlgorithm, CRC32Hardware); 253 254 SetAllocatorMayReturnNull(common_flags()->allocator_may_return_null); 255 Backend.init(common_flags()->allocator_release_to_os_interval_ms); 256 HardRssLimitMb = common_flags()->hard_rss_limit_mb; 257 SoftRssLimitMb = common_flags()->soft_rss_limit_mb; 258 Quarantine.Init( 259 static_cast<uptr>(getFlags()->QuarantineSizeKb) << 10, 260 static_cast<uptr>(getFlags()->ThreadLocalQuarantineSizeKb) << 10); 261 QuarantineChunksUpToSize = (Quarantine.GetCacheSize() == 0) ? 0 : 262 getFlags()->QuarantineChunksUpToSize; 263 DeallocationTypeMismatch = getFlags()->DeallocationTypeMismatch; 264 DeleteSizeMismatch = getFlags()->DeleteSizeMismatch; 265 ZeroContents = getFlags()->ZeroContents; 266 267 if (UNLIKELY(!GetRandom(reinterpret_cast<void *>(&Cookie), sizeof(Cookie), 268 /*blocking=*/false))) { 269 Cookie = static_cast<u32>((NanoTime() >> 12) ^ 270 (reinterpret_cast<uptr>(this) >> 4)); 271 } 272 273 CheckRssLimit = HardRssLimitMb || SoftRssLimitMb; 274 if (CheckRssLimit) 275 atomic_store_relaxed(&RssLastCheckedAtNS, MonotonicNanoTime()); 276 } 277 278 // Helper function that checks for a valid Scudo chunk. nullptr isn't. 279 bool isValidPointer(const void *Ptr) { 280 initThreadMaybe(); 281 if (UNLIKELY(!Ptr)) 282 return false; 283 if (!Chunk::isAligned(Ptr)) 284 return false; 285 return Chunk::isValid(Ptr); 286 } 287 288 NOINLINE bool isRssLimitExceeded(); 289 290 // Allocates a chunk. 291 void *allocate(uptr Size, uptr Alignment, AllocType Type, 292 bool ForceZeroContents = false) { 293 initThreadMaybe(); 294 if (UNLIKELY(Alignment > MaxAlignment)) { 295 if (AllocatorMayReturnNull()) 296 return nullptr; 297 reportAllocationAlignmentTooBig(Alignment, MaxAlignment); 298 } 299 if (UNLIKELY(Alignment < MinAlignment)) 300 Alignment = MinAlignment; 301 302 const uptr NeededSize = RoundUpTo(Size ? Size : 1, MinAlignment) + 303 Chunk::getHeaderSize(); 304 const uptr AlignedSize = (Alignment > MinAlignment) ? 305 NeededSize + (Alignment - Chunk::getHeaderSize()) : NeededSize; 306 if (UNLIKELY(Size >= MaxAllowedMallocSize) || 307 UNLIKELY(AlignedSize >= MaxAllowedMallocSize)) { 308 if (AllocatorMayReturnNull()) 309 return nullptr; 310 reportAllocationSizeTooBig(Size, AlignedSize, MaxAllowedMallocSize); 311 } 312 313 if (CheckRssLimit && UNLIKELY(isRssLimitExceeded())) { 314 if (AllocatorMayReturnNull()) 315 return nullptr; 316 reportRssLimitExceeded(); 317 } 318 319 // Primary and Secondary backed allocations have a different treatment. We 320 // deal with alignment requirements of Primary serviced allocations here, 321 // but the Secondary will take care of its own alignment needs. 322 void *BackendPtr; 323 uptr BackendSize; 324 u8 ClassId; 325 if (PrimaryT::CanAllocate(AlignedSize, MinAlignment)) { 326 BackendSize = AlignedSize; 327 ClassId = SizeClassMap::ClassID(BackendSize); 328 bool UnlockRequired; 329 ScudoTSD *TSD = getTSDAndLock(&UnlockRequired); 330 BackendPtr = Backend.allocatePrimary(&TSD->Cache, ClassId); 331 if (UnlockRequired) 332 TSD->unlock(); 333 } else { 334 BackendSize = NeededSize; 335 ClassId = 0; 336 BackendPtr = Backend.allocateSecondary(BackendSize, Alignment); 337 } 338 if (UNLIKELY(!BackendPtr)) { 339 SetAllocatorOutOfMemory(); 340 if (AllocatorMayReturnNull()) 341 return nullptr; 342 reportOutOfMemory(Size); 343 } 344 345 // If requested, we will zero out the entire contents of the returned chunk. 346 if ((ForceZeroContents || ZeroContents) && ClassId) 347 memset(BackendPtr, 0, PrimaryT::ClassIdToSize(ClassId)); 348 349 UnpackedHeader Header = {}; 350 uptr UserPtr = reinterpret_cast<uptr>(BackendPtr) + Chunk::getHeaderSize(); 351 if (UNLIKELY(!IsAligned(UserPtr, Alignment))) { 352 // Since the Secondary takes care of alignment, a non-aligned pointer 353 // means it is from the Primary. It is also the only case where the offset 354 // field of the header would be non-zero. 355 DCHECK(ClassId); 356 const uptr AlignedUserPtr = RoundUpTo(UserPtr, Alignment); 357 Header.Offset = (AlignedUserPtr - UserPtr) >> MinAlignmentLog; 358 UserPtr = AlignedUserPtr; 359 } 360 DCHECK_LE(UserPtr + Size, reinterpret_cast<uptr>(BackendPtr) + BackendSize); 361 Header.State = ChunkAllocated; 362 Header.AllocType = Type; 363 if (ClassId) { 364 Header.ClassId = ClassId; 365 Header.SizeOrUnusedBytes = Size; 366 } else { 367 // The secondary fits the allocations to a page, so the amount of unused 368 // bytes is the difference between the end of the user allocation and the 369 // next page boundary. 370 const uptr PageSize = GetPageSizeCached(); 371 const uptr TrailingBytes = (UserPtr + Size) & (PageSize - 1); 372 if (TrailingBytes) 373 Header.SizeOrUnusedBytes = PageSize - TrailingBytes; 374 } 375 void *Ptr = reinterpret_cast<void *>(UserPtr); 376 Chunk::storeHeader(Ptr, &Header); 377 if (SCUDO_CAN_USE_HOOKS && &__sanitizer_malloc_hook) 378 __sanitizer_malloc_hook(Ptr, Size); 379 return Ptr; 380 } 381 382 // Place a chunk in the quarantine or directly deallocate it in the event of 383 // a zero-sized quarantine, or if the size of the chunk is greater than the 384 // quarantine chunk size threshold. 385 void quarantineOrDeallocateChunk(void *Ptr, UnpackedHeader *Header, 386 uptr Size) { 387 const bool BypassQuarantine = !Size || (Size > QuarantineChunksUpToSize); 388 if (BypassQuarantine) { 389 UnpackedHeader NewHeader = *Header; 390 NewHeader.State = ChunkAvailable; 391 Chunk::compareExchangeHeader(Ptr, &NewHeader, Header); 392 void *BackendPtr = Chunk::getBackendPtr(Ptr, Header); 393 if (Header->ClassId) { 394 bool UnlockRequired; 395 ScudoTSD *TSD = getTSDAndLock(&UnlockRequired); 396 getBackend().deallocatePrimary(&TSD->Cache, BackendPtr, 397 Header->ClassId); 398 if (UnlockRequired) 399 TSD->unlock(); 400 } else { 401 getBackend().deallocateSecondary(BackendPtr); 402 } 403 } else { 404 // If a small memory amount was allocated with a larger alignment, we want 405 // to take that into account. Otherwise the Quarantine would be filled 406 // with tiny chunks, taking a lot of VA memory. This is an approximation 407 // of the usable size, that allows us to not call 408 // GetActuallyAllocatedSize. 409 const uptr EstimatedSize = Size + (Header->Offset << MinAlignmentLog); 410 UnpackedHeader NewHeader = *Header; 411 NewHeader.State = ChunkQuarantine; 412 Chunk::compareExchangeHeader(Ptr, &NewHeader, Header); 413 bool UnlockRequired; 414 ScudoTSD *TSD = getTSDAndLock(&UnlockRequired); 415 Quarantine.Put(getQuarantineCache(TSD), QuarantineCallback(&TSD->Cache), 416 Ptr, EstimatedSize); 417 if (UnlockRequired) 418 TSD->unlock(); 419 } 420 } 421 422 // Deallocates a Chunk, which means either adding it to the quarantine or 423 // directly returning it to the backend if criteria are met. 424 void deallocate(void *Ptr, uptr DeleteSize, uptr DeleteAlignment, 425 AllocType Type) { 426 // For a deallocation, we only ensure minimal initialization, meaning thread 427 // local data will be left uninitialized for now (when using ELF TLS). The 428 // fallback cache will be used instead. This is a workaround for a situation 429 // where the only heap operation performed in a thread would be a free past 430 // the TLS destructors, ending up in initialized thread specific data never 431 // being destroyed properly. Any other heap operation will do a full init. 432 initThreadMaybe(/*MinimalInit=*/true); 433 if (SCUDO_CAN_USE_HOOKS && &__sanitizer_free_hook) 434 __sanitizer_free_hook(Ptr); 435 if (UNLIKELY(!Ptr)) 436 return; 437 if (UNLIKELY(!Chunk::isAligned(Ptr))) 438 dieWithMessage("misaligned pointer when deallocating address %p\n", Ptr); 439 UnpackedHeader Header; 440 Chunk::loadHeader(Ptr, &Header); 441 if (UNLIKELY(Header.State != ChunkAllocated)) 442 dieWithMessage("invalid chunk state when deallocating address %p\n", Ptr); 443 if (DeallocationTypeMismatch) { 444 // The deallocation type has to match the allocation one. 445 if (Header.AllocType != Type) { 446 // With the exception of memalign'd Chunks, that can be still be free'd. 447 if (Header.AllocType != FromMemalign || Type != FromMalloc) 448 dieWithMessage("allocation type mismatch when deallocating address " 449 "%p\n", Ptr); 450 } 451 } 452 const uptr Size = Chunk::getSize(Ptr, &Header); 453 if (DeleteSizeMismatch) { 454 if (DeleteSize && DeleteSize != Size) 455 dieWithMessage("invalid sized delete when deallocating address %p\n", 456 Ptr); 457 } 458 (void)DeleteAlignment; // TODO(kostyak): verify that the alignment matches. 459 quarantineOrDeallocateChunk(Ptr, &Header, Size); 460 } 461 462 // Reallocates a chunk. We can save on a new allocation if the new requested 463 // size still fits in the chunk. 464 void *reallocate(void *OldPtr, uptr NewSize) { 465 initThreadMaybe(); 466 if (UNLIKELY(!Chunk::isAligned(OldPtr))) 467 dieWithMessage("misaligned address when reallocating address %p\n", 468 OldPtr); 469 UnpackedHeader OldHeader; 470 Chunk::loadHeader(OldPtr, &OldHeader); 471 if (UNLIKELY(OldHeader.State != ChunkAllocated)) 472 dieWithMessage("invalid chunk state when reallocating address %p\n", 473 OldPtr); 474 if (DeallocationTypeMismatch) { 475 if (UNLIKELY(OldHeader.AllocType != FromMalloc)) 476 dieWithMessage("allocation type mismatch when reallocating address " 477 "%p\n", OldPtr); 478 } 479 const uptr UsableSize = Chunk::getUsableSize(OldPtr, &OldHeader); 480 // The new size still fits in the current chunk, and the size difference 481 // is reasonable. 482 if (NewSize <= UsableSize && 483 (UsableSize - NewSize) < (SizeClassMap::kMaxSize / 2)) { 484 UnpackedHeader NewHeader = OldHeader; 485 NewHeader.SizeOrUnusedBytes = 486 OldHeader.ClassId ? NewSize : UsableSize - NewSize; 487 Chunk::compareExchangeHeader(OldPtr, &NewHeader, &OldHeader); 488 return OldPtr; 489 } 490 // Otherwise, we have to allocate a new chunk and copy the contents of the 491 // old one. 492 void *NewPtr = allocate(NewSize, MinAlignment, FromMalloc); 493 if (NewPtr) { 494 const uptr OldSize = OldHeader.ClassId ? OldHeader.SizeOrUnusedBytes : 495 UsableSize - OldHeader.SizeOrUnusedBytes; 496 memcpy(NewPtr, OldPtr, Min(NewSize, UsableSize)); 497 quarantineOrDeallocateChunk(OldPtr, &OldHeader, OldSize); 498 } 499 return NewPtr; 500 } 501 502 // Helper function that returns the actual usable size of a chunk. 503 uptr getUsableSize(const void *Ptr) { 504 initThreadMaybe(); 505 if (UNLIKELY(!Ptr)) 506 return 0; 507 UnpackedHeader Header; 508 Chunk::loadHeader(Ptr, &Header); 509 // Getting the usable size of a chunk only makes sense if it's allocated. 510 if (UNLIKELY(Header.State != ChunkAllocated)) 511 dieWithMessage("invalid chunk state when sizing address %p\n", Ptr); 512 return Chunk::getUsableSize(Ptr, &Header); 513 } 514 515 void *calloc(uptr NMemB, uptr Size) { 516 initThreadMaybe(); 517 if (UNLIKELY(CheckForCallocOverflow(NMemB, Size))) { 518 if (AllocatorMayReturnNull()) 519 return nullptr; 520 reportCallocOverflow(NMemB, Size); 521 } 522 return allocate(NMemB * Size, MinAlignment, FromMalloc, true); 523 } 524 525 void commitBack(ScudoTSD *TSD) { 526 Quarantine.Drain(getQuarantineCache(TSD), QuarantineCallback(&TSD->Cache)); 527 Backend.destroyCache(&TSD->Cache); 528 } 529 530 uptr getStats(AllocatorStat StatType) { 531 initThreadMaybe(); 532 uptr stats[AllocatorStatCount]; 533 Backend.getStats(stats); 534 return stats[StatType]; 535 } 536 537 bool canReturnNull() { 538 initThreadMaybe(); 539 return AllocatorMayReturnNull(); 540 } 541 542 void setRssLimit(uptr LimitMb, bool HardLimit) { 543 if (HardLimit) 544 HardRssLimitMb = LimitMb; 545 else 546 SoftRssLimitMb = LimitMb; 547 CheckRssLimit = HardRssLimitMb || SoftRssLimitMb; 548 } 549 550 void printStats() { 551 initThreadMaybe(); 552 Backend.printStats(); 553 } 554 }; 555 556 NOINLINE void Allocator::performSanityChecks() { 557 // Verify that the header offset field can hold the maximum offset. In the 558 // case of the Secondary allocator, it takes care of alignment and the 559 // offset will always be 0. In the case of the Primary, the worst case 560 // scenario happens in the last size class, when the backend allocation 561 // would already be aligned on the requested alignment, which would happen 562 // to be the maximum alignment that would fit in that size class. As a 563 // result, the maximum offset will be at most the maximum alignment for the 564 // last size class minus the header size, in multiples of MinAlignment. 565 UnpackedHeader Header = {}; 566 const uptr MaxPrimaryAlignment = 567 1 << MostSignificantSetBitIndex(SizeClassMap::kMaxSize - MinAlignment); 568 const uptr MaxOffset = 569 (MaxPrimaryAlignment - Chunk::getHeaderSize()) >> MinAlignmentLog; 570 Header.Offset = MaxOffset; 571 if (Header.Offset != MaxOffset) 572 dieWithMessage("maximum possible offset doesn't fit in header\n"); 573 // Verify that we can fit the maximum size or amount of unused bytes in the 574 // header. Given that the Secondary fits the allocation to a page, the worst 575 // case scenario happens in the Primary. It will depend on the second to 576 // last and last class sizes, as well as the dynamic base for the Primary. 577 // The following is an over-approximation that works for our needs. 578 const uptr MaxSizeOrUnusedBytes = SizeClassMap::kMaxSize - 1; 579 Header.SizeOrUnusedBytes = MaxSizeOrUnusedBytes; 580 if (Header.SizeOrUnusedBytes != MaxSizeOrUnusedBytes) 581 dieWithMessage("maximum possible unused bytes doesn't fit in header\n"); 582 583 const uptr LargestClassId = SizeClassMap::kLargestClassID; 584 Header.ClassId = LargestClassId; 585 if (Header.ClassId != LargestClassId) 586 dieWithMessage("largest class ID doesn't fit in header\n"); 587 } 588 589 // Opportunistic RSS limit check. This will update the RSS limit status, if 590 // it can, every 250ms, otherwise it will just return the current one. 591 NOINLINE bool Allocator::isRssLimitExceeded() { 592 u64 LastCheck = atomic_load_relaxed(&RssLastCheckedAtNS); 593 const u64 CurrentCheck = MonotonicNanoTime(); 594 if (LIKELY(CurrentCheck < LastCheck + (250ULL * 1000000ULL))) 595 return atomic_load_relaxed(&RssLimitExceeded); 596 if (!atomic_compare_exchange_weak(&RssLastCheckedAtNS, &LastCheck, 597 CurrentCheck, memory_order_relaxed)) 598 return atomic_load_relaxed(&RssLimitExceeded); 599 // TODO(kostyak): We currently use sanitizer_common's GetRSS which reads the 600 // RSS from /proc/self/statm by default. We might want to 601 // call getrusage directly, even if it's less accurate. 602 const uptr CurrentRssMb = GetRSS() >> 20; 603 if (HardRssLimitMb && UNLIKELY(HardRssLimitMb < CurrentRssMb)) 604 dieWithMessage("hard RSS limit exhausted (%zdMb vs %zdMb)\n", 605 HardRssLimitMb, CurrentRssMb); 606 if (SoftRssLimitMb) { 607 if (atomic_load_relaxed(&RssLimitExceeded)) { 608 if (CurrentRssMb <= SoftRssLimitMb) 609 atomic_store_relaxed(&RssLimitExceeded, false); 610 } else { 611 if (CurrentRssMb > SoftRssLimitMb) { 612 atomic_store_relaxed(&RssLimitExceeded, true); 613 Printf("Scudo INFO: soft RSS limit exhausted (%zdMb vs %zdMb)\n", 614 SoftRssLimitMb, CurrentRssMb); 615 } 616 } 617 } 618 return atomic_load_relaxed(&RssLimitExceeded); 619 } 620 621 static Allocator Instance(LINKER_INITIALIZED); 622 623 static BackendT &getBackend() { 624 return Instance.Backend; 625 } 626 627 void initScudo() { 628 Instance.init(); 629 } 630 631 void ScudoTSD::init() { 632 getBackend().initCache(&Cache); 633 memset(QuarantineCachePlaceHolder, 0, sizeof(QuarantineCachePlaceHolder)); 634 } 635 636 void ScudoTSD::commitBack() { 637 Instance.commitBack(this); 638 } 639 640 void *scudoAllocate(uptr Size, uptr Alignment, AllocType Type) { 641 if (Alignment && UNLIKELY(!IsPowerOfTwo(Alignment))) { 642 errno = EINVAL; 643 if (Instance.canReturnNull()) 644 return nullptr; 645 reportAllocationAlignmentNotPowerOfTwo(Alignment); 646 } 647 return SetErrnoOnNull(Instance.allocate(Size, Alignment, Type)); 648 } 649 650 void scudoDeallocate(void *Ptr, uptr Size, uptr Alignment, AllocType Type) { 651 Instance.deallocate(Ptr, Size, Alignment, Type); 652 } 653 654 void *scudoRealloc(void *Ptr, uptr Size) { 655 if (!Ptr) 656 return SetErrnoOnNull(Instance.allocate(Size, MinAlignment, FromMalloc)); 657 if (Size == 0) { 658 Instance.deallocate(Ptr, 0, 0, FromMalloc); 659 return nullptr; 660 } 661 return SetErrnoOnNull(Instance.reallocate(Ptr, Size)); 662 } 663 664 void *scudoCalloc(uptr NMemB, uptr Size) { 665 return SetErrnoOnNull(Instance.calloc(NMemB, Size)); 666 } 667 668 void *scudoValloc(uptr Size) { 669 return SetErrnoOnNull( 670 Instance.allocate(Size, GetPageSizeCached(), FromMemalign)); 671 } 672 673 void *scudoPvalloc(uptr Size) { 674 const uptr PageSize = GetPageSizeCached(); 675 if (UNLIKELY(CheckForPvallocOverflow(Size, PageSize))) { 676 errno = ENOMEM; 677 if (Instance.canReturnNull()) 678 return nullptr; 679 reportPvallocOverflow(Size); 680 } 681 // pvalloc(0) should allocate one page. 682 Size = Size ? RoundUpTo(Size, PageSize) : PageSize; 683 return SetErrnoOnNull(Instance.allocate(Size, PageSize, FromMemalign)); 684 } 685 686 int scudoPosixMemalign(void **MemPtr, uptr Alignment, uptr Size) { 687 if (UNLIKELY(!CheckPosixMemalignAlignment(Alignment))) { 688 if (!Instance.canReturnNull()) 689 reportInvalidPosixMemalignAlignment(Alignment); 690 return EINVAL; 691 } 692 void *Ptr = Instance.allocate(Size, Alignment, FromMemalign); 693 if (UNLIKELY(!Ptr)) 694 return ENOMEM; 695 *MemPtr = Ptr; 696 return 0; 697 } 698 699 void *scudoAlignedAlloc(uptr Alignment, uptr Size) { 700 if (UNLIKELY(!CheckAlignedAllocAlignmentAndSize(Alignment, Size))) { 701 errno = EINVAL; 702 if (Instance.canReturnNull()) 703 return nullptr; 704 reportInvalidAlignedAllocAlignment(Size, Alignment); 705 } 706 return SetErrnoOnNull(Instance.allocate(Size, Alignment, FromMalloc)); 707 } 708 709 uptr scudoMallocUsableSize(void *Ptr) { 710 return Instance.getUsableSize(Ptr); 711 } 712 713 } // namespace __scudo 714 715 using namespace __scudo; 716 717 // MallocExtension helper functions 718 719 uptr __sanitizer_get_current_allocated_bytes() { 720 return Instance.getStats(AllocatorStatAllocated); 721 } 722 723 uptr __sanitizer_get_heap_size() { 724 return Instance.getStats(AllocatorStatMapped); 725 } 726 727 uptr __sanitizer_get_free_bytes() { 728 return 1; 729 } 730 731 uptr __sanitizer_get_unmapped_bytes() { 732 return 1; 733 } 734 735 uptr __sanitizer_get_estimated_allocated_size(uptr Size) { 736 return Size; 737 } 738 739 int __sanitizer_get_ownership(const void *Ptr) { 740 return Instance.isValidPointer(Ptr); 741 } 742 743 uptr __sanitizer_get_allocated_size(const void *Ptr) { 744 return Instance.getUsableSize(Ptr); 745 } 746 747 #if !SANITIZER_SUPPORTS_WEAK_HOOKS 748 SANITIZER_INTERFACE_WEAK_DEF(void, __sanitizer_malloc_hook, 749 void *Ptr, uptr Size) { 750 (void)Ptr; 751 (void)Size; 752 } 753 754 SANITIZER_INTERFACE_WEAK_DEF(void, __sanitizer_free_hook, void *Ptr) { 755 (void)Ptr; 756 } 757 #endif 758 759 // Interface functions 760 761 void __scudo_set_rss_limit(uptr LimitMb, s32 HardLimit) { 762 if (!SCUDO_CAN_USE_PUBLIC_INTERFACE) 763 return; 764 Instance.setRssLimit(LimitMb, !!HardLimit); 765 } 766 767 void __scudo_print_stats() { 768 Instance.printStats(); 769 } 770