Bug 1850713: remove duplicated setting of early hint preloader id in `ScriptLoader...
[gecko.git] / third_party / rlbox / include / rlbox_sandbox.hpp
blob63995c89adf2e54af88dfd3b2684f585888e5f95
1 #pragma once
2 // IWYU pragma: private, include "rlbox.hpp"
3 // IWYU pragma: friend "rlbox_.*\.hpp"
5 #include <algorithm>
6 #include <atomic>
7 #ifdef RLBOX_MEASURE_TRANSITION_TIMES
8 # include <chrono>
9 #endif
10 #include <cstdlib>
11 #include <limits>
12 #include <map>
13 #include <mutex>
14 #ifndef RLBOX_USE_CUSTOM_SHARED_LOCK
15 # include <shared_mutex>
16 #endif
17 #ifdef RLBOX_MEASURE_TRANSITION_TIMES
18 # include <sstream>
19 # include <string>
20 #endif
21 #include <stdint.h>
22 #include <type_traits>
23 #include <utility>
24 #include <vector>
26 #include "rlbox_conversion.hpp"
27 #include "rlbox_helpers.hpp"
28 #include "rlbox_stdlib_polyfill.hpp"
29 #include "rlbox_struct_support.hpp"
30 #include "rlbox_type_traits.hpp"
31 #include "rlbox_wrapper_traits.hpp"
33 #ifdef RLBOX_MEASURE_TRANSITION_TIMES
34 using namespace std::chrono;
35 #endif
37 namespace rlbox {
39 namespace convert_fn_ptr_to_sandbox_equivalent_detail {
40 template<typename T, typename T_Sbx>
41 using conv = ::rlbox::detail::convert_to_sandbox_equivalent_t<T, T_Sbx>;
43 template<typename T_Ret, typename... T_Args>
44 using T_Func = T_Ret (*)(T_Args...);
46 template<typename T_Sbx, typename T_Ret, typename... T_Args>
47 T_Func<conv<T_Ret, T_Sbx>, conv<T_Args, T_Sbx>...> helper(
48 T_Ret (*)(T_Args...));
51 #if defined(RLBOX_MEASURE_TRANSITION_TIMES) || \
52 defined(RLBOX_TRANSITION_ACTION_OUT) || defined(RLBOX_TRANSITION_ACTION_IN)
53 enum class rlbox_transition
55 INVOKE,
56 CALLBACK
58 #endif
59 #ifdef RLBOX_MEASURE_TRANSITION_TIMES
60 struct rlbox_transition_timing
62 rlbox_transition invoke;
63 const char* name;
64 void* ptr;
65 int64_t time;
67 std::string to_string()
69 std::ostringstream ret;
70 if (invoke == rlbox_transition::INVOKE) {
71 ret << name;
72 } else {
73 ret << "Callback " << ptr;
75 ret << " : " << time << "\n";
77 return ret.str();
80 #endif
82 #ifndef RLBOX_SINGLE_THREADED_INVOCATIONS
83 # error \
84 "RLBox does not yet support threading. Please define RLBOX_SINGLE_THREADED_INVOCATIONS prior to including RLBox and ensure you are only using it from a single thread. If threading is required, please file a bug."
85 #endif
87 /**
88 * @brief Encapsulation for sandboxes.
90 * @tparam T_Sbx Type of sandbox. For the null sandbox this is
91 * `rlbox_noop_sandbox`
93 template<typename T_Sbx>
94 class rlbox_sandbox : protected T_Sbx
96 KEEP_CLASSES_FRIENDLY
98 private:
99 #ifdef RLBOX_MEASURE_TRANSITION_TIMES
100 std::vector<rlbox_transition_timing> transition_times;
101 #endif
103 static inline RLBOX_SHARED_LOCK(sandbox_list_lock);
104 // The actual type of the vector is std::vector<rlbox_sandbox<T_Sbx>*>
105 // However clang 5, 6 have bugs where compilation seg-faults on this type
106 // So we just use this std::vector<void*>
107 static inline std::vector<void*> sandbox_list;
109 RLBOX_SHARED_LOCK(func_ptr_cache_lock);
110 std::map<std::string, void*> func_ptr_map;
112 app_pointer_map<typename T_Sbx::T_PointerType> app_ptr_map;
114 // This variable tracks of the sandbox has already been created/destroyed.
115 // APIs in this class should be called only when the sandbox is created.
116 // However, it is expensive to check in APIs such as invoke or in the callback
117 // interceptor. What's more, there could be time of check time of use issues
118 // in the checks as well.
119 // In general, we leave it up to the user to ensure these APIs are never
120 // called prior to sandbox construction or after destruction. We perform some
121 // conservative sanity checks, where they would not add too much overhead.
122 enum class Sandbox_Status
124 NOT_CREATED,
125 INITIALIZING,
126 CREATED,
127 CLEANING_UP
129 std::atomic<Sandbox_Status> sandbox_created = Sandbox_Status::NOT_CREATED;
131 std::mutex callback_lock;
132 std::vector<void*> callback_keys;
134 void* transition_state = nullptr;
136 template<typename T>
137 using convert_fn_ptr_to_sandbox_equivalent_t = decltype(
138 ::rlbox::convert_fn_ptr_to_sandbox_equivalent_detail::helper<T_Sbx>(
139 std::declval<T>()));
141 template<typename T>
142 inline constexpr void check_invoke_param_type_is_ok()
144 using T_NoRef = std::remove_reference_t<T>;
146 if_constexpr_named(cond1, detail::rlbox_is_wrapper_v<T_NoRef>)
148 if_constexpr_named(
149 subcond1,
150 !std::is_same_v<T_Sbx, detail::rlbox_get_wrapper_sandbox_t<T_NoRef>>)
152 rlbox_detail_static_fail_because(
153 cond1 && subcond1,
154 "Mixing tainted data from a different sandbox types. This could "
155 "happen due to couple of different reasons.\n"
156 "1. You are using 2 sandbox types for example'rlbox_noop_sandbox' "
157 "and 'rlbox_lucet_sandbox', and are passing tainted data from one "
158 "sandbox as parameters into a function call to the other sandbox. "
159 "This is not allowed, unwrap the tainted data with copy_and_verify "
160 "or other unwrapping APIs first.\n"
161 "2. You have inadvertantly forgotten to set/remove "
162 "RLBOX_USE_STATIC_CALLS depending on the sandbox type. Some sandbox "
163 "types like rlbox_noop_sandbox require this to be set to a given "
164 "value, while other types like rlbox_lucet_sandbox, require this not "
165 "to be set.");
168 else if_constexpr_named(cond2,
169 std::is_null_pointer_v<T_NoRef> ||
170 detail::is_fundamental_or_enum_v<T_NoRef>)
172 else
174 constexpr auto unknownCase = !(cond1 || cond2);
175 rlbox_detail_static_fail_because(
176 unknownCase,
177 "Arguments to a sandbox function call should be primitives or wrapped "
178 "types like tainted, callbacks etc.");
182 template<typename T>
183 inline auto invoke_process_param(T&& param)
185 check_invoke_param_type_is_ok<T>();
187 using T_NoRef = std::remove_reference_t<T>;
189 if constexpr (detail::rlbox_is_tainted_opaque_v<T_NoRef>) {
190 auto ret = from_opaque(param);
191 return ret.UNSAFE_sandboxed(*this);
192 } else if constexpr (detail::rlbox_is_wrapper_v<T_NoRef>) {
193 return param.UNSAFE_sandboxed(*this);
194 } else if constexpr (std::is_null_pointer_v<T_NoRef>) {
195 tainted<void*, T_Sbx> ret = nullptr;
196 return ret.UNSAFE_sandboxed(*this);
197 } else if constexpr (detail::is_fundamental_or_enum_v<T_NoRef>) {
198 // For unwrapped primitives, assign to a tainted var and then unwrap so
199 // that we adjust for machine model
200 tainted<T_NoRef, T_Sbx> ret = param;
201 return ret.UNSAFE_sandboxed(*this);
202 } else {
203 rlbox_detail_static_fail_because(
204 detail::true_v<T_NoRef>,
205 "Only tainted types, callbacks or primitive values such as ints can be "
206 "passed as parameters.\n"
207 "To make a parameter tainted, try moving the allocation into the "
208 "sandbox.\n"
209 "If the parameter is a callback, try registering the callback via the "
210 "register_callback API.");
214 template<typename T, typename T_Arg>
215 inline tainted<T, T_Sbx> sandbox_callback_intercept_convert_param(
216 rlbox_sandbox<T_Sbx>& sandbox,
217 const T_Arg& arg)
219 tainted<T, T_Sbx> ret;
220 using namespace detail;
221 convert_type<T_Sbx,
222 adjust_type_direction::TO_APPLICATION,
223 adjust_type_context::SANDBOX>(
224 ret.get_raw_value_ref(),
225 arg,
226 nullptr /* example_unsandboxed_ptr */,
227 &sandbox);
228 return ret;
231 template<typename T_Ret, typename... T_Args>
232 static detail::convert_to_sandbox_equivalent_t<T_Ret, T_Sbx>
233 sandbox_callback_interceptor(
234 detail::convert_to_sandbox_equivalent_t<T_Args, T_Sbx>... args)
236 std::pair<T_Sbx*, void*> context =
237 T_Sbx::impl_get_executed_callback_sandbox_and_key();
238 auto& sandbox = *(reinterpret_cast<rlbox_sandbox<T_Sbx>*>(context.first));
239 auto key = context.second;
241 using T_Func_Ret =
242 std::conditional_t<std::is_void_v<T_Ret>, void, tainted<T_Ret, T_Sbx>>;
243 using T_Func =
244 T_Func_Ret (*)(rlbox_sandbox<T_Sbx>&, tainted<T_Args, T_Sbx>...);
245 auto target_fn_ptr = reinterpret_cast<T_Func>(key);
247 #ifdef RLBOX_MEASURE_TRANSITION_TIMES
248 high_resolution_clock::time_point enter_time = high_resolution_clock::now();
249 auto on_exit = rlbox::detail::make_scope_exit([&] {
250 auto exit_time = high_resolution_clock::now();
251 int64_t ns = duration_cast<nanoseconds>(exit_time - enter_time).count();
252 sandbox.transition_times.push_back(
253 rlbox_transition_timing{ rlbox_transition::CALLBACK,
254 nullptr /* func_name */,
255 key /* func_ptr */,
256 ns });
258 #endif
259 #ifdef RLBOX_TRANSITION_ACTION_OUT
260 RLBOX_TRANSITION_ACTION_OUT(rlbox_transition::CALLBACK,
261 nullptr /* func_name */,
262 key /* func_ptr */,
263 sandbox.transition_state);
264 #endif
265 #ifdef RLBOX_TRANSITION_ACTION_IN
266 auto on_exit_transition = rlbox::detail::make_scope_exit([&] {
267 RLBOX_TRANSITION_ACTION_IN(rlbox_transition::CALLBACK,
268 nullptr /* func_name */,
269 key /* func_ptr */,
270 sandbox.transition_state);
272 #endif
273 if constexpr (std::is_void_v<T_Func_Ret>) {
274 (*target_fn_ptr)(
275 sandbox,
276 sandbox.template sandbox_callback_intercept_convert_param<T_Args>(
277 sandbox, args)...);
278 return;
279 } else {
280 auto tainted_ret = (*target_fn_ptr)(
281 sandbox,
282 sandbox.template sandbox_callback_intercept_convert_param<T_Args>(
283 sandbox, args)...);
285 using namespace detail;
286 convert_to_sandbox_equivalent_t<T_Ret, T_Sbx> ret;
287 convert_type<T_Sbx,
288 adjust_type_direction::TO_SANDBOX,
289 adjust_type_context::SANDBOX>(
290 ret,
291 tainted_ret.get_raw_value_ref(),
292 nullptr /* example_unsandboxed_ptr */,
293 &sandbox);
294 return ret;
299 * @brief Unregister a callback function and disallow the sandbox from
300 * calling this function henceforth.
302 template<typename T_Ret, typename... T_Args>
303 inline void unregister_callback(void* key)
305 // Silently swallowing the failure is better here as RAII types may try to
306 // cleanup callbacks after sandbox destruction
307 if (sandbox_created.load() != Sandbox_Status::CREATED) {
308 return;
311 this->template impl_unregister_callback<
312 detail::convert_to_sandbox_equivalent_t<T_Ret, T_Sbx>,
313 detail::convert_to_sandbox_equivalent_t<T_Args, T_Sbx>...>(key);
315 std::lock_guard<std::mutex> lock(callback_lock);
316 auto el_ref = std::find(callback_keys.begin(), callback_keys.end(), key);
317 detail::dynamic_check(
318 el_ref != callback_keys.end(),
319 "Unexpected state. Unregistering a callback that was never registered.");
320 callback_keys.erase(el_ref);
323 static T_Sbx* find_sandbox_from_example(const void* example_sandbox_ptr)
325 detail::dynamic_check(
326 example_sandbox_ptr != nullptr,
327 "Internal error: received a null example pointer. Please file a bug.");
329 RLBOX_ACQUIRE_SHARED_GUARD(lock, sandbox_list_lock);
330 for (auto sandbox_v : sandbox_list) {
331 auto sandbox = reinterpret_cast<rlbox_sandbox<T_Sbx>*>(sandbox_v);
332 if (sandbox->is_pointer_in_sandbox_memory(example_sandbox_ptr)) {
333 return sandbox;
337 return nullptr;
340 template<typename... T_Args>
341 static auto impl_create_sandbox_helper(rlbox_sandbox<T_Sbx>* this_ptr,
342 T_Args... args)
344 return this_ptr->impl_create_sandbox(std::forward<T_Args>(args)...);
347 public:
349 * @brief Unused member that allows the calling code to save data in a
350 * "per-sandbox" storage. This can be useful to save context which is used
351 * in callbacks.
353 void* sandbox_storage;
355 /***** Function to adjust for custom machine models *****/
357 template<typename T>
358 using convert_to_sandbox_equivalent_nonclass_t =
359 detail::convert_base_types_t<T,
360 typename T_Sbx::T_ShortType,
361 typename T_Sbx::T_IntType,
362 typename T_Sbx::T_LongType,
363 typename T_Sbx::T_LongLongType,
364 typename T_Sbx::T_PointerType>;
366 T_Sbx* get_sandbox_impl() { return this; }
369 * @brief Create a new sandbox.
371 * @tparam T_Args Arguments passed to the underlying sandbox
372 * implementation. For the null sandbox, no arguments are necessary.
374 template<typename... T_Args>
375 inline bool create_sandbox(T_Args... args)
377 #ifdef RLBOX_MEASURE_TRANSITION_TIMES
378 // Warm up the timer. The first call is always slow (at least on the test
379 // platform)
380 for (int i = 0; i < 10; i++) {
381 auto val = high_resolution_clock::now();
382 RLBOX_UNUSED(val);
384 #endif
385 auto expected = Sandbox_Status::NOT_CREATED;
386 bool success = sandbox_created.compare_exchange_strong(
387 expected, Sandbox_Status::INITIALIZING /* desired */);
388 detail::dynamic_check(
389 success,
390 "create_sandbox called when sandbox already created/is being "
391 "created concurrently");
393 using T_Result = rlbox::detail::polyfill::invoke_result_t<
394 decltype(impl_create_sandbox_helper<T_Args...>),
395 decltype(this),
396 T_Args...>;
398 bool created = true;
399 if constexpr (std::is_same_v<T_Result, void>) {
400 this->impl_create_sandbox(std::forward<T_Args>(args)...);
401 } else if constexpr (std::is_same_v<T_Result, bool>) {
402 created = this->impl_create_sandbox(std::forward<T_Args>(args)...);
403 } else {
404 rlbox_detail_static_fail_because(
405 (!std::is_same_v<T_Result, void> && !std::is_same_v<T_Result, bool>),
406 "Expected impl_create_sandbox to return void or a boolean");
409 if (created) {
410 sandbox_created.store(Sandbox_Status::CREATED);
411 RLBOX_ACQUIRE_UNIQUE_GUARD(lock, sandbox_list_lock);
412 sandbox_list.push_back(this);
415 return created;
419 * @brief Destroy sandbox and reclaim any memory.
421 inline auto destroy_sandbox()
423 auto expected = Sandbox_Status::CREATED;
424 bool success = sandbox_created.compare_exchange_strong(
425 expected, Sandbox_Status::CLEANING_UP /* desired */);
427 detail::dynamic_check(
428 success,
429 "destroy_sandbox called without sandbox creation/is being "
430 "destroyed concurrently");
433 RLBOX_ACQUIRE_UNIQUE_GUARD(lock, sandbox_list_lock);
434 auto el_ref = std::find(sandbox_list.begin(), sandbox_list.end(), this);
435 detail::dynamic_check(
436 el_ref != sandbox_list.end(),
437 "Unexpected state. Destroying a sandbox that was never initialized.");
438 sandbox_list.erase(el_ref);
441 sandbox_created.store(Sandbox_Status::NOT_CREATED);
442 return this->impl_destroy_sandbox();
445 template<typename T>
446 inline T get_unsandboxed_pointer(
447 convert_to_sandbox_equivalent_nonclass_t<T> p) const
449 static_assert(std::is_pointer_v<T>);
450 if (p == 0) {
451 return nullptr;
453 auto ret = this->template impl_get_unsandboxed_pointer<T>(p);
454 return reinterpret_cast<T>(ret);
457 template<typename T>
458 inline convert_to_sandbox_equivalent_nonclass_t<T> get_sandboxed_pointer(
459 const void* p) const
461 static_assert(std::is_pointer_v<T>);
462 if (p == nullptr) {
463 return 0;
465 return this->template impl_get_sandboxed_pointer<T>(p);
468 template<typename T>
469 static inline T get_unsandboxed_pointer_no_ctx(
470 convert_to_sandbox_equivalent_nonclass_t<T> p,
471 const void* example_unsandboxed_ptr)
473 static_assert(std::is_pointer_v<T>);
474 if (p == 0) {
475 return nullptr;
477 auto ret = T_Sbx::template impl_get_unsandboxed_pointer_no_ctx<T>(
478 p, example_unsandboxed_ptr, find_sandbox_from_example);
479 return reinterpret_cast<T>(ret);
482 template<typename T>
483 static inline convert_to_sandbox_equivalent_nonclass_t<T>
484 get_sandboxed_pointer_no_ctx(const void* p,
485 const void* example_unsandboxed_ptr)
487 static_assert(std::is_pointer_v<T>);
488 if (p == nullptr) {
489 return 0;
491 return T_Sbx::template impl_get_sandboxed_pointer_no_ctx<T>(
492 p, example_unsandboxed_ptr, find_sandbox_from_example);
496 * @brief Allocate a new pointer that is accessible to both the application
497 * and sandbox. The pointer is allocated in sandbox memory.
499 * @tparam T The type of the pointer you want to create. If T=int, this
500 * would return a pointer to an int.
502 * @return tainted<T*, T_Sbx> Tainted pointer accessible to the application
503 * and sandbox.
505 template<typename T>
506 inline tainted<T*, T_Sbx> malloc_in_sandbox()
508 const uint32_t defaultCount = 1;
509 return malloc_in_sandbox<T>(defaultCount);
513 * @brief Allocate an array that is accessible to both the application
514 * and sandbox. The pointer is allocated in sandbox memory.
516 * @tparam T The type of the array elements you want to create. If T=int, this
517 * would return a pointer to an array of ints.
519 * @param count The number of array elements to allocate.
521 * @return tainted<T*, T_Sbx> Tainted pointer accessible to the application
522 * and sandbox.
524 template<typename T>
525 inline tainted<T*, T_Sbx> malloc_in_sandbox(uint32_t count)
527 // Silently swallowing the failure is better here as RAII types may try to
528 // malloc after sandbox destruction
529 if (sandbox_created.load() != Sandbox_Status::CREATED) {
530 return tainted<T*, T_Sbx>::internal_factory(nullptr);
533 detail::dynamic_check(count != 0, "Malloc tried to allocate 0 bytes");
534 if constexpr (sizeof(T) >= std::numeric_limits<uint32_t>::max()) {
535 rlbox_detail_static_fail_because(sizeof(T) >=
536 std::numeric_limits<uint32_t>::max(),
537 "Tried to allocate an object over 4GB.");
539 auto total_size = static_cast<uint64_t>(sizeof(T)) * count;
540 if constexpr (sizeof(size_t) == 4) {
541 // On a 32-bit platform, we need to make sure that total_size is not >=4GB
542 detail::dynamic_check(total_size < std::numeric_limits<uint32_t>::max(),
543 "Tried to allocate memory over 4GB");
544 } else if constexpr (sizeof(size_t) != 8) {
545 // Double check we are on a 64-bit platform
546 // Note for static checks we need to have some dependence on T, so adding
547 // a dummy
548 constexpr bool dummy = sizeof(T) >= 0;
549 rlbox_detail_static_fail_because(dummy && sizeof(size_t) != 8,
550 "Expected 32 or 64 bit platform.");
552 auto ptr_in_sandbox = this->impl_malloc_in_sandbox(total_size);
553 auto ptr = get_unsandboxed_pointer<T*>(ptr_in_sandbox);
554 if (!ptr) {
555 return tainted<T*, T_Sbx>(nullptr);
557 detail::dynamic_check(is_pointer_in_sandbox_memory(ptr),
558 "Malloc returned pointer outside the sandbox memory");
559 auto ptr_end = reinterpret_cast<uintptr_t>(ptr + (count - 1));
560 detail::dynamic_check(
561 is_in_same_sandbox(ptr, reinterpret_cast<void*>(ptr_end)),
562 "Malloc returned a pointer whose range goes beyond sandbox memory");
563 auto cast_ptr = reinterpret_cast<T*>(ptr);
564 return tainted<T*, T_Sbx>::internal_factory(cast_ptr);
568 * @brief Free the memory referenced by the tainted pointer.
570 * @param ptr Pointer to sandbox memory to free.
572 template<typename T>
573 inline void free_in_sandbox(tainted<T*, T_Sbx> ptr)
575 // Silently swallowing the failure is better here as RAII types may try to
576 // free after sandbox destruction
577 if (sandbox_created.load() != Sandbox_Status::CREATED) {
578 return;
581 this->impl_free_in_sandbox(ptr.get_raw_sandbox_value(*this));
585 * @brief Free the memory referenced by a tainted_volatile pointer ref.
587 * @param ptr_ref Pointer reference to sandbox memory to free.
589 template<typename T>
590 inline void free_in_sandbox(tainted_volatile<T, T_Sbx>& ptr_ref)
592 tainted<T, T_Sbx> ptr = ptr_ref;
593 free_in_sandbox(ptr);
597 * @brief Free the memory referenced by a tainted_opaque pointer.
599 * @param ptr_opaque Opaque pointer to sandbox memory to free.
601 template<typename T>
602 inline void free_in_sandbox(tainted_opaque<T, T_Sbx> ptr_opaque)
604 tainted<T, T_Sbx> ptr = from_opaque(ptr_opaque);
605 free_in_sandbox(ptr);
609 * @brief Check if two pointers are in the same sandbox.
610 * For the null-sandbox, this always returns true.
612 static inline bool is_in_same_sandbox(const void* p1, const void* p2)
614 const size_t num_args =
615 detail::func_arg_nums_v<decltype(T_Sbx::impl_is_in_same_sandbox)>;
616 if constexpr (num_args == 2) {
617 return T_Sbx::impl_is_in_same_sandbox(p1, p2);
618 } else {
619 return T_Sbx::impl_is_in_same_sandbox(p1, p2, find_sandbox_from_example);
624 * @brief Check if the pointer points to this sandbox's memory.
625 * For the null-sandbox, this always returns true.
627 inline bool is_pointer_in_sandbox_memory(const void* p)
629 return this->impl_is_pointer_in_sandbox_memory(p);
633 * @brief Check if the pointer points to application memory.
634 * For the null-sandbox, this always returns true.
636 inline bool is_pointer_in_app_memory(const void* p)
638 return this->impl_is_pointer_in_app_memory(p);
641 inline size_t get_total_memory() { return this->impl_get_total_memory(); }
643 inline void* get_memory_location()
645 return this->impl_get_memory_location();
648 void* get_transition_state() { return transition_state; }
650 void set_transition_state(void* new_state) { transition_state = new_state; }
653 * @brief For internal use only.
654 * Grant access of the passed in buffer in to the sandbox instance. Called by
655 * internal APIs only if the underlying sandbox supports
656 * can_grant_deny_access by including the line
657 * ```
658 * using can_grant_deny_access = void;
659 * ```
661 template<typename T>
662 inline tainted<T*, T_Sbx> INTERNAL_grant_access(T* src,
663 size_t num,
664 bool& success)
666 auto ret = this->impl_grant_access(src, num, success);
667 return tainted<T*, T_Sbx>::internal_factory(ret);
671 * @brief For internal use only.
672 * Grant access of the passed in buffer in to the sandbox instance. Called by
673 * internal APIs only if the underlying sandbox supports
674 * can_grant_deny_access by including the line
675 * ```
676 * using can_grant_deny_access = void;
677 * ```
679 template<typename T>
680 inline T* INTERNAL_deny_access(tainted<T*, T_Sbx> src,
681 size_t num,
682 bool& success)
684 auto ret =
685 this->impl_deny_access(src.INTERNAL_unverified_safe(), num, success);
686 return ret;
689 void* lookup_symbol(const char* func_name)
692 RLBOX_ACQUIRE_SHARED_GUARD(lock, func_ptr_cache_lock);
694 auto func_ptr_ref = func_ptr_map.find(func_name);
695 if (func_ptr_ref != func_ptr_map.end()) {
696 return func_ptr_ref->second;
700 void* func_ptr = this->impl_lookup_symbol(func_name);
701 RLBOX_ACQUIRE_UNIQUE_GUARD(lock, func_ptr_cache_lock);
702 func_ptr_map[func_name] = func_ptr;
703 return func_ptr;
706 void* internal_lookup_symbol(const char* func_name)
709 RLBOX_ACQUIRE_SHARED_GUARD(lock, func_ptr_cache_lock);
711 auto func_ptr_ref = func_ptr_map.find(func_name);
712 if (func_ptr_ref != func_ptr_map.end()) {
713 return func_ptr_ref->second;
717 void* func_ptr = 0;
718 if constexpr (rlbox::detail::
719 has_member_using_needs_internal_lookup_symbol_v<T_Sbx>) {
720 func_ptr = this->impl_internal_lookup_symbol(func_name);
721 } else {
722 func_ptr = this->impl_lookup_symbol(func_name);
724 RLBOX_ACQUIRE_UNIQUE_GUARD(lock, func_ptr_cache_lock);
725 func_ptr_map[func_name] = func_ptr;
726 return func_ptr;
729 // this is an internal function invoked from macros, so it has be public
730 template<typename T, typename... T_Args>
731 inline auto INTERNAL_invoke_with_func_name(const char* func_name,
732 T_Args&&... params)
734 return INTERNAL_invoke_with_func_ptr<T, T_Args...>(
735 func_name, lookup_symbol(func_name), std::forward<T_Args>(params)...);
738 // this is an internal function invoked from macros, so it has be public
739 // Explicitly don't use inline on this, as this adds a lot of instructions
740 // prior to function call. What's more, by not inlining, different function
741 // calls with the same signature can share the same code segments for
742 // sandboxed function execution in the binary
743 template<typename T, typename... T_Args>
744 auto INTERNAL_invoke_with_func_ptr(const char* func_name,
745 void* func_ptr,
746 T_Args&&... params)
748 // unused in some paths
749 RLBOX_UNUSED(func_name);
750 #ifdef RLBOX_MEASURE_TRANSITION_TIMES
751 auto enter_time = high_resolution_clock::now();
752 auto on_exit = rlbox::detail::make_scope_exit([&] {
753 auto exit_time = high_resolution_clock::now();
754 int64_t ns = duration_cast<nanoseconds>(exit_time - enter_time).count();
755 transition_times.push_back(rlbox_transition_timing{
756 rlbox_transition::INVOKE, func_name, func_ptr, ns });
758 #endif
759 #ifdef RLBOX_TRANSITION_ACTION_IN
760 RLBOX_TRANSITION_ACTION_IN(
761 rlbox_transition::INVOKE, func_name, func_ptr, transition_state);
762 #endif
763 #ifdef RLBOX_TRANSITION_ACTION_OUT
764 auto on_exit_transition = rlbox::detail::make_scope_exit([&] {
765 RLBOX_TRANSITION_ACTION_OUT(
766 rlbox_transition::INVOKE, func_name, func_ptr, transition_state);
768 #endif
769 (check_invoke_param_type_is_ok<T_Args>(), ...);
771 static_assert(
772 rlbox::detail::polyfill::is_invocable_v<
774 detail::rlbox_remove_wrapper_t<std::remove_reference_t<T_Args>>...>,
775 "Mismatched arguments types for function");
777 using T_Result = rlbox::detail::polyfill::invoke_result_t<
779 detail::rlbox_remove_wrapper_t<std::remove_reference_t<T_Args>>...>;
781 using T_Converted =
782 std::remove_pointer_t<convert_fn_ptr_to_sandbox_equivalent_t<T*>>;
784 if constexpr (std::is_void_v<T_Result>) {
785 this->template impl_invoke_with_func_ptr<T>(
786 reinterpret_cast<T_Converted*>(func_ptr),
787 invoke_process_param(params)...);
788 return;
789 } else {
790 auto raw_result = this->template impl_invoke_with_func_ptr<T>(
791 reinterpret_cast<T_Converted*>(func_ptr),
792 invoke_process_param(params)...);
793 tainted<T_Result, T_Sbx> wrapped_result;
794 using namespace detail;
795 convert_type<T_Sbx,
796 adjust_type_direction::TO_APPLICATION,
797 adjust_type_context::SANDBOX>(
798 wrapped_result.get_raw_value_ref(),
799 raw_result,
800 nullptr /* example_unsandboxed_ptr */,
801 this /* sandbox_ptr */);
802 return wrapped_result;
806 // Useful in the porting stage to temporarily allow non tainted pointers to go
807 // through. This will only ever work in the rlbox_noop_sandbox. Any sandbox
808 // that actually enforces isolation will crash here.
809 template<typename T2>
810 tainted<T2, T_Sbx> UNSAFE_accept_pointer(T2 ptr)
812 static_assert(std::is_pointer_v<T2>,
813 "UNSAFE_accept_pointer expects a pointer param");
814 tainted<T2, T_Sbx> ret;
815 ret.assign_raw_pointer(*this, ptr);
816 return ret;
819 template<typename T_Ret, typename... T_Args>
820 using T_Cb_no_wrap = detail::rlbox_remove_wrapper_t<T_Ret>(
821 detail::rlbox_remove_wrapper_t<T_Args>...);
823 template<typename T_Ret>
824 sandbox_callback<T_Cb_no_wrap<T_Ret>*, T_Sbx> register_callback(T_Ret (*)())
826 rlbox_detail_static_fail_because(
827 detail::true_v<T_Ret>,
828 "Modify the callback to change the first parameter to a sandbox. "
829 "For instance if a callback has type\n\n"
830 "int foo() {...}\n\n"
831 "Change this to \n\n"
832 "tainted<int, T_Sbx> foo(rlbox_sandbox<T_Sbx>& sandbox) {...}\n");
834 // this is never executed, but we need it for the function to type-check
835 std::abort();
839 * @brief Expose a callback function to the sandboxed code.
841 * @param func_ptr The callback to expose.
843 * @tparam T_RL Sandbox reference type (first argument).
844 * @tparam T_Ret Return type of callback. Must be tainted or void.
845 * @tparam T_Args Types of remaining callback arguments. Must be tainted.
847 * @return Wrapped callback function pointer that can be passed to the
848 * sandbox.
850 template<typename T_RL, typename T_Ret, typename... T_Args>
851 sandbox_callback<T_Cb_no_wrap<T_Ret, T_Args...>*, T_Sbx> register_callback(
852 T_Ret (*func_ptr)(T_RL, T_Args...))
854 // Some branches don't use the param
855 RLBOX_UNUSED(func_ptr);
857 if_constexpr_named(cond1, !std::is_same_v<T_RL, rlbox_sandbox<T_Sbx>&>)
859 rlbox_detail_static_fail_because(
860 cond1,
861 "Modify the callback to change the first parameter to a sandbox. "
862 "For instance if a callback has type\n\n"
863 "int foo(int a, int b) {...}\n\n"
864 "Change this to \n\n"
865 "tainted<int, T_Sbx> foo(rlbox_sandbox<T_Sbx>& sandbox, "
866 "tainted<int, T_Sbx> a, tainted<int, T_Sbx> b) {...}\n");
868 else if_constexpr_named(
869 cond2, !(detail::rlbox_is_tainted_or_opaque_v<T_Args> && ...))
871 rlbox_detail_static_fail_because(
872 cond2,
873 "Change all arguments to the callback have to be tainted or "
874 "tainted_opaque. "
875 "For instance if a callback has type\n\n"
876 "int foo(int a, int b) {...}\n\n"
877 "Change this to \n\n"
878 "tainted<int, T_Sbx> foo(rlbox_sandbox<T_Sbx>& sandbox, "
879 "tainted<int, T_Sbx> a, tainted<int, T_Sbx> b) {...}\n");
881 else if_constexpr_named(
882 cond3, (std::is_array_v<detail::rlbox_remove_wrapper_t<T_Args>> || ...))
884 rlbox_detail_static_fail_because(
885 cond3,
886 "Change all static array arguments to the callback to be pointers. "
887 "For instance if a callback has type\n\n"
888 "int foo(int a[4]) {...}\n\n"
889 "Change this to \n\n"
890 "tainted<int, T_Sbx> foo(rlbox_sandbox<T_Sbx>& sandbox, "
891 "tainted<int*, T_Sbx> a) {...}\n");
893 else if_constexpr_named(
894 cond4,
895 !(std::is_void_v<T_Ret> || detail::rlbox_is_tainted_or_opaque_v<T_Ret>))
897 rlbox_detail_static_fail_because(
898 cond4,
899 "Change the callback return type to be tainted or tainted_opaque if it "
900 "is not void. "
901 "For instance if a callback has type\n\n"
902 "int foo(int a, int b) {...}\n\n"
903 "Change this to \n\n"
904 "tainted<int, T_Sbx> foo(rlbox_sandbox<T_Sbx>& sandbox, "
905 "tainted<int, T_Sbx> a, tainted<int, T_Sbx> b) {...}\n");
907 else
909 detail::dynamic_check(
910 sandbox_created.load() == Sandbox_Status::CREATED,
911 "register_callback called without sandbox creation");
913 // Need unique key for each callback we register - just use the func addr
914 void* unique_key = reinterpret_cast<void*>(func_ptr);
916 // Make sure that the user hasn't previously registered this function...
917 // If they have, we would returning 2 owning types (sandbox_callback) to
918 // the same callback which would be bad
920 std::lock_guard<std::mutex> lock(callback_lock);
921 bool exists =
922 std::find(callback_keys.begin(), callback_keys.end(), unique_key) !=
923 callback_keys.end();
924 detail::dynamic_check(
925 !exists, "You have previously already registered this callback.");
926 callback_keys.push_back(unique_key);
929 auto callback_interceptor =
930 sandbox_callback_interceptor<detail::rlbox_remove_wrapper_t<T_Ret>,
931 detail::rlbox_remove_wrapper_t<T_Args>...>;
933 auto callback_trampoline = this->template impl_register_callback<
934 detail::convert_to_sandbox_equivalent_t<
935 detail::rlbox_remove_wrapper_t<T_Ret>,
936 T_Sbx>,
937 detail::convert_to_sandbox_equivalent_t<
938 detail::rlbox_remove_wrapper_t<T_Args>,
939 T_Sbx>...>(unique_key, reinterpret_cast<void*>(callback_interceptor));
941 auto tainted_func_ptr = reinterpret_cast<
942 detail::rlbox_tainted_opaque_to_tainted_t<T_Ret, T_Sbx> (*)(
943 T_RL, detail::rlbox_tainted_opaque_to_tainted_t<T_Args, T_Sbx>...)>(
944 reinterpret_cast<void*>(func_ptr));
946 auto ret = sandbox_callback<T_Cb_no_wrap<T_Ret, T_Args...>*, T_Sbx>(
947 this,
948 tainted_func_ptr,
949 callback_interceptor,
950 callback_trampoline,
951 unique_key);
952 return ret;
956 // this is an internal function invoked from macros, so it has be public
957 template<typename T>
958 inline tainted<T*, T_Sbx> INTERNAL_get_sandbox_function_name(
959 const char* func_name)
961 return INTERNAL_get_sandbox_function_ptr<T>(
962 internal_lookup_symbol(func_name));
965 // this is an internal function invoked from macros, so it has be public
966 template<typename T>
967 inline tainted<T*, T_Sbx> INTERNAL_get_sandbox_function_ptr(void* func_ptr)
969 return tainted<T*, T_Sbx>::internal_factory(reinterpret_cast<T*>(func_ptr));
973 * @brief Create a "fake" pointer referring to a location in the application
974 * memory
976 * @param ptr The pointer to refer to
978 * @return The app_pointer object that refers to this location.
980 template<typename T>
981 app_pointer<T*, T_Sbx> get_app_pointer(T* ptr)
983 auto max_ptr = (typename T_Sbx::T_PointerType)(get_total_memory() - 1);
984 auto idx = app_ptr_map.get_app_pointer_idx((void*)ptr, max_ptr);
985 auto idx_as_ptr = this->template impl_get_unsandboxed_pointer<T>(idx);
986 // Right now we simply assume that any integer can be converted to a valid
987 // pointer in the sandbox This may not be true for some sandboxing mechanism
988 // plugins in the future In this case, we will have to come up with
989 // something more clever to construct indexes that look like valid pointers
990 // Add a check for now to make sure things work fine
991 detail::dynamic_check(is_pointer_in_sandbox_memory(idx_as_ptr),
992 "App pointers are not currently supported for this "
993 "rlbox sandbox plugin. Please file a bug.");
994 auto ret = app_pointer<T*, T_Sbx>(
995 &app_ptr_map, idx, reinterpret_cast<T*>(idx_as_ptr));
996 return ret;
1000 * @brief The mirror of get_app_pointer. Take a tainted pointer which is
1001 * actually an app_pointer, and get the application location being pointed to
1003 * @param tainted_ptr The tainted pointer that is actually an app_pointer
1005 * @return The original location being referred to by the app_ptr
1007 template<typename T>
1008 T* lookup_app_ptr(tainted<T*, T_Sbx> tainted_ptr)
1010 auto idx = tainted_ptr.get_raw_sandbox_value(*this);
1011 void* ret = app_ptr_map.lookup_index(idx);
1012 return reinterpret_cast<T*>(ret);
1015 #ifdef RLBOX_MEASURE_TRANSITION_TIMES
1016 inline std::vector<rlbox_transition_timing>&
1017 process_and_get_transition_times()
1019 return transition_times;
1021 inline int64_t get_total_ns_time_in_sandbox_and_transitions()
1023 int64_t ret = 0;
1024 for (auto& transition_time : transition_times) {
1025 if (transition_time.invoke == rlbox_transition::INVOKE) {
1026 ret += transition_time.time;
1027 } else {
1028 ret -= transition_time.time;
1031 return ret;
1033 inline void clear_transition_times() { transition_times.clear(); }
1034 #endif
1037 #if defined(__clang__)
1038 # pragma clang diagnostic push
1039 # pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments"
1040 #elif defined(__GNUC__) || defined(__GNUG__)
1041 // Can't turn off the variadic macro warning emitted from -pedantic so use a
1042 // hack to stop GCC emitting warnings for the reminder of this file
1043 # pragma GCC system_header
1044 #elif defined(_MSC_VER)
1045 // Doesn't seem to emit the warning
1046 #else
1047 // Don't know the compiler... just let it go through
1048 #endif
1051 * @def invoke_sandbox_function
1052 * @brief Call sandbox function.
1054 * @param func_name The sandboxed library function to call.
1055 * @param ... Arguments to function should be simple or tainted values.
1056 * @return Tainted value or void.
1058 #ifdef RLBOX_USE_STATIC_CALLS
1060 # define sandbox_lookup_symbol_helper(prefix, func_name) prefix(func_name)
1062 # define invoke_sandbox_function(func_name, ...) \
1063 template INTERNAL_invoke_with_func_ptr<decltype(func_name)>( \
1064 #func_name, \
1065 sandbox_lookup_symbol_helper(RLBOX_USE_STATIC_CALLS(), func_name), \
1066 ##__VA_ARGS__)
1068 # define get_sandbox_function_address(func_name) \
1069 template INTERNAL_get_sandbox_function_ptr<decltype(func_name)>( \
1070 sandbox_lookup_symbol_helper(RLBOX_USE_STATIC_CALLS(), func_name))
1072 #else
1074 # define invoke_sandbox_function(func_name, ...) \
1075 template INTERNAL_invoke_with_func_name<decltype(func_name)>( \
1076 #func_name, ##__VA_ARGS__)
1078 # define get_sandbox_function_address(func_name) \
1079 template INTERNAL_get_sandbox_function_name<decltype(func_name)>(#func_name)
1081 #endif
1083 #define sandbox_invoke(sandbox, func_name, ...) \
1084 (sandbox).invoke_sandbox_function(func_name, ##__VA_ARGS__)
1086 #define sandbox_function_address(sandbox, func_name) \
1087 (sandbox).get_sandbox_function_address(func_name)
1089 #if defined(__clang__)
1090 # pragma clang diagnostic pop
1091 #else
1092 #endif