1 /* 2 * Copyright (C) 2013 Google Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions are 6 * met: 7 * 8 * * Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * * Redistributions in binary form must reproduce the above 11 * copyright notice, this list of conditions and the following disclaimer 12 * in the documentation and/or other materials provided with the 13 * distribution. 14 * * Neither the name of Google Inc. nor the names of its 15 * contributors may be used to endorse or promote products derived from 16 * this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31 #ifndef WTF_PartitionAlloc_h 32 #define WTF_PartitionAlloc_h 33 34 // DESCRIPTION 35 // partitionAlloc() and partitionFree() are approximately analagous 36 // to malloc() and free(). 37 // The main difference is that a PartitionRoot object must be supplied to 38 // partitionAlloc(), representing a specific "heap partition" that will 39 // be used to satisfy the allocation. Different partitions are guaranteed to 40 // exist in separate address spaces, including being separate from the main 41 // system heap. If the contained objects are all freed, physical memory is 42 // returned to the system but the address space remains reserved. 43 // 44 // Allocations and frees against a single partition must be single threaded. 45 // Allocations must not exceed a max size, typically 4088 bytes at this time. 46 // Allocation sizes must be aligned to the system pointer size. 47 // The separate APIs partitionAllocGeneric and partitionFreeGeneric are 48 // provided, and they do not have the above three restrictions. In return, you 49 // take a small performance hit and are also obliged to keep track of 50 // allocation sizes, and pass them to partitionFreeGeneric. 51 // 52 // This allocator is designed to be extremely fast, thanks to the following 53 // properties and design: 54 // - Just a single (reasonably predicatable) branch in the hot / fast path for 55 // both allocating and (significantly) freeing. 56 // - A minimal number of operations in the hot / fast path, with the slow paths 57 // in separate functions, leading to the possibility of inlining. 58 // - Each partition page (which is usually multiple physical pages) has a header 59 // structure which allows fast mapping of free() address to an underlying 60 // bucket. 61 // - Supports a lock-free API for fast performance in single-threaded cases. 62 // - The freelist for a given bucket is split across a number of partition 63 // pages, enabling various simple tricks to try and minimize fragmentation. 64 // - Fine-grained bucket sizes leading to less waste and better packing. 65 // 66 // The following security properties are provided at this time: 67 // - Linear overflows cannot corrupt into the partition. 68 // - Linear overflows cannot corrupt out of the partition. 69 // - Freed pages will only be re-used within the partition. 70 // - Freed pages will only hold same-sized objects when re-used. 71 // - Dereference of freelist pointer will fault. 72 // 73 // The following security properties could be investigated in the future: 74 // - No double-free detection (tcmalloc has some but it may be only a detection 75 // and not a defense). 76 // - No randomness in freelist pointers. 77 // - Per-object bucketing (instead of per-size) is mostly available at the API, 78 // but not used yet. 79 // - No randomness of freelist entries or bucket position. 80 // - No specific protection against corruption of page header metadata. 81 82 #include "wtf/Assertions.h" 83 #include "wtf/CPU.h" 84 #include "wtf/FastMalloc.h" 85 #include "wtf/SpinLock.h" 86 87 #include <stdlib.h> 88 89 namespace WTF { 90 91 #if CPU(MIPS) 92 // Allocation granularity of sizeof(double) bytes. 93 typedef double align_t; 94 #define WTF_ALIGN(n) __attribute__((__aligned__(n))) 95 #else 96 // Allocation granularity of sizeof(void*) bytes. 97 typedef void * align_t; 98 #define WTF_ALIGN(n) 99 #endif 100 101 static const size_t kAllocationGranularity = sizeof(align_t); 102 static const size_t kAllocationGranularityMask = kAllocationGranularity - 1; 103 static const size_t kBucketShift = (kAllocationGranularity == 8) ? 3 : 2; 104 // Supports allocations up to 4088 (one bucket is used for metadata). 105 static const size_t kMaxAllocationOrder = 12; 106 static const size_t kMaxAllocation = (1 << kMaxAllocationOrder) - kAllocationGranularity; 107 static const size_t kNumBuckets = (1 << kMaxAllocationOrder) / (1 << kBucketShift); 108 // Underlying partition storage pages are a power-of-two size. It is typical 109 // for a partition page to be based on multiple system pages. We rarely deal 110 // with system pages. Most references to "page" refer to partition pages. We 111 // do also have the concept of "super pages" -- these are the underlying 112 // system allocations we make. Super pages can typically fit multiple 113 // partition pages inside them. See PageAllocator.h for more details on 114 // super pages. 115 static const size_t kPartitionPageSize = 1 << 14; // 16KB 116 static const size_t kPartitionPageOffsetMask = kPartitionPageSize - 1; 117 static const size_t kPartitionPageBaseMask = ~kPartitionPageOffsetMask; 118 // Special bucket id for free page metadata. 119 static const size_t kFreePageBucket = 0; 120 121 struct PartitionRoot; 122 struct PartitionBucket; 123 124 struct PartitionFreelistEntry { 125 PartitionFreelistEntry* next; 126 }; 127 128 struct PartitionPageHeader { 129 int numAllocatedSlots; // Deliberately signed. 130 PartitionBucket* bucket; 131 PartitionFreelistEntry* freelistHead; 132 PartitionPageHeader* next; 133 PartitionPageHeader* prev; 134 } WTF_ALIGN(sizeof(align_t)); 135 136 struct PartitionFreepagelistEntry { 137 PartitionPageHeader* page; 138 PartitionFreepagelistEntry* next; 139 } WTF_ALIGN(sizeof(align_t)); 140 141 struct PartitionBucket { 142 PartitionRoot* root; 143 PartitionPageHeader* currPage; 144 PartitionFreepagelistEntry* freePages; 145 size_t numFullPages; 146 } WTF_ALIGN(sizeof(align_t)); 147 148 struct PartitionRoot { 149 int lock; 150 PartitionPageHeader seedPage; 151 PartitionBucket seedBucket; 152 PartitionBucket buckets[kNumBuckets]; 153 char* nextSuperPage; 154 char* nextPartitionPage; 155 char* nextPartitionPageEnd; 156 bool initialized; 157 }; 158 159 WTF_EXPORT void partitionAllocInit(PartitionRoot*); 160 WTF_EXPORT bool partitionAllocShutdown(PartitionRoot*); 161 162 WTF_EXPORT NEVER_INLINE void* partitionAllocSlowPath(PartitionBucket*); 163 WTF_EXPORT NEVER_INLINE void partitionFreeSlowPath(PartitionPageHeader*); 164 WTF_EXPORT NEVER_INLINE void* partitionReallocGeneric(PartitionRoot*, void*, size_t, size_t); 165 166 ALWAYS_INLINE PartitionFreelistEntry* partitionFreelistMask(PartitionFreelistEntry* ptr) 167 { 168 // For now, use a simple / fast mask that guarantees an invalid pointer in 169 // case it gets used as a vtable pointer. 170 // The one attack we're fully mitigating is where an object is freed and its 171 // vtable used where the attacker doesn't get the chance to run allocations 172 // between the free and use. 173 // We're deliberately not trying to defend against OOB reads or writes. 174 uintptr_t masked = ~reinterpret_cast<uintptr_t>(ptr); 175 return reinterpret_cast<PartitionFreelistEntry*>(masked); 176 } 177 178 ALWAYS_INLINE size_t partitionBucketSize(const PartitionBucket* bucket) 179 { 180 PartitionRoot* root = bucket->root; 181 size_t index = bucket - &root->buckets[0]; 182 size_t size; 183 if (UNLIKELY(index == kFreePageBucket)) 184 size = sizeof(PartitionFreepagelistEntry); 185 else 186 size = index << kBucketShift; 187 return size; 188 } 189 190 ALWAYS_INLINE void* partitionBucketAlloc(PartitionBucket* bucket) 191 { 192 PartitionPageHeader* page = bucket->currPage; 193 PartitionFreelistEntry* ret = page->freelistHead; 194 if (LIKELY(ret != 0)) { 195 page->freelistHead = partitionFreelistMask(ret->next); 196 page->numAllocatedSlots++; 197 return ret; 198 } 199 return partitionAllocSlowPath(bucket); 200 } 201 202 ALWAYS_INLINE size_t partitionAllocRoundup(size_t size) 203 { 204 return (size + kAllocationGranularityMask) & ~kAllocationGranularityMask; 205 } 206 207 ALWAYS_INLINE void* partitionAlloc(PartitionRoot* root, size_t size) 208 { 209 #if defined(MEMORY_TOOL_REPLACES_ALLOCATOR) 210 return malloc(size); 211 #else 212 #if CPU(MIPS) 213 size = partitionAllocRoundup(size); 214 #endif 215 size_t index = size >> kBucketShift; 216 ASSERT(index < kNumBuckets); 217 ASSERT(size == index << kBucketShift); 218 PartitionBucket* bucket = &root->buckets[index]; 219 return partitionBucketAlloc(bucket); 220 #endif 221 } 222 223 ALWAYS_INLINE PartitionPageHeader* partitionPointerToPage(void* ptr) 224 { 225 uintptr_t pointerAsUint = reinterpret_cast<uintptr_t>(ptr); 226 // Checks that the pointer is after the page header. You can't free the 227 // page header! 228 ASSERT((pointerAsUint & kPartitionPageOffsetMask) >= sizeof(PartitionPageHeader)); 229 PartitionPageHeader* page = reinterpret_cast<PartitionPageHeader*>(pointerAsUint & kPartitionPageBaseMask); 230 // Checks that the pointer is a multiple of bucket size. 231 ASSERT(!(((pointerAsUint & kPartitionPageOffsetMask) - sizeof(PartitionPageHeader)) % partitionBucketSize(page->bucket))); 232 return page; 233 } 234 235 ALWAYS_INLINE void partitionFreeWithPage(void* ptr, PartitionPageHeader* page) 236 { 237 PartitionFreelistEntry* entry = static_cast<PartitionFreelistEntry*>(ptr); 238 entry->next = partitionFreelistMask(page->freelistHead); 239 page->freelistHead = entry; 240 --page->numAllocatedSlots; 241 if (UNLIKELY(page->numAllocatedSlots <= 0)) 242 partitionFreeSlowPath(page); 243 } 244 245 ALWAYS_INLINE void partitionFree(void* ptr) 246 { 247 #if defined(MEMORY_TOOL_REPLACES_ALLOCATOR) 248 free(ptr); 249 #else 250 PartitionPageHeader* page = partitionPointerToPage(ptr); 251 partitionFreeWithPage(ptr, page); 252 #endif 253 } 254 255 ALWAYS_INLINE void* partitionAllocGeneric(PartitionRoot* root, size_t size) 256 { 257 #if defined(MEMORY_TOOL_REPLACES_ALLOCATOR) 258 return malloc(size); 259 #else 260 size = partitionAllocRoundup(size); 261 if (LIKELY(size <= kMaxAllocation)) { 262 spinLockLock(&root->lock); 263 void* ret = partitionAlloc(root, size); 264 spinLockUnlock(&root->lock); 265 return ret; 266 } 267 return WTF::fastMalloc(size); 268 #endif 269 } 270 271 ALWAYS_INLINE void partitionFreeGeneric(void* ptr, size_t size) 272 { 273 #if defined(MEMORY_TOOL_REPLACES_ALLOCATOR) 274 free(ptr); 275 #else 276 if (LIKELY(size <= kMaxAllocation)) { 277 PartitionPageHeader* page = partitionPointerToPage(ptr); 278 PartitionRoot* root = page->bucket->root; 279 spinLockLock(&root->lock); 280 partitionFreeWithPage(ptr, page); 281 spinLockUnlock(&root->lock); 282 return; 283 } 284 return WTF::fastFree(ptr); 285 #endif 286 } 287 288 } // namespace WTF 289 290 using WTF::PartitionRoot; 291 using WTF::partitionAllocInit; 292 using WTF::partitionAllocShutdown; 293 using WTF::partitionAlloc; 294 using WTF::partitionFree; 295 using WTF::partitionAllocGeneric; 296 using WTF::partitionFreeGeneric; 297 using WTF::partitionReallocGeneric; 298 299 #endif // WTF_PartitionAlloc_h 300