1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
6 
7 #ifndef mozilla_interceptor_VMSharingPolicies_h
8 #define mozilla_interceptor_VMSharingPolicies_h
9 
10 #include "mozilla/Assertions.h"
11 #include "mozilla/Attributes.h"
12 #include "mozilla/Maybe.h"
13 #include "mozilla/Types.h"
14 
15 namespace mozilla {
16 namespace interceptor {
17 
18 /**
19  * This class is an abstraction of a reservation of virtual address space that
20  * has been obtained from a VMSharingPolicy via the policy's |Reserve| method.
21  *
22  * TrampolinePool allows us to obtain a trampoline without needing to concern
23  * ourselves with the underlying implementation of the VM sharing policy.
24  *
25  * For example, VMSharingPolicyShared delegates to VMSharingPolicyUnique, but
26  * also requires taking a lock before doing so. By invoking |GetNextTrampoline|
27  * on a TrampolinePool, the caller does not need to concern themselves with
28  * that detail.
29  *
30  * We accompolish this with a recursive implementation that provides an inner
31  * TrampolinePool that is provided by the delegated VMSharingPolicy.
32  */
33 template <typename VMPolicyT, typename InnerT>
34 class MOZ_STACK_CLASS TrampolinePool final {
35  public:
36   TrampolinePool(TrampolinePool&& aOther) = default;
37 
TrampolinePool(VMPolicyT & aVMPolicy,InnerT && aInner)38   TrampolinePool(VMPolicyT& aVMPolicy, InnerT&& aInner)
39       : mVMPolicy(aVMPolicy), mInner(std::move(aInner)) {}
40 
41   TrampolinePool& operator=(TrampolinePool&& aOther) = delete;
42   TrampolinePool(const TrampolinePool&) = delete;
43   TrampolinePool& operator=(const TrampolinePool&) = delete;
44 
45   using MMPolicyT = typename VMPolicyT::MMPolicyT;
46 
GetNextTrampoline()47   Maybe<Trampoline<MMPolicyT>> GetNextTrampoline() {
48     return mVMPolicy.GetNextTrampoline(mInner);
49   }
50 
51 #if defined(_M_X64)
IsInLowest2GB()52   bool IsInLowest2GB() const {
53     return mVMPolicy.IsTrampolineSpaceInLowest2GB(mInner);
54   }
55 #endif  // defined(_M_X64)
56 
57  private:
58   VMPolicyT& mVMPolicy;
59   InnerT mInner;
60 };
61 
62 /**
63  * This specialization is the base case for TrampolinePool, and is used by
64  * VMSharingPolicyUnique (since that policy does not delegate anything).
65  */
66 template <typename VMPolicyT>
67 class MOZ_STACK_CLASS TrampolinePool<VMPolicyT, decltype(nullptr)> final {
68  public:
TrampolinePool(VMPolicyT & aVMPolicy)69   explicit TrampolinePool(VMPolicyT& aVMPolicy) : mVMPolicy(aVMPolicy) {}
70 
71   TrampolinePool(TrampolinePool&& aOther) = default;
72 
73   TrampolinePool& operator=(TrampolinePool&& aOther) = delete;
74   TrampolinePool(const TrampolinePool&) = delete;
75   TrampolinePool& operator=(const TrampolinePool&) = delete;
76 
77   using MMPolicyT = typename VMPolicyT::MMPolicyT;
78 
GetNextTrampoline()79   Maybe<Trampoline<MMPolicyT>> GetNextTrampoline() {
80     return mVMPolicy.GetNextTrampoline();
81   }
82 
83 #if defined(_M_X64)
IsInLowest2GB()84   bool IsInLowest2GB() const {
85     return mVMPolicy.IsTrampolineSpaceInLowest2GB();
86   }
87 #endif  // defined(_M_X64)
88 
89  private:
90   VMPolicyT& mVMPolicy;
91 };
92 
93 template <typename MMPolicy>
94 class VMSharingPolicyUnique : public MMPolicy {
95   using ThisType = VMSharingPolicyUnique<MMPolicy>;
96 
97  public:
98   using PoolType = TrampolinePool<ThisType, decltype(nullptr)>;
99 
100   template <typename... Args>
VMSharingPolicyUnique(Args &&...aArgs)101   explicit VMSharingPolicyUnique(Args&&... aArgs)
102       : MMPolicy(std::forward<Args>(aArgs)...), mNextChunkIndex(0) {}
103 
Reserve(const uintptr_t aPivotAddr,const uint32_t aMaxDistanceFromPivot)104   Maybe<PoolType> Reserve(const uintptr_t aPivotAddr,
105                           const uint32_t aMaxDistanceFromPivot) {
106     // Win32 allocates VM addresses at a 64KiB granularity, so we might as well
107     // utilize that entire 64KiB reservation.
108     uint32_t len = MMPolicy::GetAllocGranularity();
109 
110     Maybe<Span<const uint8_t>> maybeBounds = MMPolicy::SpanFromPivotAndDistance(
111         len, aPivotAddr, aMaxDistanceFromPivot);
112 
113     return Reserve(len, maybeBounds);
114   }
115 
Reserve(const uint32_t aSize,const Maybe<Span<const uint8_t>> & aBounds)116   Maybe<PoolType> Reserve(const uint32_t aSize,
117                           const Maybe<Span<const uint8_t>>& aBounds) {
118     uint32_t bytesReserved = MMPolicy::Reserve(aSize, aBounds);
119     if (!bytesReserved) {
120       return Nothing();
121     }
122 
123     return Some(PoolType(*this));
124   }
125 
Items()126   TrampolineCollection<MMPolicy> Items() const {
127     return TrampolineCollection<MMPolicy>(*this, this->GetLocalView(),
128                                           this->GetRemoteView(), kChunkSize,
129                                           mNextChunkIndex);
130   }
131 
Clear()132   void Clear() { mNextChunkIndex = 0; }
133 
134   ~VMSharingPolicyUnique() = default;
135 
136   VMSharingPolicyUnique(const VMSharingPolicyUnique&) = delete;
137   VMSharingPolicyUnique& operator=(const VMSharingPolicyUnique&) = delete;
138 
VMSharingPolicyUnique(VMSharingPolicyUnique && aOther)139   VMSharingPolicyUnique(VMSharingPolicyUnique&& aOther)
140       : MMPolicy(std::move(aOther)), mNextChunkIndex(aOther.mNextChunkIndex) {
141     aOther.mNextChunkIndex = 0;
142   }
143 
144   VMSharingPolicyUnique& operator=(VMSharingPolicyUnique&& aOther) {
145     static_cast<MMPolicy&>(*this) = std::move(aOther);
146     mNextChunkIndex = aOther.mNextChunkIndex;
147     aOther.mNextChunkIndex = 0;
148     return *this;
149   }
150 
151  protected:
152   // In VMSharingPolicyUnique we do not implement the overload that accepts
153   // an inner trampoline pool, as this policy is expected to be the
154   // implementation of the base case.
GetNextTrampoline()155   Maybe<Trampoline<MMPolicy>> GetNextTrampoline() {
156     uint32_t offset = mNextChunkIndex * kChunkSize;
157     if (!this->MaybeCommitNextPage(offset, kChunkSize)) {
158       return Nothing();
159     }
160 
161     Trampoline<MMPolicy> result(this, this->GetLocalView() + offset,
162                                 this->GetRemoteView() + offset, kChunkSize);
163     if (!!result) {
164       ++mNextChunkIndex;
165     }
166 
167     return Some(std::move(result));
168   }
169 
170  private:
171   uint32_t mNextChunkIndex;
172   static const uint32_t kChunkSize = 128;
173 
174   template <typename VMPolicyT, typename FriendT>
175   friend class TrampolinePool;
176 };
177 
178 }  // namespace interceptor
179 }  // namespace mozilla
180 
181 // We don't include RangeMap.h until this point because it depends on the
182 // TrampolinePool definitions from above.
183 #include "mozilla/interceptor/RangeMap.h"
184 
185 namespace mozilla {
186 namespace interceptor {
187 
188 template <typename MMPolicy, bool Dummy>
189 class VMSharingPolicyShared;
190 
191 // We only support this policy for in-proc MMPolicy.
192 // Dummy is not actually needed for the implementation, but we need this
193 // to be a partial specialization so that its statics are treated as inlines
194 // (At least until we have C++17 enabled by default)
195 template <bool Dummy>
196 class MOZ_TRIVIAL_CTOR_DTOR VMSharingPolicyShared<MMPolicyInProcess, Dummy>
197     : public MMPolicyInProcess {
198   typedef VMSharingPolicyUnique<MMPolicyInProcess> UniquePolicyT;
199   typedef VMSharingPolicyShared<MMPolicyInProcess, Dummy> ThisType;
200 
201  public:
202   using PoolType = TrampolinePool<ThisType, UniquePolicyT::PoolType>;
203   using MMPolicyT = MMPolicyInProcess;
204 
VMSharingPolicyShared()205   constexpr VMSharingPolicyShared() {}
206 
ShouldUnhookUponDestruction()207   bool ShouldUnhookUponDestruction() const { return false; }
208 
Reserve(const uintptr_t aPivotAddr,const uint32_t aMaxDistanceFromPivot)209   Maybe<PoolType> Reserve(const uintptr_t aPivotAddr,
210                           const uint32_t aMaxDistanceFromPivot) {
211     // Win32 allocates VM addresses at a 64KiB granularity, so we might as well
212     // utilize that entire 64KiB reservation.
213     uint32_t len = this->GetAllocGranularity();
214 
215     Maybe<Span<const uint8_t>> maybeBounds =
216         MMPolicyInProcess::SpanFromPivotAndDistance(len, aPivotAddr,
217                                                     aMaxDistanceFromPivot);
218 
219     AutoCriticalSection lock(GetCS());
220     VMSharingPolicyUnique<MMPolicyT>* uniquePol = sVMMap.GetPolicy(maybeBounds);
221     MOZ_ASSERT(uniquePol);
222     if (!uniquePol) {
223       return Nothing();
224     }
225 
226     Maybe<UniquePolicyT::PoolType> maybeUnique =
227         uniquePol->Reserve(len, maybeBounds);
228     if (!maybeUnique) {
229       return Nothing();
230     }
231 
232     return Some(PoolType(*this, std::move(maybeUnique.ref())));
233   }
234 
Items()235   TrampolineCollection<MMPolicyInProcess> Items() const {
236     // Since ShouldUnhookUponDestruction returns false, this can be empty
237     return TrampolineCollection<MMPolicyInProcess>(*this);
238   }
239 
Clear()240   void Clear() {
241     // This must be a no-op for shared VM policy; we can't have one interceptor
242     // wiping out trampolines for all interceptors in the process.
243   }
244 
245   VMSharingPolicyShared(const VMSharingPolicyShared&) = delete;
246   VMSharingPolicyShared(VMSharingPolicyShared&&) = delete;
247   VMSharingPolicyShared& operator=(const VMSharingPolicyShared&) = delete;
248   VMSharingPolicyShared& operator=(VMSharingPolicyShared&&) = delete;
249 
250  private:
GetCS()251   static CRITICAL_SECTION* GetCS() {
252     static const bool isAlloc = []() -> bool {
253       DWORD flags = 0;
254 #if defined(RELEASE_OR_BETA)
255       flags |= CRITICAL_SECTION_NO_DEBUG_INFO;
256 #endif  // defined(RELEASE_OR_BETA)
257       ::InitializeCriticalSectionEx(&sCS, 4000, flags);
258       return true;
259     }();
260     Unused << isAlloc;
261 
262     return &sCS;
263   }
264 
265   // In VMSharingPolicyShared, we only implement the overload that accepts
266   // a VMSharingPolicyUnique trampoline pool as |aInner|, since we require the
267   // former policy to wrap the latter.
GetNextTrampoline(UniquePolicyT::PoolType & aInner)268   Maybe<Trampoline<MMPolicyInProcess>> GetNextTrampoline(
269       UniquePolicyT::PoolType& aInner) {
270     AutoCriticalSection lock(GetCS());
271     return aInner.GetNextTrampoline();
272   }
273 
274 #if defined(_M_X64)
IsTrampolineSpaceInLowest2GB(const UniquePolicyT::PoolType & aInner)275   bool IsTrampolineSpaceInLowest2GB(
276       const UniquePolicyT::PoolType& aInner) const {
277     AutoCriticalSection lock(GetCS());
278     return aInner.IsInLowest2GB();
279   }
280 #endif  // defined(_M_X64)
281 
282  private:
283   template <typename VMPolicyT, typename InnerT>
284   friend class TrampolinePool;
285 
286   static RangeMap<MMPolicyInProcess> sVMMap;
287   static CRITICAL_SECTION sCS;
288 };
289 
290 template <bool Dummy>
291 RangeMap<MMPolicyInProcess>
292     VMSharingPolicyShared<MMPolicyInProcess, Dummy>::sVMMap;
293 
294 template <bool Dummy>
295 CRITICAL_SECTION VMSharingPolicyShared<MMPolicyInProcess, Dummy>::sCS;
296 
297 }  // namespace interceptor
298 }  // namespace mozilla
299 
300 #endif  // mozilla_interceptor_VMSharingPolicies_h
301