1 #pragma once
2 // IWYU pragma: private, include "rlbox.hpp"
3 // IWYU pragma: friend "rlbox_.*\.hpp"
4 
5 #include <type_traits>
6 #include <utility>
7 
8 #include "rlbox_helpers.hpp"
9 #include "rlbox_struct_support.hpp"
10 #include "rlbox_types.hpp"
11 
12 namespace rlbox {
13 
14 namespace callback_detail {
15 
16   // Compute the expected type of the callback
17   template<typename T_Sbx, typename T_Ret, typename... T_Args>
18   using T_Cb =
19     std::conditional_t<std::is_void_v<T_Ret>, void, tainted<T_Ret, T_Sbx>> (*)(
20       rlbox_sandbox<T_Sbx>&,
21       tainted<T_Args, T_Sbx>...);
22 
23   template<typename T_Sbx, typename T_Ret, typename... T_Args>
24   T_Cb<T_Sbx, T_Ret, T_Args...> callback_type_helper(T_Ret (*)(T_Args...));
25 
26   // Compute the expected type of the interceptor
27   template<typename T_Sbx, typename T_Ret, typename... T_Args>
28   using T_I = detail::convert_to_sandbox_equivalent_t<T_Ret, T_Sbx> (*)(
29     detail::convert_to_sandbox_equivalent_t<T_Args, T_Sbx>...);
30 
31   template<typename T_Sbx, typename T_Ret, typename... T_Args>
32   T_I<T_Sbx, T_Ret, T_Args...> interceptor_type_helper(T_Ret (*)(T_Args...));
33 }
34 
35 template<typename T, typename T_Sbx>
36 class sandbox_callback
37 {
38   KEEP_CLASSES_FRIENDLY
39 
40 private:
41   rlbox_sandbox<T_Sbx>* sandbox;
42 
43   using T_Callback =
44     decltype(callback_detail::callback_type_helper<T_Sbx>(std::declval<T>()));
45   T_Callback callback;
46 
47   // The interceptor is the function that runs between the sandbox invoking the
48   // callback and the actual callback running The interceptor is responsible for
49   // wrapping and converting callback arguments, returns etc. to their
50   // appropriate representations
51   using T_Interceptor =
52     decltype(callback_detail::interceptor_type_helper<T_Sbx>(
53       std::declval<T>()));
54   T_Interceptor callback_interceptor;
55 
56   // The trampoline is the internal sandbox representation of the callback
57   // Depending on the sandbox type, this could be the callback pointer directly
58   // or a trampoline function that gates exits from the sandbox.
59   using T_Trampoline = detail::convert_to_sandbox_equivalent_t<T, T_Sbx>;
60   T_Trampoline callback_trampoline;
61 
62   // The unique key representing the callback to pass to unregister_callback on
63   // destruction
64   void* key;
65 
move_obj(sandbox_callback && other)66   inline void move_obj(sandbox_callback&& other)
67   {
68     sandbox = other.sandbox;
69     callback = other.callback;
70     callback_interceptor = other.callback_interceptor;
71     callback_trampoline = other.callback_trampoline;
72     key = other.key;
73     other.sandbox = nullptr;
74     other.callback = nullptr;
75     other.callback_interceptor = nullptr;
76     other.callback_trampoline = 0;
77     other.key = nullptr;
78   }
79 
80   template<typename T_Ret, typename... T_Args>
unregister_helper(T_Ret (*)(T_Args...))81   inline void unregister_helper(T_Ret (*)(T_Args...))
82   {
83     if (callback != nullptr) {
84       // Don't need to worry about race between unregister and move as
85       // 1) this will not happen in a correctly written program
86       // 2) if this does happen, the worst that can happen is an invocation of a
87       // null function pointer, which causes a crash that cannot be exploited
88       // for RCE
89       sandbox->template unregister_callback<T_Ret, T_Args...>(key);
90       sandbox = nullptr;
91       callback = nullptr;
92       callback_interceptor = nullptr;
93       callback_trampoline = 0;
94       key = nullptr;
95     }
96   }
97 
get_raw_value() const98   inline T_Callback get_raw_value() const noexcept { return callback; }
get_raw_sandbox_value() const99   inline T_Trampoline get_raw_sandbox_value() const noexcept
100   {
101     return callback_trampoline;
102   }
get_raw_value()103   inline T_Callback get_raw_value() noexcept { return callback; }
get_raw_sandbox_value()104   inline T_Trampoline get_raw_sandbox_value() noexcept
105   {
106     return callback_trampoline;
107   }
108 
109   // Keep constructor private as only rlbox_sandbox should be able to create
110   // this object
sandbox_callback(rlbox_sandbox<T_Sbx> * p_sandbox,T_Callback p_callback,T_Interceptor p_callback_interceptor,T_Trampoline p_callback_trampoline,void * p_key)111   sandbox_callback(rlbox_sandbox<T_Sbx>* p_sandbox,
112                    T_Callback p_callback,
113                    T_Interceptor p_callback_interceptor,
114                    T_Trampoline p_callback_trampoline,
115                    void* p_key)
116     : sandbox(p_sandbox)
117     , callback(p_callback)
118     , callback_interceptor(p_callback_interceptor)
119     , callback_trampoline(p_callback_trampoline)
120     , key(p_key)
121   {
122     detail::dynamic_check(sandbox != nullptr,
123                           "Unexpected null sandbox when creating a callback");
124   }
125 
126 public:
sandbox_callback()127   sandbox_callback()
128     : sandbox(nullptr)
129     , callback(nullptr)
130     , callback_interceptor(nullptr)
131     , callback_trampoline(0)
132     , key(nullptr)
133   {}
134 
sandbox_callback(sandbox_callback && other)135   sandbox_callback(sandbox_callback&& other)
136   {
137     move_obj(std::forward<sandbox_callback>(other));
138   }
139 
operator =(sandbox_callback && other)140   inline sandbox_callback& operator=(sandbox_callback&& other)
141   {
142     if (this != &other) {
143       move_obj(std::forward<sandbox_callback>(other));
144     }
145     return *this;
146   }
147 
unregister()148   void unregister()
149   {
150     T dummy = nullptr;
151     unregister_helper(dummy);
152   }
153 
~sandbox_callback()154   ~sandbox_callback() { unregister(); }
155 
156   /**
157    * @brief Check if callback is _not_ registered.
158    */
is_unregistered() const159   inline bool is_unregistered() const noexcept
160   {
161     return get_raw_value() == nullptr;
162   }
163 
164   /**
165    * @brief Unwrap a callback without verification. This is an unsafe operation
166    * and should be used with care.
167    */
UNSAFE_unverified() const168   inline auto UNSAFE_unverified() const noexcept { return get_raw_value(); }
169   /**
170    * @brief Like UNSAFE_unverified, but get the underlying sandbox
171    * representation.
172    *
173    * @param sandbox Reference to sandbox.
174    */
UNSAFE_sandboxed(rlbox_sandbox<T_Sbx> & sandbox) const175   inline auto UNSAFE_sandboxed(rlbox_sandbox<T_Sbx>& sandbox) const noexcept
176   {
177     RLBOX_UNUSED(sandbox);
178     return get_raw_sandbox_value();
179   }
UNSAFE_unverified()180   inline auto UNSAFE_unverified() noexcept { return get_raw_value(); }
UNSAFE_sandboxed(rlbox_sandbox<T_Sbx> & sandbox)181   inline auto UNSAFE_sandboxed(rlbox_sandbox<T_Sbx>& sandbox) noexcept
182   {
183     RLBOX_UNUSED(sandbox);
184     return get_raw_sandbox_value();
185   }
186 };
187 
188 template<typename T, typename T_Sbx>
189 class app_pointer
190 {
191   KEEP_CLASSES_FRIENDLY
192 
193 private:
194   app_pointer_map<typename T_Sbx::T_PointerType>* map;
195   typename T_Sbx::T_PointerType idx;
196   T idx_unsandboxed;
197 
move_obj(app_pointer && other)198   inline void move_obj(app_pointer&& other)
199   {
200     map = other.map;
201     idx = other.idx;
202     idx_unsandboxed = other.idx_unsandboxed;
203     other.map = nullptr;
204     other.idx = 0;
205     other.idx_unsandboxed = nullptr;
206   }
207 
get_raw_value() const208   inline T get_raw_value() const noexcept
209   {
210     return to_tainted().get_raw_value();
211   }
get_raw_sandbox_value() const212   inline typename T_Sbx::T_PointerType get_raw_sandbox_value() const noexcept
213   {
214     return idx;
215   }
get_raw_value()216   inline T get_raw_value() noexcept { return to_tainted().get_raw_value(); }
get_raw_sandbox_value()217   inline typename T_Sbx::T_PointerType get_raw_sandbox_value() noexcept
218   {
219     return idx;
220   }
221 
app_pointer(app_pointer_map<typename T_Sbx::T_PointerType> * a_map,typename T_Sbx::T_PointerType a_idx,T a_idx_unsandboxed)222   app_pointer(app_pointer_map<typename T_Sbx::T_PointerType>* a_map,
223               typename T_Sbx::T_PointerType a_idx,
224               T a_idx_unsandboxed)
225     : map(a_map)
226     , idx(a_idx)
227     , idx_unsandboxed(a_idx_unsandboxed)
228   {}
229 
230 public:
app_pointer()231   app_pointer()
232     : map(nullptr)
233     , idx(0)
234     , idx_unsandboxed(0)
235   {}
236 
~app_pointer()237   ~app_pointer() { unregister(); }
238 
app_pointer(app_pointer && other)239   app_pointer(app_pointer&& other)
240   {
241     move_obj(std::forward<app_pointer>(other));
242   }
243 
operator =(app_pointer && other)244   inline app_pointer& operator=(app_pointer&& other)
245   {
246     if (this != &other) {
247       move_obj(std::forward<app_pointer>(other));
248     }
249     return *this;
250   }
251 
unregister()252   void unregister()
253   {
254     if (idx != 0) {
255       map->remove_app_ptr(idx);
256       map = nullptr;
257       idx = 0;
258       idx_unsandboxed = nullptr;
259     }
260   }
261 
to_tainted()262   tainted<T, T_Sbx> to_tainted()
263   {
264     return tainted<T, T_Sbx>::internal_factory(
265       reinterpret_cast<T>(idx_unsandboxed));
266   }
267 
268   /**
269    * @brief Check if app pointer is _not_ registered.
270    */
is_unregistered() const271   inline bool is_unregistered() const noexcept { return idx == 0; }
272 
273   /**
274    * @brief Unwrap app_pointer without verification. This is an unsafe operation
275    * and should be used with care.
276    */
UNSAFE_unverified() const277   inline auto UNSAFE_unverified() const noexcept { return get_raw_value(); }
278   /**
279    * @brief Like UNSAFE_unverified, but get the underlying sandbox
280    * representation.
281    *
282    * @param sandbox Reference to sandbox.
283    */
UNSAFE_sandboxed(rlbox_sandbox<T_Sbx> & sandbox) const284   inline auto UNSAFE_sandboxed(rlbox_sandbox<T_Sbx>& sandbox) const noexcept
285   {
286     RLBOX_UNUSED(sandbox);
287     return get_raw_sandbox_value();
288   }
UNSAFE_unverified()289   inline auto UNSAFE_unverified() noexcept { return get_raw_value(); }
UNSAFE_sandboxed(rlbox_sandbox<T_Sbx> & sandbox)290   inline auto UNSAFE_sandboxed(rlbox_sandbox<T_Sbx>& sandbox) noexcept
291   {
292     RLBOX_UNUSED(sandbox);
293     return get_raw_sandbox_value();
294   }
295 };
296 
297 /**
298  * @brief Tainted boolean value that serves as a "hint" and not a definite
299  * answer.  Comparisons with a tainted_volatile return such hints.  They are
300  * not `tainted<bool>` values because a compromised sandbox can modify
301  * tainted_volatile data at any time.
302  */
303 class tainted_boolean_hint
304 {
305 private:
306   bool val;
307 
308 public:
tainted_boolean_hint(bool init)309   tainted_boolean_hint(bool init)
310     : val(init)
311   {}
312   tainted_boolean_hint(const tainted_boolean_hint&) = default;
operator =(bool rhs)313   inline tainted_boolean_hint& operator=(bool rhs)
314   {
315     val = rhs;
316     return *this;
317   }
operator !()318   inline tainted_boolean_hint operator!() { return tainted_boolean_hint(!val); }
319   template<size_t N>
unverified_safe_because(const char (& reason)[N]) const320   inline bool unverified_safe_because(const char (&reason)[N]) const
321   {
322     (void)reason; /* unused */
323     return val;
324   }
UNSAFE_unverified() const325   inline bool UNSAFE_unverified() const { return val; }
UNSAFE_unverified()326   inline bool UNSAFE_unverified() { return val; }
INTERNAL_unverified_safe()327   inline auto INTERNAL_unverified_safe() { return UNSAFE_unverified(); }
INTERNAL_unverified_safe() const328   inline auto INTERNAL_unverified_safe() const { return UNSAFE_unverified(); }
329 
330   // Add a template parameter to make sure the assert only fires when called
331   template<typename T = void>
copy_and_verify(...) const332   inline bool copy_and_verify(...) const
333   {
334     rlbox_detail_static_fail_because(
335       detail::true_v<T>,
336       "You can't call copy_and_verify on this value, as this is a result of a "
337       "comparison with memory accessible by the sandbox. \n"
338       "The sandbox could unexpectedly change the value leading to "
339       "time-of-check-time-of-use attacks. \n"
340       "You can avoid this by making a local copy of the data."
341       "For example, if your original code, looked like \n"
342       "if ((tainted_ptr->member == 5).copy_and_verify(...)) { ... } \n\n"
343       "Change this to \n\n"
344       "tainted<int> val = tainted_ptr->member\n"
345       "if ((val == 5).copy_and_verify(...)) { ... } \n\n"
346       "tainted<int, T_Sbx> foo(rlbox_sandbox<T_Sbx>& sandbox) {...} \n\n"
347       "Alternately, if you are sure your code is safe you can use the "
348       "unverified_safe_because API to remove tainting\n");
349 
350     // this is never executed, but we need it for the function to type-check
351     return false;
352   }
353 };
354 
355 /**
356  * @brief Tainted integer value that serves as a "hint" and not a definite
357  * answer.  Comparisons with a tainted_volatile return such hints.  They are
358  * not `tainted<int>` values because a compromised sandbox can modify
359  * tainted_volatile data at any time.
360  */
361 class tainted_int_hint
362 {
363 private:
364   int val;
365 
366 public:
tainted_int_hint(int init)367   tainted_int_hint(int init)
368     : val(init)
369   {}
370   tainted_int_hint(const tainted_int_hint&) = default;
operator =(int rhs)371   inline tainted_int_hint& operator=(int rhs)
372   {
373     val = rhs;
374     return *this;
375   }
operator !()376   inline tainted_boolean_hint operator!() { return tainted_boolean_hint(!val); }
377   template<size_t N>
unverified_safe_because(const char (& reason)[N]) const378   inline int unverified_safe_because(const char (&reason)[N]) const
379   {
380     (void)reason; /* unused */
381     return val;
382   }
UNSAFE_unverified() const383   inline int UNSAFE_unverified() const { return val; }
UNSAFE_unverified()384   inline int UNSAFE_unverified() { return val; }
INTERNAL_unverified_safe()385   inline auto INTERNAL_unverified_safe() { return UNSAFE_unverified(); }
INTERNAL_unverified_safe() const386   inline auto INTERNAL_unverified_safe() const { return UNSAFE_unverified(); }
387 
388   // Add a template parameter to make sure the assert only fires when called
389   template<typename T = void>
copy_and_verify(...) const390   inline int copy_and_verify(...) const
391   {
392     rlbox_detail_static_fail_because(
393       detail::true_v<T>,
394       "You can't call copy_and_verify on this value, as this is a result of a "
395       "comparison with memory accessible by the sandbox. \n"
396       "The sandbox could unexpectedly change the value leading to "
397       "time-of-check-time-of-use attacks. \n"
398       "You can avoid this by making a local copy of the data."
399       "For example, if your original code, looked like \n"
400       "if ((tainted_ptr->member == 5).copy_and_verify(...)) { ... } \n\n"
401       "Change this to \n\n"
402       "tainted<int> val = tainted_ptr->member\n"
403       "if ((val == 5).copy_and_verify(...)) { ... } \n\n"
404       "tainted<int, T_Sbx> foo(rlbox_sandbox<T_Sbx>& sandbox) {...} \n\n"
405       "Alternately, if you are sure your code is safe you can use the "
406       "unverified_safe_because API to remove tainting\n");
407 
408     // this is never executed, but we need it for the function to type-check
409     return 0;
410   }
411 };
412 
413 }
414