summaryrefslogtreecommitdiffstats
path: root/third_party/rlbox/include/rlbox_sandbox.hpp
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rlbox/include/rlbox_sandbox.hpp')
-rw-r--r--third_party/rlbox/include/rlbox_sandbox.hpp1094
1 files changed, 1094 insertions, 0 deletions
diff --git a/third_party/rlbox/include/rlbox_sandbox.hpp b/third_party/rlbox/include/rlbox_sandbox.hpp
new file mode 100644
index 0000000000..63995c89ad
--- /dev/null
+++ b/third_party/rlbox/include/rlbox_sandbox.hpp
@@ -0,0 +1,1094 @@
+#pragma once
+// IWYU pragma: private, include "rlbox.hpp"
+// IWYU pragma: friend "rlbox_.*\.hpp"
+
+#include <algorithm>
+#include <atomic>
+#ifdef RLBOX_MEASURE_TRANSITION_TIMES
+# include <chrono>
+#endif
+#include <cstdlib>
+#include <limits>
+#include <map>
+#include <mutex>
+#ifndef RLBOX_USE_CUSTOM_SHARED_LOCK
+# include <shared_mutex>
+#endif
+#ifdef RLBOX_MEASURE_TRANSITION_TIMES
+# include <sstream>
+# include <string>
+#endif
+#include <stdint.h>
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+#include "rlbox_conversion.hpp"
+#include "rlbox_helpers.hpp"
+#include "rlbox_stdlib_polyfill.hpp"
+#include "rlbox_struct_support.hpp"
+#include "rlbox_type_traits.hpp"
+#include "rlbox_wrapper_traits.hpp"
+
+#ifdef RLBOX_MEASURE_TRANSITION_TIMES
+using namespace std::chrono;
+#endif
+
+namespace rlbox {
+
+namespace convert_fn_ptr_to_sandbox_equivalent_detail {
+ template<typename T, typename T_Sbx>
+ using conv = ::rlbox::detail::convert_to_sandbox_equivalent_t<T, T_Sbx>;
+
+ template<typename T_Ret, typename... T_Args>
+ using T_Func = T_Ret (*)(T_Args...);
+
+ template<typename T_Sbx, typename T_Ret, typename... T_Args>
+ T_Func<conv<T_Ret, T_Sbx>, conv<T_Args, T_Sbx>...> helper(
+ T_Ret (*)(T_Args...));
+}
+
+#if defined(RLBOX_MEASURE_TRANSITION_TIMES) || \
+ defined(RLBOX_TRANSITION_ACTION_OUT) || defined(RLBOX_TRANSITION_ACTION_IN)
+enum class rlbox_transition
+{
+ INVOKE,
+ CALLBACK
+};
+#endif
+#ifdef RLBOX_MEASURE_TRANSITION_TIMES
+struct rlbox_transition_timing
+{
+ rlbox_transition invoke;
+ const char* name;
+ void* ptr;
+ int64_t time;
+
+ std::string to_string()
+ {
+ std::ostringstream ret;
+ if (invoke == rlbox_transition::INVOKE) {
+ ret << name;
+ } else {
+ ret << "Callback " << ptr;
+ }
+ ret << " : " << time << "\n";
+
+ return ret.str();
+ }
+};
+#endif
+
+#ifndef RLBOX_SINGLE_THREADED_INVOCATIONS
+# error \
+ "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."
+#endif
+
+/**
+ * @brief Encapsulation for sandboxes.
+ *
+ * @tparam T_Sbx Type of sandbox. For the null sandbox this is
+ * `rlbox_noop_sandbox`
+ */
+template<typename T_Sbx>
+class rlbox_sandbox : protected T_Sbx
+{
+ KEEP_CLASSES_FRIENDLY
+
+private:
+#ifdef RLBOX_MEASURE_TRANSITION_TIMES
+ std::vector<rlbox_transition_timing> transition_times;
+#endif
+
+ static inline RLBOX_SHARED_LOCK(sandbox_list_lock);
+ // The actual type of the vector is std::vector<rlbox_sandbox<T_Sbx>*>
+ // However clang 5, 6 have bugs where compilation seg-faults on this type
+ // So we just use this std::vector<void*>
+ static inline std::vector<void*> sandbox_list;
+
+ RLBOX_SHARED_LOCK(func_ptr_cache_lock);
+ std::map<std::string, void*> func_ptr_map;
+
+ app_pointer_map<typename T_Sbx::T_PointerType> app_ptr_map;
+
+ // This variable tracks of the sandbox has already been created/destroyed.
+ // APIs in this class should be called only when the sandbox is created.
+ // However, it is expensive to check in APIs such as invoke or in the callback
+ // interceptor. What's more, there could be time of check time of use issues
+ // in the checks as well.
+ // In general, we leave it up to the user to ensure these APIs are never
+ // called prior to sandbox construction or after destruction. We perform some
+ // conservative sanity checks, where they would not add too much overhead.
+ enum class Sandbox_Status
+ {
+ NOT_CREATED,
+ INITIALIZING,
+ CREATED,
+ CLEANING_UP
+ };
+ std::atomic<Sandbox_Status> sandbox_created = Sandbox_Status::NOT_CREATED;
+
+ std::mutex callback_lock;
+ std::vector<void*> callback_keys;
+
+ void* transition_state = nullptr;
+
+ template<typename T>
+ using convert_fn_ptr_to_sandbox_equivalent_t = decltype(
+ ::rlbox::convert_fn_ptr_to_sandbox_equivalent_detail::helper<T_Sbx>(
+ std::declval<T>()));
+
+ template<typename T>
+ inline constexpr void check_invoke_param_type_is_ok()
+ {
+ using T_NoRef = std::remove_reference_t<T>;
+
+ if_constexpr_named(cond1, detail::rlbox_is_wrapper_v<T_NoRef>)
+ {
+ if_constexpr_named(
+ subcond1,
+ !std::is_same_v<T_Sbx, detail::rlbox_get_wrapper_sandbox_t<T_NoRef>>)
+ {
+ rlbox_detail_static_fail_because(
+ cond1 && subcond1,
+ "Mixing tainted data from a different sandbox types. This could "
+ "happen due to couple of different reasons.\n"
+ "1. You are using 2 sandbox types for example'rlbox_noop_sandbox' "
+ "and 'rlbox_lucet_sandbox', and are passing tainted data from one "
+ "sandbox as parameters into a function call to the other sandbox. "
+ "This is not allowed, unwrap the tainted data with copy_and_verify "
+ "or other unwrapping APIs first.\n"
+ "2. You have inadvertantly forgotten to set/remove "
+ "RLBOX_USE_STATIC_CALLS depending on the sandbox type. Some sandbox "
+ "types like rlbox_noop_sandbox require this to be set to a given "
+ "value, while other types like rlbox_lucet_sandbox, require this not "
+ "to be set.");
+ }
+ }
+ else if_constexpr_named(cond2,
+ std::is_null_pointer_v<T_NoRef> ||
+ detail::is_fundamental_or_enum_v<T_NoRef>)
+ {}
+ else
+ {
+ constexpr auto unknownCase = !(cond1 || cond2);
+ rlbox_detail_static_fail_because(
+ unknownCase,
+ "Arguments to a sandbox function call should be primitives or wrapped "
+ "types like tainted, callbacks etc.");
+ }
+ }
+
+ template<typename T>
+ inline auto invoke_process_param(T&& param)
+ {
+ check_invoke_param_type_is_ok<T>();
+
+ using T_NoRef = std::remove_reference_t<T>;
+
+ if constexpr (detail::rlbox_is_tainted_opaque_v<T_NoRef>) {
+ auto ret = from_opaque(param);
+ return ret.UNSAFE_sandboxed(*this);
+ } else if constexpr (detail::rlbox_is_wrapper_v<T_NoRef>) {
+ return param.UNSAFE_sandboxed(*this);
+ } else if constexpr (std::is_null_pointer_v<T_NoRef>) {
+ tainted<void*, T_Sbx> ret = nullptr;
+ return ret.UNSAFE_sandboxed(*this);
+ } else if constexpr (detail::is_fundamental_or_enum_v<T_NoRef>) {
+ // For unwrapped primitives, assign to a tainted var and then unwrap so
+ // that we adjust for machine model
+ tainted<T_NoRef, T_Sbx> ret = param;
+ return ret.UNSAFE_sandboxed(*this);
+ } else {
+ rlbox_detail_static_fail_because(
+ detail::true_v<T_NoRef>,
+ "Only tainted types, callbacks or primitive values such as ints can be "
+ "passed as parameters.\n"
+ "To make a parameter tainted, try moving the allocation into the "
+ "sandbox.\n"
+ "If the parameter is a callback, try registering the callback via the "
+ "register_callback API.");
+ }
+ }
+
+ template<typename T, typename T_Arg>
+ inline tainted<T, T_Sbx> sandbox_callback_intercept_convert_param(
+ rlbox_sandbox<T_Sbx>& sandbox,
+ const T_Arg& arg)
+ {
+ tainted<T, T_Sbx> ret;
+ using namespace detail;
+ convert_type<T_Sbx,
+ adjust_type_direction::TO_APPLICATION,
+ adjust_type_context::SANDBOX>(
+ ret.get_raw_value_ref(),
+ arg,
+ nullptr /* example_unsandboxed_ptr */,
+ &sandbox);
+ return ret;
+ }
+
+ template<typename T_Ret, typename... T_Args>
+ static detail::convert_to_sandbox_equivalent_t<T_Ret, T_Sbx>
+ sandbox_callback_interceptor(
+ detail::convert_to_sandbox_equivalent_t<T_Args, T_Sbx>... args)
+ {
+ std::pair<T_Sbx*, void*> context =
+ T_Sbx::impl_get_executed_callback_sandbox_and_key();
+ auto& sandbox = *(reinterpret_cast<rlbox_sandbox<T_Sbx>*>(context.first));
+ auto key = context.second;
+
+ using T_Func_Ret =
+ std::conditional_t<std::is_void_v<T_Ret>, void, tainted<T_Ret, T_Sbx>>;
+ using T_Func =
+ T_Func_Ret (*)(rlbox_sandbox<T_Sbx>&, tainted<T_Args, T_Sbx>...);
+ auto target_fn_ptr = reinterpret_cast<T_Func>(key);
+
+#ifdef RLBOX_MEASURE_TRANSITION_TIMES
+ high_resolution_clock::time_point enter_time = high_resolution_clock::now();
+ auto on_exit = rlbox::detail::make_scope_exit([&] {
+ auto exit_time = high_resolution_clock::now();
+ int64_t ns = duration_cast<nanoseconds>(exit_time - enter_time).count();
+ sandbox.transition_times.push_back(
+ rlbox_transition_timing{ rlbox_transition::CALLBACK,
+ nullptr /* func_name */,
+ key /* func_ptr */,
+ ns });
+ });
+#endif
+#ifdef RLBOX_TRANSITION_ACTION_OUT
+ RLBOX_TRANSITION_ACTION_OUT(rlbox_transition::CALLBACK,
+ nullptr /* func_name */,
+ key /* func_ptr */,
+ sandbox.transition_state);
+#endif
+#ifdef RLBOX_TRANSITION_ACTION_IN
+ auto on_exit_transition = rlbox::detail::make_scope_exit([&] {
+ RLBOX_TRANSITION_ACTION_IN(rlbox_transition::CALLBACK,
+ nullptr /* func_name */,
+ key /* func_ptr */,
+ sandbox.transition_state);
+ });
+#endif
+ if constexpr (std::is_void_v<T_Func_Ret>) {
+ (*target_fn_ptr)(
+ sandbox,
+ sandbox.template sandbox_callback_intercept_convert_param<T_Args>(
+ sandbox, args)...);
+ return;
+ } else {
+ auto tainted_ret = (*target_fn_ptr)(
+ sandbox,
+ sandbox.template sandbox_callback_intercept_convert_param<T_Args>(
+ sandbox, args)...);
+
+ using namespace detail;
+ convert_to_sandbox_equivalent_t<T_Ret, T_Sbx> ret;
+ convert_type<T_Sbx,
+ adjust_type_direction::TO_SANDBOX,
+ adjust_type_context::SANDBOX>(
+ ret,
+ tainted_ret.get_raw_value_ref(),
+ nullptr /* example_unsandboxed_ptr */,
+ &sandbox);
+ return ret;
+ }
+ }
+
+ /**
+ * @brief Unregister a callback function and disallow the sandbox from
+ * calling this function henceforth.
+ */
+ template<typename T_Ret, typename... T_Args>
+ inline void unregister_callback(void* key)
+ {
+ // Silently swallowing the failure is better here as RAII types may try to
+ // cleanup callbacks after sandbox destruction
+ if (sandbox_created.load() != Sandbox_Status::CREATED) {
+ return;
+ }
+
+ this->template impl_unregister_callback<
+ detail::convert_to_sandbox_equivalent_t<T_Ret, T_Sbx>,
+ detail::convert_to_sandbox_equivalent_t<T_Args, T_Sbx>...>(key);
+
+ std::lock_guard<std::mutex> lock(callback_lock);
+ auto el_ref = std::find(callback_keys.begin(), callback_keys.end(), key);
+ detail::dynamic_check(
+ el_ref != callback_keys.end(),
+ "Unexpected state. Unregistering a callback that was never registered.");
+ callback_keys.erase(el_ref);
+ }
+
+ static T_Sbx* find_sandbox_from_example(const void* example_sandbox_ptr)
+ {
+ detail::dynamic_check(
+ example_sandbox_ptr != nullptr,
+ "Internal error: received a null example pointer. Please file a bug.");
+
+ RLBOX_ACQUIRE_SHARED_GUARD(lock, sandbox_list_lock);
+ for (auto sandbox_v : sandbox_list) {
+ auto sandbox = reinterpret_cast<rlbox_sandbox<T_Sbx>*>(sandbox_v);
+ if (sandbox->is_pointer_in_sandbox_memory(example_sandbox_ptr)) {
+ return sandbox;
+ }
+ }
+
+ return nullptr;
+ }
+
+ template<typename... T_Args>
+ static auto impl_create_sandbox_helper(rlbox_sandbox<T_Sbx>* this_ptr,
+ T_Args... args)
+ {
+ return this_ptr->impl_create_sandbox(std::forward<T_Args>(args)...);
+ }
+
+public:
+ /**
+ * @brief Unused member that allows the calling code to save data in a
+ * "per-sandbox" storage. This can be useful to save context which is used
+ * in callbacks.
+ */
+ void* sandbox_storage;
+
+ /***** Function to adjust for custom machine models *****/
+
+ template<typename T>
+ using convert_to_sandbox_equivalent_nonclass_t =
+ detail::convert_base_types_t<T,
+ typename T_Sbx::T_ShortType,
+ typename T_Sbx::T_IntType,
+ typename T_Sbx::T_LongType,
+ typename T_Sbx::T_LongLongType,
+ typename T_Sbx::T_PointerType>;
+
+ T_Sbx* get_sandbox_impl() { return this; }
+
+ /**
+ * @brief Create a new sandbox.
+ *
+ * @tparam T_Args Arguments passed to the underlying sandbox
+ * implementation. For the null sandbox, no arguments are necessary.
+ */
+ template<typename... T_Args>
+ inline bool create_sandbox(T_Args... args)
+ {
+#ifdef RLBOX_MEASURE_TRANSITION_TIMES
+ // Warm up the timer. The first call is always slow (at least on the test
+ // platform)
+ for (int i = 0; i < 10; i++) {
+ auto val = high_resolution_clock::now();
+ RLBOX_UNUSED(val);
+ }
+#endif
+ auto expected = Sandbox_Status::NOT_CREATED;
+ bool success = sandbox_created.compare_exchange_strong(
+ expected, Sandbox_Status::INITIALIZING /* desired */);
+ detail::dynamic_check(
+ success,
+ "create_sandbox called when sandbox already created/is being "
+ "created concurrently");
+
+ using T_Result = rlbox::detail::polyfill::invoke_result_t<
+ decltype(impl_create_sandbox_helper<T_Args...>),
+ decltype(this),
+ T_Args...>;
+
+ bool created = true;
+ if constexpr (std::is_same_v<T_Result, void>) {
+ this->impl_create_sandbox(std::forward<T_Args>(args)...);
+ } else if constexpr (std::is_same_v<T_Result, bool>) {
+ created = this->impl_create_sandbox(std::forward<T_Args>(args)...);
+ } else {
+ rlbox_detail_static_fail_because(
+ (!std::is_same_v<T_Result, void> && !std::is_same_v<T_Result, bool>),
+ "Expected impl_create_sandbox to return void or a boolean");
+ }
+
+ if (created) {
+ sandbox_created.store(Sandbox_Status::CREATED);
+ RLBOX_ACQUIRE_UNIQUE_GUARD(lock, sandbox_list_lock);
+ sandbox_list.push_back(this);
+ }
+
+ return created;
+ }
+
+ /**
+ * @brief Destroy sandbox and reclaim any memory.
+ */
+ inline auto destroy_sandbox()
+ {
+ auto expected = Sandbox_Status::CREATED;
+ bool success = sandbox_created.compare_exchange_strong(
+ expected, Sandbox_Status::CLEANING_UP /* desired */);
+
+ detail::dynamic_check(
+ success,
+ "destroy_sandbox called without sandbox creation/is being "
+ "destroyed concurrently");
+
+ {
+ RLBOX_ACQUIRE_UNIQUE_GUARD(lock, sandbox_list_lock);
+ auto el_ref = std::find(sandbox_list.begin(), sandbox_list.end(), this);
+ detail::dynamic_check(
+ el_ref != sandbox_list.end(),
+ "Unexpected state. Destroying a sandbox that was never initialized.");
+ sandbox_list.erase(el_ref);
+ }
+
+ sandbox_created.store(Sandbox_Status::NOT_CREATED);
+ return this->impl_destroy_sandbox();
+ }
+
+ template<typename T>
+ inline T get_unsandboxed_pointer(
+ convert_to_sandbox_equivalent_nonclass_t<T> p) const
+ {
+ static_assert(std::is_pointer_v<T>);
+ if (p == 0) {
+ return nullptr;
+ }
+ auto ret = this->template impl_get_unsandboxed_pointer<T>(p);
+ return reinterpret_cast<T>(ret);
+ }
+
+ template<typename T>
+ inline convert_to_sandbox_equivalent_nonclass_t<T> get_sandboxed_pointer(
+ const void* p) const
+ {
+ static_assert(std::is_pointer_v<T>);
+ if (p == nullptr) {
+ return 0;
+ }
+ return this->template impl_get_sandboxed_pointer<T>(p);
+ }
+
+ template<typename T>
+ static inline T get_unsandboxed_pointer_no_ctx(
+ convert_to_sandbox_equivalent_nonclass_t<T> p,
+ const void* example_unsandboxed_ptr)
+ {
+ static_assert(std::is_pointer_v<T>);
+ if (p == 0) {
+ return nullptr;
+ }
+ auto ret = T_Sbx::template impl_get_unsandboxed_pointer_no_ctx<T>(
+ p, example_unsandboxed_ptr, find_sandbox_from_example);
+ return reinterpret_cast<T>(ret);
+ }
+
+ template<typename T>
+ static inline convert_to_sandbox_equivalent_nonclass_t<T>
+ get_sandboxed_pointer_no_ctx(const void* p,
+ const void* example_unsandboxed_ptr)
+ {
+ static_assert(std::is_pointer_v<T>);
+ if (p == nullptr) {
+ return 0;
+ }
+ return T_Sbx::template impl_get_sandboxed_pointer_no_ctx<T>(
+ p, example_unsandboxed_ptr, find_sandbox_from_example);
+ }
+
+ /**
+ * @brief Allocate a new pointer that is accessible to both the application
+ * and sandbox. The pointer is allocated in sandbox memory.
+ *
+ * @tparam T The type of the pointer you want to create. If T=int, this
+ * would return a pointer to an int.
+ *
+ * @return tainted<T*, T_Sbx> Tainted pointer accessible to the application
+ * and sandbox.
+ */
+ template<typename T>
+ inline tainted<T*, T_Sbx> malloc_in_sandbox()
+ {
+ const uint32_t defaultCount = 1;
+ return malloc_in_sandbox<T>(defaultCount);
+ }
+
+ /**
+ * @brief Allocate an array that is accessible to both the application
+ * and sandbox. The pointer is allocated in sandbox memory.
+ *
+ * @tparam T The type of the array elements you want to create. If T=int, this
+ * would return a pointer to an array of ints.
+ *
+ * @param count The number of array elements to allocate.
+ *
+ * @return tainted<T*, T_Sbx> Tainted pointer accessible to the application
+ * and sandbox.
+ */
+ template<typename T>
+ inline tainted<T*, T_Sbx> malloc_in_sandbox(uint32_t count)
+ {
+ // Silently swallowing the failure is better here as RAII types may try to
+ // malloc after sandbox destruction
+ if (sandbox_created.load() != Sandbox_Status::CREATED) {
+ return tainted<T*, T_Sbx>::internal_factory(nullptr);
+ }
+
+ detail::dynamic_check(count != 0, "Malloc tried to allocate 0 bytes");
+ if constexpr (sizeof(T) >= std::numeric_limits<uint32_t>::max()) {
+ rlbox_detail_static_fail_because(sizeof(T) >=
+ std::numeric_limits<uint32_t>::max(),
+ "Tried to allocate an object over 4GB.");
+ }
+ auto total_size = static_cast<uint64_t>(sizeof(T)) * count;
+ if constexpr (sizeof(size_t) == 4) {
+ // On a 32-bit platform, we need to make sure that total_size is not >=4GB
+ detail::dynamic_check(total_size < std::numeric_limits<uint32_t>::max(),
+ "Tried to allocate memory over 4GB");
+ } else if constexpr (sizeof(size_t) != 8) {
+ // Double check we are on a 64-bit platform
+ // Note for static checks we need to have some dependence on T, so adding
+ // a dummy
+ constexpr bool dummy = sizeof(T) >= 0;
+ rlbox_detail_static_fail_because(dummy && sizeof(size_t) != 8,
+ "Expected 32 or 64 bit platform.");
+ }
+ auto ptr_in_sandbox = this->impl_malloc_in_sandbox(total_size);
+ auto ptr = get_unsandboxed_pointer<T*>(ptr_in_sandbox);
+ if (!ptr) {
+ return tainted<T*, T_Sbx>(nullptr);
+ }
+ detail::dynamic_check(is_pointer_in_sandbox_memory(ptr),
+ "Malloc returned pointer outside the sandbox memory");
+ auto ptr_end = reinterpret_cast<uintptr_t>(ptr + (count - 1));
+ detail::dynamic_check(
+ is_in_same_sandbox(ptr, reinterpret_cast<void*>(ptr_end)),
+ "Malloc returned a pointer whose range goes beyond sandbox memory");
+ auto cast_ptr = reinterpret_cast<T*>(ptr);
+ return tainted<T*, T_Sbx>::internal_factory(cast_ptr);
+ }
+
+ /**
+ * @brief Free the memory referenced by the tainted pointer.
+ *
+ * @param ptr Pointer to sandbox memory to free.
+ */
+ template<typename T>
+ inline void free_in_sandbox(tainted<T*, T_Sbx> ptr)
+ {
+ // Silently swallowing the failure is better here as RAII types may try to
+ // free after sandbox destruction
+ if (sandbox_created.load() != Sandbox_Status::CREATED) {
+ return;
+ }
+
+ this->impl_free_in_sandbox(ptr.get_raw_sandbox_value(*this));
+ }
+
+ /**
+ * @brief Free the memory referenced by a tainted_volatile pointer ref.
+ *
+ * @param ptr_ref Pointer reference to sandbox memory to free.
+ */
+ template<typename T>
+ inline void free_in_sandbox(tainted_volatile<T, T_Sbx>& ptr_ref)
+ {
+ tainted<T, T_Sbx> ptr = ptr_ref;
+ free_in_sandbox(ptr);
+ }
+
+ /**
+ * @brief Free the memory referenced by a tainted_opaque pointer.
+ *
+ * @param ptr_opaque Opaque pointer to sandbox memory to free.
+ */
+ template<typename T>
+ inline void free_in_sandbox(tainted_opaque<T, T_Sbx> ptr_opaque)
+ {
+ tainted<T, T_Sbx> ptr = from_opaque(ptr_opaque);
+ free_in_sandbox(ptr);
+ }
+
+ /**
+ * @brief Check if two pointers are in the same sandbox.
+ * For the null-sandbox, this always returns true.
+ */
+ static inline bool is_in_same_sandbox(const void* p1, const void* p2)
+ {
+ const size_t num_args =
+ detail::func_arg_nums_v<decltype(T_Sbx::impl_is_in_same_sandbox)>;
+ if constexpr (num_args == 2) {
+ return T_Sbx::impl_is_in_same_sandbox(p1, p2);
+ } else {
+ return T_Sbx::impl_is_in_same_sandbox(p1, p2, find_sandbox_from_example);
+ }
+ }
+
+ /**
+ * @brief Check if the pointer points to this sandbox's memory.
+ * For the null-sandbox, this always returns true.
+ */
+ inline bool is_pointer_in_sandbox_memory(const void* p)
+ {
+ return this->impl_is_pointer_in_sandbox_memory(p);
+ }
+
+ /**
+ * @brief Check if the pointer points to application memory.
+ * For the null-sandbox, this always returns true.
+ */
+ inline bool is_pointer_in_app_memory(const void* p)
+ {
+ return this->impl_is_pointer_in_app_memory(p);
+ }
+
+ inline size_t get_total_memory() { return this->impl_get_total_memory(); }
+
+ inline void* get_memory_location()
+ {
+ return this->impl_get_memory_location();
+ }
+
+ void* get_transition_state() { return transition_state; }
+
+ void set_transition_state(void* new_state) { transition_state = new_state; }
+
+ /**
+ * @brief For internal use only.
+ * Grant access of the passed in buffer in to the sandbox instance. Called by
+ * internal APIs only if the underlying sandbox supports
+ * can_grant_deny_access by including the line
+ * ```
+ * using can_grant_deny_access = void;
+ * ```
+ */
+ template<typename T>
+ inline tainted<T*, T_Sbx> INTERNAL_grant_access(T* src,
+ size_t num,
+ bool& success)
+ {
+ auto ret = this->impl_grant_access(src, num, success);
+ return tainted<T*, T_Sbx>::internal_factory(ret);
+ }
+
+ /**
+ * @brief For internal use only.
+ * Grant access of the passed in buffer in to the sandbox instance. Called by
+ * internal APIs only if the underlying sandbox supports
+ * can_grant_deny_access by including the line
+ * ```
+ * using can_grant_deny_access = void;
+ * ```
+ */
+ template<typename T>
+ inline T* INTERNAL_deny_access(tainted<T*, T_Sbx> src,
+ size_t num,
+ bool& success)
+ {
+ auto ret =
+ this->impl_deny_access(src.INTERNAL_unverified_safe(), num, success);
+ return ret;
+ }
+
+ void* lookup_symbol(const char* func_name)
+ {
+ {
+ RLBOX_ACQUIRE_SHARED_GUARD(lock, func_ptr_cache_lock);
+
+ auto func_ptr_ref = func_ptr_map.find(func_name);
+ if (func_ptr_ref != func_ptr_map.end()) {
+ return func_ptr_ref->second;
+ }
+ }
+
+ void* func_ptr = this->impl_lookup_symbol(func_name);
+ RLBOX_ACQUIRE_UNIQUE_GUARD(lock, func_ptr_cache_lock);
+ func_ptr_map[func_name] = func_ptr;
+ return func_ptr;
+ }
+
+ void* internal_lookup_symbol(const char* func_name)
+ {
+ {
+ RLBOX_ACQUIRE_SHARED_GUARD(lock, func_ptr_cache_lock);
+
+ auto func_ptr_ref = func_ptr_map.find(func_name);
+ if (func_ptr_ref != func_ptr_map.end()) {
+ return func_ptr_ref->second;
+ }
+ }
+
+ void* func_ptr = 0;
+ if constexpr (rlbox::detail::
+ has_member_using_needs_internal_lookup_symbol_v<T_Sbx>) {
+ func_ptr = this->impl_internal_lookup_symbol(func_name);
+ } else {
+ func_ptr = this->impl_lookup_symbol(func_name);
+ }
+ RLBOX_ACQUIRE_UNIQUE_GUARD(lock, func_ptr_cache_lock);
+ func_ptr_map[func_name] = func_ptr;
+ return func_ptr;
+ }
+
+ // this is an internal function invoked from macros, so it has be public
+ template<typename T, typename... T_Args>
+ inline auto INTERNAL_invoke_with_func_name(const char* func_name,
+ T_Args&&... params)
+ {
+ return INTERNAL_invoke_with_func_ptr<T, T_Args...>(
+ func_name, lookup_symbol(func_name), std::forward<T_Args>(params)...);
+ }
+
+ // this is an internal function invoked from macros, so it has be public
+ // Explicitly don't use inline on this, as this adds a lot of instructions
+ // prior to function call. What's more, by not inlining, different function
+ // calls with the same signature can share the same code segments for
+ // sandboxed function execution in the binary
+ template<typename T, typename... T_Args>
+ auto INTERNAL_invoke_with_func_ptr(const char* func_name,
+ void* func_ptr,
+ T_Args&&... params)
+ {
+ // unused in some paths
+ RLBOX_UNUSED(func_name);
+#ifdef RLBOX_MEASURE_TRANSITION_TIMES
+ auto enter_time = high_resolution_clock::now();
+ auto on_exit = rlbox::detail::make_scope_exit([&] {
+ auto exit_time = high_resolution_clock::now();
+ int64_t ns = duration_cast<nanoseconds>(exit_time - enter_time).count();
+ transition_times.push_back(rlbox_transition_timing{
+ rlbox_transition::INVOKE, func_name, func_ptr, ns });
+ });
+#endif
+#ifdef RLBOX_TRANSITION_ACTION_IN
+ RLBOX_TRANSITION_ACTION_IN(
+ rlbox_transition::INVOKE, func_name, func_ptr, transition_state);
+#endif
+#ifdef RLBOX_TRANSITION_ACTION_OUT
+ auto on_exit_transition = rlbox::detail::make_scope_exit([&] {
+ RLBOX_TRANSITION_ACTION_OUT(
+ rlbox_transition::INVOKE, func_name, func_ptr, transition_state);
+ });
+#endif
+ (check_invoke_param_type_is_ok<T_Args>(), ...);
+
+ static_assert(
+ rlbox::detail::polyfill::is_invocable_v<
+ T,
+ detail::rlbox_remove_wrapper_t<std::remove_reference_t<T_Args>>...>,
+ "Mismatched arguments types for function");
+
+ using T_Result = rlbox::detail::polyfill::invoke_result_t<
+ T,
+ detail::rlbox_remove_wrapper_t<std::remove_reference_t<T_Args>>...>;
+
+ using T_Converted =
+ std::remove_pointer_t<convert_fn_ptr_to_sandbox_equivalent_t<T*>>;
+
+ if constexpr (std::is_void_v<T_Result>) {
+ this->template impl_invoke_with_func_ptr<T>(
+ reinterpret_cast<T_Converted*>(func_ptr),
+ invoke_process_param(params)...);
+ return;
+ } else {
+ auto raw_result = this->template impl_invoke_with_func_ptr<T>(
+ reinterpret_cast<T_Converted*>(func_ptr),
+ invoke_process_param(params)...);
+ tainted<T_Result, T_Sbx> wrapped_result;
+ using namespace detail;
+ convert_type<T_Sbx,
+ adjust_type_direction::TO_APPLICATION,
+ adjust_type_context::SANDBOX>(
+ wrapped_result.get_raw_value_ref(),
+ raw_result,
+ nullptr /* example_unsandboxed_ptr */,
+ this /* sandbox_ptr */);
+ return wrapped_result;
+ }
+ }
+
+ // Useful in the porting stage to temporarily allow non tainted pointers to go
+ // through. This will only ever work in the rlbox_noop_sandbox. Any sandbox
+ // that actually enforces isolation will crash here.
+ template<typename T2>
+ tainted<T2, T_Sbx> UNSAFE_accept_pointer(T2 ptr)
+ {
+ static_assert(std::is_pointer_v<T2>,
+ "UNSAFE_accept_pointer expects a pointer param");
+ tainted<T2, T_Sbx> ret;
+ ret.assign_raw_pointer(*this, ptr);
+ return ret;
+ }
+
+ template<typename T_Ret, typename... T_Args>
+ using T_Cb_no_wrap = detail::rlbox_remove_wrapper_t<T_Ret>(
+ detail::rlbox_remove_wrapper_t<T_Args>...);
+
+ template<typename T_Ret>
+ sandbox_callback<T_Cb_no_wrap<T_Ret>*, T_Sbx> register_callback(T_Ret (*)())
+ {
+ rlbox_detail_static_fail_because(
+ detail::true_v<T_Ret>,
+ "Modify the callback to change the first parameter to a sandbox. "
+ "For instance if a callback has type\n\n"
+ "int foo() {...}\n\n"
+ "Change this to \n\n"
+ "tainted<int, T_Sbx> foo(rlbox_sandbox<T_Sbx>& sandbox) {...}\n");
+
+ // this is never executed, but we need it for the function to type-check
+ std::abort();
+ }
+
+ /**
+ * @brief Expose a callback function to the sandboxed code.
+ *
+ * @param func_ptr The callback to expose.
+ *
+ * @tparam T_RL Sandbox reference type (first argument).
+ * @tparam T_Ret Return type of callback. Must be tainted or void.
+ * @tparam T_Args Types of remaining callback arguments. Must be tainted.
+ *
+ * @return Wrapped callback function pointer that can be passed to the
+ * sandbox.
+ */
+ template<typename T_RL, typename T_Ret, typename... T_Args>
+ sandbox_callback<T_Cb_no_wrap<T_Ret, T_Args...>*, T_Sbx> register_callback(
+ T_Ret (*func_ptr)(T_RL, T_Args...))
+ {
+ // Some branches don't use the param
+ RLBOX_UNUSED(func_ptr);
+
+ if_constexpr_named(cond1, !std::is_same_v<T_RL, rlbox_sandbox<T_Sbx>&>)
+ {
+ rlbox_detail_static_fail_because(
+ cond1,
+ "Modify the callback to change the first parameter to a sandbox. "
+ "For instance if a callback has type\n\n"
+ "int foo(int a, int b) {...}\n\n"
+ "Change this to \n\n"
+ "tainted<int, T_Sbx> foo(rlbox_sandbox<T_Sbx>& sandbox, "
+ "tainted<int, T_Sbx> a, tainted<int, T_Sbx> b) {...}\n");
+ }
+ else if_constexpr_named(
+ cond2, !(detail::rlbox_is_tainted_or_opaque_v<T_Args> && ...))
+ {
+ rlbox_detail_static_fail_because(
+ cond2,
+ "Change all arguments to the callback have to be tainted or "
+ "tainted_opaque. "
+ "For instance if a callback has type\n\n"
+ "int foo(int a, int b) {...}\n\n"
+ "Change this to \n\n"
+ "tainted<int, T_Sbx> foo(rlbox_sandbox<T_Sbx>& sandbox, "
+ "tainted<int, T_Sbx> a, tainted<int, T_Sbx> b) {...}\n");
+ }
+ else if_constexpr_named(
+ cond3, (std::is_array_v<detail::rlbox_remove_wrapper_t<T_Args>> || ...))
+ {
+ rlbox_detail_static_fail_because(
+ cond3,
+ "Change all static array arguments to the callback to be pointers. "
+ "For instance if a callback has type\n\n"
+ "int foo(int a[4]) {...}\n\n"
+ "Change this to \n\n"
+ "tainted<int, T_Sbx> foo(rlbox_sandbox<T_Sbx>& sandbox, "
+ "tainted<int*, T_Sbx> a) {...}\n");
+ }
+ else if_constexpr_named(
+ cond4,
+ !(std::is_void_v<T_Ret> || detail::rlbox_is_tainted_or_opaque_v<T_Ret>))
+ {
+ rlbox_detail_static_fail_because(
+ cond4,
+ "Change the callback return type to be tainted or tainted_opaque if it "
+ "is not void. "
+ "For instance if a callback has type\n\n"
+ "int foo(int a, int b) {...}\n\n"
+ "Change this to \n\n"
+ "tainted<int, T_Sbx> foo(rlbox_sandbox<T_Sbx>& sandbox, "
+ "tainted<int, T_Sbx> a, tainted<int, T_Sbx> b) {...}\n");
+ }
+ else
+ {
+ detail::dynamic_check(
+ sandbox_created.load() == Sandbox_Status::CREATED,
+ "register_callback called without sandbox creation");
+
+ // Need unique key for each callback we register - just use the func addr
+ void* unique_key = reinterpret_cast<void*>(func_ptr);
+
+ // Make sure that the user hasn't previously registered this function...
+ // If they have, we would returning 2 owning types (sandbox_callback) to
+ // the same callback which would be bad
+ {
+ std::lock_guard<std::mutex> lock(callback_lock);
+ bool exists =
+ std::find(callback_keys.begin(), callback_keys.end(), unique_key) !=
+ callback_keys.end();
+ detail::dynamic_check(
+ !exists, "You have previously already registered this callback.");
+ callback_keys.push_back(unique_key);
+ }
+
+ auto callback_interceptor =
+ sandbox_callback_interceptor<detail::rlbox_remove_wrapper_t<T_Ret>,
+ detail::rlbox_remove_wrapper_t<T_Args>...>;
+
+ auto callback_trampoline = this->template impl_register_callback<
+ detail::convert_to_sandbox_equivalent_t<
+ detail::rlbox_remove_wrapper_t<T_Ret>,
+ T_Sbx>,
+ detail::convert_to_sandbox_equivalent_t<
+ detail::rlbox_remove_wrapper_t<T_Args>,
+ T_Sbx>...>(unique_key, reinterpret_cast<void*>(callback_interceptor));
+
+ auto tainted_func_ptr = reinterpret_cast<
+ detail::rlbox_tainted_opaque_to_tainted_t<T_Ret, T_Sbx> (*)(
+ T_RL, detail::rlbox_tainted_opaque_to_tainted_t<T_Args, T_Sbx>...)>(
+ reinterpret_cast<void*>(func_ptr));
+
+ auto ret = sandbox_callback<T_Cb_no_wrap<T_Ret, T_Args...>*, T_Sbx>(
+ this,
+ tainted_func_ptr,
+ callback_interceptor,
+ callback_trampoline,
+ unique_key);
+ return ret;
+ }
+ }
+
+ // this is an internal function invoked from macros, so it has be public
+ template<typename T>
+ inline tainted<T*, T_Sbx> INTERNAL_get_sandbox_function_name(
+ const char* func_name)
+ {
+ return INTERNAL_get_sandbox_function_ptr<T>(
+ internal_lookup_symbol(func_name));
+ }
+
+ // this is an internal function invoked from macros, so it has be public
+ template<typename T>
+ inline tainted<T*, T_Sbx> INTERNAL_get_sandbox_function_ptr(void* func_ptr)
+ {
+ return tainted<T*, T_Sbx>::internal_factory(reinterpret_cast<T*>(func_ptr));
+ }
+
+ /**
+ * @brief Create a "fake" pointer referring to a location in the application
+ * memory
+ *
+ * @param ptr The pointer to refer to
+ *
+ * @return The app_pointer object that refers to this location.
+ */
+ template<typename T>
+ app_pointer<T*, T_Sbx> get_app_pointer(T* ptr)
+ {
+ auto max_ptr = (typename T_Sbx::T_PointerType)(get_total_memory() - 1);
+ auto idx = app_ptr_map.get_app_pointer_idx((void*)ptr, max_ptr);
+ auto idx_as_ptr = this->template impl_get_unsandboxed_pointer<T>(idx);
+ // Right now we simply assume that any integer can be converted to a valid
+ // pointer in the sandbox This may not be true for some sandboxing mechanism
+ // plugins in the future In this case, we will have to come up with
+ // something more clever to construct indexes that look like valid pointers
+ // Add a check for now to make sure things work fine
+ detail::dynamic_check(is_pointer_in_sandbox_memory(idx_as_ptr),
+ "App pointers are not currently supported for this "
+ "rlbox sandbox plugin. Please file a bug.");
+ auto ret = app_pointer<T*, T_Sbx>(
+ &app_ptr_map, idx, reinterpret_cast<T*>(idx_as_ptr));
+ return ret;
+ }
+
+ /**
+ * @brief The mirror of get_app_pointer. Take a tainted pointer which is
+ * actually an app_pointer, and get the application location being pointed to
+ *
+ * @param tainted_ptr The tainted pointer that is actually an app_pointer
+ *
+ * @return The original location being referred to by the app_ptr
+ */
+ template<typename T>
+ T* lookup_app_ptr(tainted<T*, T_Sbx> tainted_ptr)
+ {
+ auto idx = tainted_ptr.get_raw_sandbox_value(*this);
+ void* ret = app_ptr_map.lookup_index(idx);
+ return reinterpret_cast<T*>(ret);
+ }
+
+#ifdef RLBOX_MEASURE_TRANSITION_TIMES
+ inline std::vector<rlbox_transition_timing>&
+ process_and_get_transition_times()
+ {
+ return transition_times;
+ }
+ inline int64_t get_total_ns_time_in_sandbox_and_transitions()
+ {
+ int64_t ret = 0;
+ for (auto& transition_time : transition_times) {
+ if (transition_time.invoke == rlbox_transition::INVOKE) {
+ ret += transition_time.time;
+ } else {
+ ret -= transition_time.time;
+ }
+ }
+ return ret;
+ }
+ inline void clear_transition_times() { transition_times.clear(); }
+#endif
+};
+
+#if defined(__clang__)
+# pragma clang diagnostic push
+# pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments"
+#elif defined(__GNUC__) || defined(__GNUG__)
+// Can't turn off the variadic macro warning emitted from -pedantic so use a
+// hack to stop GCC emitting warnings for the reminder of this file
+# pragma GCC system_header
+#elif defined(_MSC_VER)
+// Doesn't seem to emit the warning
+#else
+// Don't know the compiler... just let it go through
+#endif
+
+/**
+ * @def invoke_sandbox_function
+ * @brief Call sandbox function.
+ *
+ * @param func_name The sandboxed library function to call.
+ * @param ... Arguments to function should be simple or tainted values.
+ * @return Tainted value or void.
+ */
+#ifdef RLBOX_USE_STATIC_CALLS
+
+# define sandbox_lookup_symbol_helper(prefix, func_name) prefix(func_name)
+
+# define invoke_sandbox_function(func_name, ...) \
+ template INTERNAL_invoke_with_func_ptr<decltype(func_name)>( \
+ #func_name, \
+ sandbox_lookup_symbol_helper(RLBOX_USE_STATIC_CALLS(), func_name), \
+ ##__VA_ARGS__)
+
+# define get_sandbox_function_address(func_name) \
+ template INTERNAL_get_sandbox_function_ptr<decltype(func_name)>( \
+ sandbox_lookup_symbol_helper(RLBOX_USE_STATIC_CALLS(), func_name))
+
+#else
+
+# define invoke_sandbox_function(func_name, ...) \
+ template INTERNAL_invoke_with_func_name<decltype(func_name)>( \
+ #func_name, ##__VA_ARGS__)
+
+# define get_sandbox_function_address(func_name) \
+ template INTERNAL_get_sandbox_function_name<decltype(func_name)>(#func_name)
+
+#endif
+
+#define sandbox_invoke(sandbox, func_name, ...) \
+ (sandbox).invoke_sandbox_function(func_name, ##__VA_ARGS__)
+
+#define sandbox_function_address(sandbox, func_name) \
+ (sandbox).get_sandbox_function_address(func_name)
+
+#if defined(__clang__)
+# pragma clang diagnostic pop
+#else
+#endif
+
+}