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