1 // Copyright 2016 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 // This code should move into the default Windows shim once the win-specific
6 // allocation shim has been removed, and the generic shim has becaome the
7 // default.
8 
9 #include "partition_alloc/shim/winheap_stubs_win.h"
10 
11 #include <windows.h>
12 
13 #include <malloc.h>
14 #include <new.h>
15 
16 #include <algorithm>
17 #include <bit>
18 #include <climits>
19 #include <limits>
20 
21 #include "partition_alloc/partition_alloc_base/bits.h"
22 #include "partition_alloc/partition_alloc_base/numerics/safe_conversions.h"
23 #include "partition_alloc/partition_alloc_check.h"
24 
25 namespace allocator_shim {
26 
27 bool g_is_win_shim_layer_initialized = false;
28 
29 namespace {
30 
31 const size_t kWindowsPageSize = 4096;
32 const size_t kMaxWindowsAllocation = INT_MAX - kWindowsPageSize;
33 
get_heap_handle()34 inline HANDLE get_heap_handle() {
35   return reinterpret_cast<HANDLE>(_get_heap_handle());
36 }
37 
38 }  // namespace
39 
WinHeapMalloc(size_t size)40 void* WinHeapMalloc(size_t size) {
41   if (size < kMaxWindowsAllocation) {
42     return HeapAlloc(get_heap_handle(), 0, size);
43   }
44   return nullptr;
45 }
46 
WinHeapFree(void * ptr)47 void WinHeapFree(void* ptr) {
48   if (!ptr) {
49     return;
50   }
51 
52   HeapFree(get_heap_handle(), 0, ptr);
53 }
54 
WinHeapRealloc(void * ptr,size_t size)55 void* WinHeapRealloc(void* ptr, size_t size) {
56   if (!ptr) {
57     return WinHeapMalloc(size);
58   }
59   if (!size) {
60     WinHeapFree(ptr);
61     return nullptr;
62   }
63   if (size < kMaxWindowsAllocation) {
64     return HeapReAlloc(get_heap_handle(), 0, ptr, size);
65   }
66   return nullptr;
67 }
68 
WinHeapGetSizeEstimate(void * ptr)69 size_t WinHeapGetSizeEstimate(void* ptr) {
70   if (!ptr) {
71     return 0;
72   }
73 
74   return HeapSize(get_heap_handle(), 0, ptr);
75 }
76 
77 // Call the new handler, if one has been set.
78 // Returns true on successfully calling the handler, false otherwise.
WinCallNewHandler(size_t size)79 bool WinCallNewHandler(size_t size) {
80 #ifdef _CPPUNWIND
81 #error "Exceptions in allocator shim are not supported!"
82 #endif  // _CPPUNWIND
83   // Get the current new handler.
84   _PNH nh = _query_new_handler();
85   if (!nh) {
86     return false;
87   }
88   // Since exceptions are disabled, we don't really know if new_handler
89   // failed.  Assume it will abort if it fails.
90   return nh(size) ? true : false;
91 }
92 
93 // The Windows _aligned_* functions are implemented by creating an allocation
94 // with enough space to create an aligned allocation internally. The offset to
95 // the original allocation is prefixed to the aligned allocation so that it can
96 // be correctly freed.
97 
98 namespace {
99 
100 struct AlignedPrefix {
101   // Offset to the original allocation point.
102   unsigned int original_allocation_offset;
103   // Make sure an unsigned int is enough to store the offset
104   static_assert(
105       kMaxWindowsAllocation < std::numeric_limits<unsigned int>::max(),
106       "original_allocation_offset must be able to fit into an unsigned int");
107 #if BUILDFLAG(PA_DCHECK_IS_ON)
108   // Magic value used to check that _aligned_free() and _aligned_realloc() are
109   // only ever called on an aligned allocated chunk.
110   static constexpr unsigned int kMagic = 0x12003400;
111   unsigned int magic;
112 #endif  // BUILDFLAG(PA_DCHECK_IS_ON)
113 };
114 
115 // Compute how large an allocation we need to fit an allocation with the given
116 // size and alignment and space for a prefix pointer.
AdjustedSize(size_t size,size_t alignment)117 size_t AdjustedSize(size_t size, size_t alignment) {
118   // Minimal alignment is the prefix size so the prefix is properly aligned.
119   alignment = std::max(alignment, alignof(AlignedPrefix));
120   return size + sizeof(AlignedPrefix) + alignment - 1;
121 }
122 
123 // Align the allocation and write the prefix.
AlignAllocation(void * ptr,size_t alignment)124 void* AlignAllocation(void* ptr, size_t alignment) {
125   // Minimal alignment is the prefix size so the prefix is properly aligned.
126   alignment = std::max(alignment, alignof(AlignedPrefix));
127 
128   uintptr_t address = reinterpret_cast<uintptr_t>(ptr);
129   address = partition_alloc::internal::base::bits::AlignUp(
130       address + sizeof(AlignedPrefix), alignment);
131 
132   // Write the prefix.
133   AlignedPrefix* prefix = reinterpret_cast<AlignedPrefix*>(address) - 1;
134   prefix->original_allocation_offset =
135       partition_alloc::internal::base::checked_cast<unsigned int>(
136           address - reinterpret_cast<uintptr_t>(ptr));
137 #if BUILDFLAG(PA_DCHECK_IS_ON)
138   prefix->magic = AlignedPrefix::kMagic;
139 #endif  // BUILDFLAG(PA_DCHECK_IS_ON)
140   return reinterpret_cast<void*>(address);
141 }
142 
143 // Return the original allocation from an aligned allocation.
UnalignAllocation(void * ptr)144 void* UnalignAllocation(void* ptr) {
145   AlignedPrefix* prefix = reinterpret_cast<AlignedPrefix*>(ptr) - 1;
146 #if BUILDFLAG(PA_DCHECK_IS_ON)
147   PA_DCHECK(prefix->magic == AlignedPrefix::kMagic);
148 #endif  // BUILDFLAG(PA_DCHECK_IS_ON)
149   void* unaligned =
150       static_cast<uint8_t*>(ptr) - prefix->original_allocation_offset;
151   PA_CHECK(unaligned < ptr);
152   PA_CHECK(reinterpret_cast<uintptr_t>(ptr) -
153                reinterpret_cast<uintptr_t>(unaligned) <=
154            kMaxWindowsAllocation);
155   return unaligned;
156 }
157 
158 }  // namespace
159 
WinHeapAlignedMalloc(size_t size,size_t alignment)160 void* WinHeapAlignedMalloc(size_t size, size_t alignment) {
161   PA_CHECK(std::has_single_bit(alignment));
162 
163   size_t adjusted = AdjustedSize(size, alignment);
164   if (adjusted >= kMaxWindowsAllocation) {
165     return nullptr;
166   }
167 
168   void* ptr = WinHeapMalloc(adjusted);
169   if (!ptr) {
170     return nullptr;
171   }
172 
173   return AlignAllocation(ptr, alignment);
174 }
175 
WinHeapAlignedRealloc(void * ptr,size_t size,size_t alignment)176 void* WinHeapAlignedRealloc(void* ptr, size_t size, size_t alignment) {
177   PA_CHECK(std::has_single_bit(alignment));
178 
179   if (!ptr) {
180     return WinHeapAlignedMalloc(size, alignment);
181   }
182   if (!size) {
183     WinHeapAlignedFree(ptr);
184     return nullptr;
185   }
186 
187   size_t adjusted = AdjustedSize(size, alignment);
188   if (adjusted >= kMaxWindowsAllocation) {
189     return nullptr;
190   }
191 
192   // Try to resize the allocation in place first.
193   void* unaligned = UnalignAllocation(ptr);
194   if (HeapReAlloc(get_heap_handle(), HEAP_REALLOC_IN_PLACE_ONLY, unaligned,
195                   adjusted)) {
196     return ptr;
197   }
198 
199   // Otherwise manually perform an _aligned_malloc() and copy since an
200   // unaligned allocation from HeapReAlloc() would force us to copy the
201   // allocation twice.
202   void* new_ptr = WinHeapAlignedMalloc(size, alignment);
203   if (!new_ptr) {
204     return nullptr;
205   }
206 
207   size_t gap =
208       reinterpret_cast<uintptr_t>(ptr) - reinterpret_cast<uintptr_t>(unaligned);
209   size_t old_size = WinHeapGetSizeEstimate(unaligned) - gap;
210   memcpy(new_ptr, ptr, std::min(size, old_size));
211   WinHeapAlignedFree(ptr);
212   return new_ptr;
213 }
214 
WinHeapAlignedFree(void * ptr)215 void WinHeapAlignedFree(void* ptr) {
216   if (!ptr) {
217     return;
218   }
219 
220   void* original_allocation = UnalignAllocation(ptr);
221   WinHeapFree(original_allocation);
222 }
223 
224 }  // namespace allocator_shim
225