Kostya Serebryany | 712fc98 | 2016-06-07 01:20:26 +0000 | [diff] [blame^] | 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 <smmintrin.h> |
| 26 | |
| 27 | #include <atomic> |
| 28 | #include <cstring> |
| 29 | |
| 30 | namespace __scudo { |
| 31 | |
| 32 | const uptr AllocatorSpace = ~0ULL; |
| 33 | const uptr AllocatorSize = 0x10000000000ULL; |
| 34 | const uptr MinAlignmentLog = 4; // 16 bytes for x64 |
| 35 | const uptr MaxAlignmentLog = 24; |
| 36 | |
| 37 | typedef DefaultSizeClassMap SizeClassMap; |
| 38 | typedef SizeClassAllocator64<AllocatorSpace, AllocatorSize, 0, SizeClassMap> |
| 39 | PrimaryAllocator; |
| 40 | typedef SizeClassAllocatorLocalCache<PrimaryAllocator> AllocatorCache; |
| 41 | typedef LargeMmapAllocator<> SecondaryAllocator; |
| 42 | typedef CombinedAllocator<PrimaryAllocator, AllocatorCache, SecondaryAllocator> |
| 43 | ScudoAllocator; |
| 44 | |
| 45 | static ScudoAllocator &getAllocator(); |
| 46 | |
| 47 | static thread_local Xorshift128Plus Prng; |
| 48 | // Global static cookie, initialized at start-up. |
| 49 | static u64 Cookie; |
| 50 | |
| 51 | enum ChunkState : u8 { |
| 52 | ChunkAvailable = 0, |
| 53 | ChunkAllocated = 1, |
| 54 | ChunkQuarantine = 2 |
| 55 | }; |
| 56 | |
| 57 | typedef unsigned __int128 PackedHeader; |
| 58 | typedef std::atomic<PackedHeader> AtomicPackedHeader; |
| 59 | |
| 60 | // Our header requires 128-bit of storage on x64 (the only platform supported |
| 61 | // as of now), which fits nicely with the alignment requirements. |
| 62 | // Having the offset saves us from using functions such as GetBlockBegin, that |
| 63 | // is fairly costly. Our first implementation used the MetaData as well, which |
| 64 | // offers the advantage of being stored away from the chunk itself, but |
| 65 | // accessing it was costly as well. |
| 66 | // The header will be atomically loaded and stored using the 16-byte primitives |
| 67 | // offered by the platform (likely requires cmpxchg16b support). |
| 68 | struct UnpackedHeader { |
| 69 | // 1st 8 bytes |
| 70 | u16 Checksum : 16; |
| 71 | u64 RequestedSize : 40; // Needed for reallocation purposes. |
| 72 | u8 State : 2; // available, allocated, or quarantined |
| 73 | u8 AllocType : 2; // malloc, new, new[], or memalign |
| 74 | u8 Unused_0_ : 4; |
| 75 | // 2nd 8 bytes |
| 76 | u64 Offset : 20; // Offset from the beginning of the backend |
| 77 | // allocation to the beginning chunk itself, in |
| 78 | // multiples of MinAlignment. See comment about its |
| 79 | // maximum value and test in Initialize. |
| 80 | u64 Unused_1_ : 28; |
| 81 | u16 Salt : 16; |
| 82 | }; |
| 83 | |
| 84 | COMPILER_CHECK(sizeof(UnpackedHeader) == sizeof(PackedHeader)); |
| 85 | |
| 86 | const uptr ChunkHeaderSize = sizeof(PackedHeader); |
| 87 | |
| 88 | struct ScudoChunk : UnpackedHeader { |
| 89 | // We can't use the offset member of the chunk itself, as we would double |
| 90 | // fetch it without any warranty that it wouldn't have been tampered. To |
| 91 | // prevent this, we work with a local copy of the header. |
| 92 | void *AllocBeg(UnpackedHeader *Header) { |
| 93 | return reinterpret_cast<void *>( |
| 94 | reinterpret_cast<uptr>(this) - (Header->Offset << MinAlignmentLog)); |
| 95 | } |
| 96 | |
| 97 | // CRC32 checksum of the Chunk pointer and its ChunkHeader. |
| 98 | // It currently uses the Intel Nehalem SSE4.2 crc32 64-bit instruction. |
| 99 | u16 Checksum(UnpackedHeader *Header) const { |
| 100 | u64 HeaderHolder[2]; |
| 101 | memcpy(HeaderHolder, Header, sizeof(HeaderHolder)); |
| 102 | u64 Crc = _mm_crc32_u64(Cookie, reinterpret_cast<uptr>(this)); |
| 103 | // This is somewhat of a shortcut. The checksum is stored in the 16 least |
| 104 | // significant bits of the first 8 bytes of the header, hence zero-ing |
| 105 | // those bits out. It would be more valid to zero the checksum field of the |
| 106 | // UnpackedHeader, but would require holding an additional copy of it. |
| 107 | Crc = _mm_crc32_u64(Crc, HeaderHolder[0] & 0xffffffffffff0000ULL); |
| 108 | Crc = _mm_crc32_u64(Crc, HeaderHolder[1]); |
| 109 | return static_cast<u16>(Crc); |
| 110 | } |
| 111 | |
| 112 | // Loads and unpacks the header, verifying the checksum in the process. |
| 113 | void loadHeader(UnpackedHeader *NewUnpackedHeader) const { |
| 114 | const AtomicPackedHeader *AtomicHeader = |
| 115 | reinterpret_cast<const AtomicPackedHeader *>(this); |
| 116 | PackedHeader NewPackedHeader = |
| 117 | AtomicHeader->load(std::memory_order_relaxed); |
| 118 | *NewUnpackedHeader = bit_cast<UnpackedHeader>(NewPackedHeader); |
| 119 | if ((NewUnpackedHeader->Unused_0_ != 0) || |
| 120 | (NewUnpackedHeader->Unused_1_ != 0) || |
| 121 | (NewUnpackedHeader->Checksum != Checksum(NewUnpackedHeader))) { |
| 122 | dieWithMessage("ERROR: corrupted chunk header at address %p\n", this); |
| 123 | } |
| 124 | } |
| 125 | |
| 126 | // Packs and stores the header, computing the checksum in the process. |
| 127 | void storeHeader(UnpackedHeader *NewUnpackedHeader) { |
| 128 | NewUnpackedHeader->Checksum = Checksum(NewUnpackedHeader); |
| 129 | PackedHeader NewPackedHeader = bit_cast<PackedHeader>(*NewUnpackedHeader); |
| 130 | AtomicPackedHeader *AtomicHeader = |
| 131 | reinterpret_cast<AtomicPackedHeader *>(this); |
| 132 | AtomicHeader->store(NewPackedHeader, std::memory_order_relaxed); |
| 133 | } |
| 134 | |
| 135 | // Packs and stores the header, computing the checksum in the process. We |
| 136 | // compare the current header with the expected provided one to ensure that |
| 137 | // we are not being raced by a corruption occurring in another thread. |
| 138 | void compareExchangeHeader(UnpackedHeader *NewUnpackedHeader, |
| 139 | UnpackedHeader *OldUnpackedHeader) { |
| 140 | NewUnpackedHeader->Checksum = Checksum(NewUnpackedHeader); |
| 141 | PackedHeader NewPackedHeader = bit_cast<PackedHeader>(*NewUnpackedHeader); |
| 142 | PackedHeader OldPackedHeader = bit_cast<PackedHeader>(*OldUnpackedHeader); |
| 143 | AtomicPackedHeader *AtomicHeader = |
| 144 | reinterpret_cast<AtomicPackedHeader *>(this); |
| 145 | if (!AtomicHeader->compare_exchange_strong(OldPackedHeader, |
| 146 | NewPackedHeader, |
| 147 | std::memory_order_relaxed, |
| 148 | std::memory_order_relaxed)) { |
| 149 | dieWithMessage("ERROR: race on chunk header at address %p\n", this); |
| 150 | } |
| 151 | } |
| 152 | }; |
| 153 | |
| 154 | static bool ScudoInitIsRunning = false; |
| 155 | |
| 156 | static pthread_once_t GlobalInited = PTHREAD_ONCE_INIT; |
| 157 | static pthread_key_t pkey; |
| 158 | |
| 159 | static thread_local bool ThreadInited = false; |
| 160 | static thread_local bool ThreadTornDown = false; |
| 161 | static thread_local AllocatorCache Cache; |
| 162 | |
| 163 | static void teardownThread(void *p) { |
| 164 | uptr v = reinterpret_cast<uptr>(p); |
| 165 | // The glibc POSIX thread-local-storage deallocation routine calls user |
| 166 | // provided destructors in a loop of PTHREAD_DESTRUCTOR_ITERATIONS. |
| 167 | // We want to be called last since other destructors might call free and the |
| 168 | // like, so we wait until PTHREAD_DESTRUCTOR_ITERATIONS before draining the |
| 169 | // quarantine and swallowing the cache. |
| 170 | if (v < PTHREAD_DESTRUCTOR_ITERATIONS) { |
| 171 | pthread_setspecific(pkey, reinterpret_cast<void *>(v + 1)); |
| 172 | return; |
| 173 | } |
| 174 | drainQuarantine(); |
| 175 | getAllocator().DestroyCache(&Cache); |
| 176 | ThreadTornDown = true; |
| 177 | } |
| 178 | |
| 179 | static void initInternal() { |
| 180 | SanitizerToolName = "Scudo"; |
| 181 | CHECK(!ScudoInitIsRunning && "Scudo init calls itself!"); |
| 182 | ScudoInitIsRunning = true; |
| 183 | |
| 184 | initFlags(); |
| 185 | |
| 186 | AllocatorOptions Options; |
| 187 | Options.setFrom(getFlags(), common_flags()); |
| 188 | initAllocator(Options); |
| 189 | |
| 190 | ScudoInitIsRunning = false; |
| 191 | } |
| 192 | |
| 193 | static void initGlobal() { |
| 194 | pthread_key_create(&pkey, teardownThread); |
| 195 | initInternal(); |
| 196 | } |
| 197 | |
| 198 | static void NOINLINE initThread() { |
| 199 | pthread_once(&GlobalInited, initGlobal); |
| 200 | pthread_setspecific(pkey, reinterpret_cast<void *>(1)); |
| 201 | getAllocator().InitCache(&Cache); |
| 202 | ThreadInited = true; |
| 203 | } |
| 204 | |
| 205 | struct QuarantineCallback { |
| 206 | explicit QuarantineCallback(AllocatorCache *Cache) |
| 207 | : Cache_(Cache) {} |
| 208 | |
| 209 | // Chunk recycling function, returns a quarantined chunk to the backend. |
| 210 | void Recycle(ScudoChunk *Chunk) { |
| 211 | UnpackedHeader Header; |
| 212 | Chunk->loadHeader(&Header); |
| 213 | if (Header.State != ChunkQuarantine) { |
| 214 | dieWithMessage("ERROR: invalid chunk state when recycling address %p\n", |
| 215 | Chunk); |
| 216 | } |
| 217 | void *Ptr = Chunk->AllocBeg(&Header); |
| 218 | getAllocator().Deallocate(Cache_, Ptr); |
| 219 | } |
| 220 | |
| 221 | /// Internal quarantine allocation and deallocation functions. |
| 222 | void *Allocate(uptr Size) { |
| 223 | // The internal quarantine memory cannot be protected by us. But the only |
| 224 | // structures allocated are QuarantineBatch, that are 8KB for x64. So we |
| 225 | // will use mmap for those, and given that Deallocate doesn't pass a size |
| 226 | // in, we enforce the size of the allocation to be sizeof(QuarantineBatch). |
| 227 | // TODO(kostyak): switching to mmap impacts greatly performances, we have |
| 228 | // to find another solution |
| 229 | // CHECK_EQ(Size, sizeof(QuarantineBatch)); |
| 230 | // return MmapOrDie(Size, "QuarantineBatch"); |
| 231 | return getAllocator().Allocate(Cache_, Size, 1, false); |
| 232 | } |
| 233 | |
| 234 | void Deallocate(void *Ptr) { |
| 235 | // UnmapOrDie(Ptr, sizeof(QuarantineBatch)); |
| 236 | getAllocator().Deallocate(Cache_, Ptr); |
| 237 | } |
| 238 | |
| 239 | AllocatorCache *Cache_; |
| 240 | }; |
| 241 | |
| 242 | typedef Quarantine<QuarantineCallback, ScudoChunk> ScudoQuarantine; |
| 243 | typedef ScudoQuarantine::Cache QuarantineCache; |
| 244 | static thread_local QuarantineCache ThreadQuarantineCache; |
| 245 | |
| 246 | void AllocatorOptions::setFrom(const Flags *f, const CommonFlags *cf) { |
| 247 | MayReturnNull = cf->allocator_may_return_null; |
| 248 | QuarantineSizeMb = f->QuarantineSizeMb; |
| 249 | ThreadLocalQuarantineSizeKb = f->ThreadLocalQuarantineSizeKb; |
| 250 | DeallocationTypeMismatch = f->DeallocationTypeMismatch; |
| 251 | DeleteSizeMismatch = f->DeleteSizeMismatch; |
| 252 | ZeroContents = f->ZeroContents; |
| 253 | } |
| 254 | |
| 255 | void AllocatorOptions::copyTo(Flags *f, CommonFlags *cf) const { |
| 256 | cf->allocator_may_return_null = MayReturnNull; |
| 257 | f->QuarantineSizeMb = QuarantineSizeMb; |
| 258 | f->ThreadLocalQuarantineSizeKb = ThreadLocalQuarantineSizeKb; |
| 259 | f->DeallocationTypeMismatch = DeallocationTypeMismatch; |
| 260 | f->DeleteSizeMismatch = DeleteSizeMismatch; |
| 261 | f->ZeroContents = ZeroContents; |
| 262 | } |
| 263 | |
| 264 | struct Allocator { |
| 265 | static const uptr MaxAllowedMallocSize = 1ULL << 40; |
| 266 | static const uptr MinAlignment = 1 << MinAlignmentLog; |
| 267 | static const uptr MaxAlignment = 1 << MaxAlignmentLog; // 16 MB |
| 268 | |
| 269 | ScudoAllocator BackendAllocator; |
| 270 | ScudoQuarantine AllocatorQuarantine; |
| 271 | |
| 272 | // The fallback caches are used when the thread local caches have been |
| 273 | // 'detroyed' on thread tear-down. They are protected by a Mutex as they can |
| 274 | // be accessed by different threads. |
| 275 | StaticSpinMutex FallbackMutex; |
| 276 | AllocatorCache FallbackAllocatorCache; |
| 277 | QuarantineCache FallbackQuarantineCache; |
| 278 | |
| 279 | bool DeallocationTypeMismatch; |
| 280 | bool ZeroContents; |
| 281 | bool DeleteSizeMismatch; |
| 282 | |
| 283 | explicit Allocator(LinkerInitialized) |
| 284 | : AllocatorQuarantine(LINKER_INITIALIZED), |
| 285 | FallbackQuarantineCache(LINKER_INITIALIZED) {} |
| 286 | |
| 287 | void init(const AllocatorOptions &Options) { |
| 288 | // Currently SSE 4.2 support is required. This might change later. |
| 289 | CHECK(testCPUFeature(SSE4_2)); // for crc32 |
| 290 | |
| 291 | // Verify that the header offset field can hold the maximum offset. In the |
| 292 | // worst case scenario, the backend allocation is already aligned on |
| 293 | // MaxAlignment, so in order to store the header and still be aligned, we |
| 294 | // add an extra MaxAlignment. As a result, the offset from the beginning of |
| 295 | // the backend allocation to the chunk will be MaxAlignment - |
| 296 | // ChunkHeaderSize. |
| 297 | UnpackedHeader Header = {}; |
| 298 | uptr MaximumOffset = (MaxAlignment - ChunkHeaderSize) >> MinAlignmentLog; |
| 299 | Header.Offset = MaximumOffset; |
| 300 | if (Header.Offset != MaximumOffset) { |
| 301 | dieWithMessage("ERROR: the maximum possible offset doesn't fit in the " |
| 302 | "header\n"); |
| 303 | } |
| 304 | |
| 305 | DeallocationTypeMismatch = Options.DeallocationTypeMismatch; |
| 306 | DeleteSizeMismatch = Options.DeleteSizeMismatch; |
| 307 | ZeroContents = Options.ZeroContents; |
| 308 | BackendAllocator.Init(Options.MayReturnNull); |
| 309 | AllocatorQuarantine.Init(static_cast<uptr>(Options.QuarantineSizeMb) << 20, |
| 310 | static_cast<uptr>( |
| 311 | Options.ThreadLocalQuarantineSizeKb) << 10); |
| 312 | BackendAllocator.InitCache(&FallbackAllocatorCache); |
| 313 | Cookie = Prng.Next(); |
| 314 | } |
| 315 | |
| 316 | // Allocates a chunk. |
| 317 | void *allocate(uptr Size, uptr Alignment, AllocType Type) { |
| 318 | if (UNLIKELY(!ThreadInited)) |
| 319 | initThread(); |
| 320 | if (!IsPowerOfTwo(Alignment)) { |
| 321 | dieWithMessage("ERROR: malloc alignment is not a power of 2\n"); |
| 322 | } |
| 323 | if (Alignment > MaxAlignment) |
| 324 | return BackendAllocator.ReturnNullOrDie(); |
| 325 | if (Alignment < MinAlignment) |
| 326 | Alignment = MinAlignment; |
| 327 | if (Size == 0) |
| 328 | Size = 1; |
| 329 | if (Size >= MaxAllowedMallocSize) |
| 330 | return BackendAllocator.ReturnNullOrDie(); |
| 331 | uptr RoundedSize = RoundUpTo(Size, MinAlignment); |
| 332 | uptr ExtraBytes = ChunkHeaderSize; |
| 333 | if (Alignment > MinAlignment) |
| 334 | ExtraBytes += Alignment; |
| 335 | uptr NeededSize = RoundedSize + ExtraBytes; |
| 336 | if (NeededSize >= MaxAllowedMallocSize) |
| 337 | return BackendAllocator.ReturnNullOrDie(); |
| 338 | |
| 339 | void *Ptr; |
| 340 | if (LIKELY(!ThreadTornDown)) { |
| 341 | Ptr = BackendAllocator.Allocate(&Cache, NeededSize, MinAlignment); |
| 342 | } else { |
| 343 | SpinMutexLock l(&FallbackMutex); |
| 344 | Ptr = BackendAllocator.Allocate(&FallbackAllocatorCache, NeededSize, |
| 345 | MinAlignment); |
| 346 | } |
| 347 | if (!Ptr) |
| 348 | return BackendAllocator.ReturnNullOrDie(); |
| 349 | |
| 350 | // If requested, we will zero out the entire contents of the returned chunk. |
| 351 | if (ZeroContents && BackendAllocator.FromPrimary(Ptr)) |
| 352 | memset(Ptr, 0, BackendAllocator.GetActuallyAllocatedSize(Ptr)); |
| 353 | |
| 354 | uptr AllocBeg = reinterpret_cast<uptr>(Ptr); |
| 355 | uptr ChunkBeg = AllocBeg + ChunkHeaderSize; |
| 356 | if (!IsAligned(ChunkBeg, Alignment)) |
| 357 | ChunkBeg = RoundUpTo(ChunkBeg, Alignment); |
| 358 | CHECK_LE(ChunkBeg + Size, AllocBeg + NeededSize); |
| 359 | ScudoChunk *Chunk = |
| 360 | reinterpret_cast<ScudoChunk *>(ChunkBeg - ChunkHeaderSize); |
| 361 | UnpackedHeader Header = {}; |
| 362 | Header.State = ChunkAllocated; |
| 363 | Header.Offset = (ChunkBeg - ChunkHeaderSize - AllocBeg) >> MinAlignmentLog; |
| 364 | Header.AllocType = Type; |
| 365 | Header.RequestedSize = Size; |
| 366 | Header.Salt = static_cast<u16>(Prng.Next()); |
| 367 | Chunk->storeHeader(&Header); |
| 368 | void *UserPtr = reinterpret_cast<void *>(ChunkBeg); |
| 369 | // TODO(kostyak): hooks sound like a terrible idea security wise but might |
| 370 | // be needed for things to work properly? |
| 371 | // if (&__sanitizer_malloc_hook) __sanitizer_malloc_hook(UserPtr, Size); |
| 372 | return UserPtr; |
| 373 | } |
| 374 | |
| 375 | // Deallocates a Chunk, which means adding it to the delayed free list (or |
| 376 | // Quarantine). |
| 377 | void deallocate(void *UserPtr, uptr DeleteSize, AllocType Type) { |
| 378 | if (UNLIKELY(!ThreadInited)) |
| 379 | initThread(); |
| 380 | // TODO(kostyak): see hook comment above |
| 381 | // if (&__sanitizer_free_hook) __sanitizer_free_hook(UserPtr); |
| 382 | if (!UserPtr) |
| 383 | return; |
| 384 | uptr ChunkBeg = reinterpret_cast<uptr>(UserPtr); |
| 385 | if (!IsAligned(ChunkBeg, MinAlignment)) { |
| 386 | dieWithMessage("ERROR: attempted to deallocate a chunk not properly " |
| 387 | "aligned at address %p\n", UserPtr); |
| 388 | } |
| 389 | ScudoChunk *Chunk = |
| 390 | reinterpret_cast<ScudoChunk *>(ChunkBeg - ChunkHeaderSize); |
| 391 | UnpackedHeader OldHeader; |
| 392 | Chunk->loadHeader(&OldHeader); |
| 393 | if (OldHeader.State != ChunkAllocated) { |
| 394 | dieWithMessage("ERROR: invalid chunk state when deallocating address " |
| 395 | "%p\n", Chunk); |
| 396 | } |
| 397 | UnpackedHeader NewHeader = OldHeader; |
| 398 | NewHeader.State = ChunkQuarantine; |
| 399 | Chunk->compareExchangeHeader(&NewHeader, &OldHeader); |
| 400 | if (DeallocationTypeMismatch) { |
| 401 | // The deallocation type has to match the allocation one. |
| 402 | if (NewHeader.AllocType != Type) { |
| 403 | // With the exception of memalign'd Chunks, that can be still be free'd. |
| 404 | if (NewHeader.AllocType != FromMemalign || Type != FromMalloc) { |
| 405 | dieWithMessage("ERROR: allocation type mismatch on address %p\n", |
| 406 | Chunk); |
| 407 | } |
| 408 | } |
| 409 | } |
| 410 | uptr Size = NewHeader.RequestedSize; |
| 411 | if (DeleteSizeMismatch) { |
| 412 | if (DeleteSize && DeleteSize != Size) { |
| 413 | dieWithMessage("ERROR: invalid sized delete on chunk at address %p\n", |
| 414 | Chunk); |
| 415 | } |
| 416 | } |
| 417 | if (LIKELY(!ThreadTornDown)) { |
| 418 | AllocatorQuarantine.Put(&ThreadQuarantineCache, |
| 419 | QuarantineCallback(&Cache), Chunk, Size); |
| 420 | } else { |
| 421 | SpinMutexLock l(&FallbackMutex); |
| 422 | AllocatorQuarantine.Put(&FallbackQuarantineCache, |
| 423 | QuarantineCallback(&FallbackAllocatorCache), |
| 424 | Chunk, Size); |
| 425 | } |
| 426 | } |
| 427 | |
| 428 | // Returns the actual usable size of a chunk. Since this requires loading the |
| 429 | // header, we will return it in the second parameter, as it can be required |
| 430 | // by the caller to perform additional processing. |
| 431 | uptr getUsableSize(const void *Ptr, UnpackedHeader *Header) { |
| 432 | if (UNLIKELY(!ThreadInited)) |
| 433 | initThread(); |
| 434 | if (!Ptr) |
| 435 | return 0; |
| 436 | uptr ChunkBeg = reinterpret_cast<uptr>(Ptr); |
| 437 | ScudoChunk *Chunk = |
| 438 | reinterpret_cast<ScudoChunk *>(ChunkBeg - ChunkHeaderSize); |
| 439 | Chunk->loadHeader(Header); |
| 440 | // Getting the usable size of a chunk only makes sense if it's allocated. |
| 441 | if (Header->State != ChunkAllocated) { |
| 442 | dieWithMessage("ERROR: attempted to size a non-allocated chunk at " |
| 443 | "address %p\n", Chunk); |
| 444 | } |
| 445 | uptr Size = |
| 446 | BackendAllocator.GetActuallyAllocatedSize(Chunk->AllocBeg(Header)); |
| 447 | // UsableSize works as malloc_usable_size, which is also what (AFAIU) |
| 448 | // tcmalloc's MallocExtension::GetAllocatedSize aims at providing. This |
| 449 | // means we will return the size of the chunk from the user beginning to |
| 450 | // the end of the 'user' allocation, hence us subtracting the header size |
| 451 | // and the offset from the size. |
| 452 | if (Size == 0) |
| 453 | return Size; |
| 454 | return Size - ChunkHeaderSize - (Header->Offset << MinAlignmentLog); |
| 455 | } |
| 456 | |
| 457 | // Helper function that doesn't care about the header. |
| 458 | uptr getUsableSize(const void *Ptr) { |
| 459 | UnpackedHeader Header; |
| 460 | return getUsableSize(Ptr, &Header); |
| 461 | } |
| 462 | |
| 463 | // Reallocates a chunk. We can save on a new allocation if the new requested |
| 464 | // size still fits in the chunk. |
| 465 | void *reallocate(void *OldPtr, uptr NewSize) { |
| 466 | if (UNLIKELY(!ThreadInited)) |
| 467 | initThread(); |
| 468 | UnpackedHeader OldHeader; |
| 469 | uptr Size = getUsableSize(OldPtr, &OldHeader); |
| 470 | uptr ChunkBeg = reinterpret_cast<uptr>(OldPtr); |
| 471 | ScudoChunk *Chunk = |
| 472 | reinterpret_cast<ScudoChunk *>(ChunkBeg - ChunkHeaderSize); |
| 473 | if (OldHeader.AllocType != FromMalloc) { |
| 474 | dieWithMessage("ERROR: invalid chunk type when reallocating address %p\n", |
| 475 | Chunk); |
| 476 | } |
| 477 | UnpackedHeader NewHeader = OldHeader; |
| 478 | // The new size still fits in the current chunk. |
| 479 | if (NewSize <= Size) { |
| 480 | NewHeader.RequestedSize = NewSize; |
| 481 | Chunk->compareExchangeHeader(&NewHeader, &OldHeader); |
| 482 | return OldPtr; |
| 483 | } |
| 484 | // Otherwise, we have to allocate a new chunk and copy the contents of the |
| 485 | // old one. |
| 486 | void *NewPtr = allocate(NewSize, MinAlignment, FromMalloc); |
| 487 | if (NewPtr) { |
| 488 | uptr OldSize = OldHeader.RequestedSize; |
| 489 | memcpy(NewPtr, OldPtr, Min(NewSize, OldSize)); |
| 490 | NewHeader.State = ChunkQuarantine; |
| 491 | Chunk->compareExchangeHeader(&NewHeader, &OldHeader); |
| 492 | if (LIKELY(!ThreadTornDown)) { |
| 493 | AllocatorQuarantine.Put(&ThreadQuarantineCache, |
| 494 | QuarantineCallback(&Cache), Chunk, OldSize); |
| 495 | } else { |
| 496 | SpinMutexLock l(&FallbackMutex); |
| 497 | AllocatorQuarantine.Put(&FallbackQuarantineCache, |
| 498 | QuarantineCallback(&FallbackAllocatorCache), |
| 499 | Chunk, OldSize); |
| 500 | } |
| 501 | } |
| 502 | return NewPtr; |
| 503 | } |
| 504 | |
| 505 | void *calloc(uptr NMemB, uptr Size) { |
| 506 | if (UNLIKELY(!ThreadInited)) |
| 507 | initThread(); |
| 508 | uptr Total = NMemB * Size; |
| 509 | if (Size != 0 && Total / Size != NMemB) // Overflow check |
| 510 | return BackendAllocator.ReturnNullOrDie(); |
| 511 | void *Ptr = allocate(Total, MinAlignment, FromMalloc); |
| 512 | // If ZeroContents, the content of the chunk has already been zero'd out. |
| 513 | if (!ZeroContents && Ptr && BackendAllocator.FromPrimary(Ptr)) |
| 514 | memset(Ptr, 0, getUsableSize(Ptr)); |
| 515 | return Ptr; |
| 516 | } |
| 517 | |
| 518 | void drainQuarantine() { |
| 519 | AllocatorQuarantine.Drain(&ThreadQuarantineCache, |
| 520 | QuarantineCallback(&Cache)); |
| 521 | } |
| 522 | }; |
| 523 | |
| 524 | static Allocator Instance(LINKER_INITIALIZED); |
| 525 | |
| 526 | static ScudoAllocator &getAllocator() { |
| 527 | return Instance.BackendAllocator; |
| 528 | } |
| 529 | |
| 530 | void initAllocator(const AllocatorOptions &Options) { |
| 531 | Instance.init(Options); |
| 532 | } |
| 533 | |
| 534 | void drainQuarantine() { |
| 535 | Instance.drainQuarantine(); |
| 536 | } |
| 537 | |
| 538 | void *scudoMalloc(uptr Size, AllocType Type) { |
| 539 | return Instance.allocate(Size, Allocator::MinAlignment, Type); |
| 540 | } |
| 541 | |
| 542 | void scudoFree(void *Ptr, AllocType Type) { |
| 543 | Instance.deallocate(Ptr, 0, Type); |
| 544 | } |
| 545 | |
| 546 | void scudoSizedFree(void *Ptr, uptr Size, AllocType Type) { |
| 547 | Instance.deallocate(Ptr, Size, Type); |
| 548 | } |
| 549 | |
| 550 | void *scudoRealloc(void *Ptr, uptr Size) { |
| 551 | if (!Ptr) |
| 552 | return Instance.allocate(Size, Allocator::MinAlignment, FromMalloc); |
| 553 | if (Size == 0) { |
| 554 | Instance.deallocate(Ptr, 0, FromMalloc); |
| 555 | return nullptr; |
| 556 | } |
| 557 | return Instance.reallocate(Ptr, Size); |
| 558 | } |
| 559 | |
| 560 | void *scudoCalloc(uptr NMemB, uptr Size) { |
| 561 | return Instance.calloc(NMemB, Size); |
| 562 | } |
| 563 | |
| 564 | void *scudoValloc(uptr Size) { |
| 565 | return Instance.allocate(Size, GetPageSizeCached(), FromMemalign); |
| 566 | } |
| 567 | |
| 568 | void *scudoMemalign(uptr Alignment, uptr Size) { |
| 569 | return Instance.allocate(Size, Alignment, FromMemalign); |
| 570 | } |
| 571 | |
| 572 | void *scudoPvalloc(uptr Size) { |
| 573 | uptr PageSize = GetPageSizeCached(); |
| 574 | Size = RoundUpTo(Size, PageSize); |
| 575 | if (Size == 0) { |
| 576 | // pvalloc(0) should allocate one page. |
| 577 | Size = PageSize; |
| 578 | } |
| 579 | return Instance.allocate(Size, PageSize, FromMemalign); |
| 580 | } |
| 581 | |
| 582 | int scudoPosixMemalign(void **MemPtr, uptr Alignment, uptr Size) { |
| 583 | *MemPtr = Instance.allocate(Size, Alignment, FromMemalign); |
| 584 | return 0; |
| 585 | } |
| 586 | |
| 587 | void *scudoAlignedAlloc(uptr Alignment, uptr Size) { |
| 588 | // size must be a multiple of the alignment. To avoid a division, we first |
| 589 | // make sure that alignment is a power of 2. |
| 590 | CHECK(IsPowerOfTwo(Alignment)); |
| 591 | CHECK_EQ((Size & (Alignment - 1)), 0); |
| 592 | return Instance.allocate(Size, Alignment, FromMalloc); |
| 593 | } |
| 594 | |
| 595 | uptr scudoMallocUsableSize(void *Ptr) { |
| 596 | return Instance.getUsableSize(Ptr); |
| 597 | } |
| 598 | |
| 599 | } // namespace __scudo |
| 600 | |
| 601 | using namespace __scudo; |
| 602 | |
| 603 | // MallocExtension helper functions |
| 604 | |
| 605 | uptr __sanitizer_get_current_allocated_bytes() { |
| 606 | uptr stats[AllocatorStatCount]; |
| 607 | getAllocator().GetStats(stats); |
| 608 | return stats[AllocatorStatAllocated]; |
| 609 | } |
| 610 | |
| 611 | uptr __sanitizer_get_heap_size() { |
| 612 | uptr stats[AllocatorStatCount]; |
| 613 | getAllocator().GetStats(stats); |
| 614 | return stats[AllocatorStatMapped]; |
| 615 | } |
| 616 | |
| 617 | uptr __sanitizer_get_free_bytes() { |
| 618 | return 1; |
| 619 | } |
| 620 | |
| 621 | uptr __sanitizer_get_unmapped_bytes() { |
| 622 | return 1; |
| 623 | } |
| 624 | |
| 625 | uptr __sanitizer_get_estimated_allocated_size(uptr size) { |
| 626 | return size; |
| 627 | } |
| 628 | |
| 629 | int __sanitizer_get_ownership(const void *p) { |
| 630 | return Instance.getUsableSize(p) != 0; |
| 631 | } |
| 632 | |
| 633 | uptr __sanitizer_get_allocated_size(const void *p) { |
| 634 | return Instance.getUsableSize(p); |
| 635 | } |