diff options
Diffstat (limited to 'third_party/rlbox/include/rlbox_sandbox.hpp')
-rw-r--r-- | third_party/rlbox/include/rlbox_sandbox.hpp | 1094 |
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..b6cebeb3d0 --- /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 + +} |