#pragma once // IWYU pragma: private, include "rlbox.hpp" // IWYU pragma: friend "rlbox_.*\.hpp" #include #include #include "rlbox_helpers.hpp" #include "rlbox_struct_support.hpp" #include "rlbox_types.hpp" namespace rlbox { namespace callback_detail { // Compute the expected type of the callback template using T_Cb = std::conditional_t, void, tainted> (*)( rlbox_sandbox&, tainted...); template T_Cb callback_type_helper(T_Ret (*)(T_Args...)); // Compute the expected type of the interceptor template using T_I = detail::convert_to_sandbox_equivalent_t (*)( detail::convert_to_sandbox_equivalent_t...); template T_I interceptor_type_helper(T_Ret (*)(T_Args...)); } template class sandbox_callback { KEEP_CLASSES_FRIENDLY private: rlbox_sandbox* sandbox; using T_Callback = decltype(callback_detail::callback_type_helper(std::declval())); T_Callback callback; // The interceptor is the function that runs between the sandbox invoking the // callback and the actual callback running The interceptor is responsible for // wrapping and converting callback arguments, returns etc. to their // appropriate representations using T_Interceptor = decltype(callback_detail::interceptor_type_helper( std::declval())); T_Interceptor callback_interceptor; // The trampoline is the internal sandbox representation of the callback // Depending on the sandbox type, this could be the callback pointer directly // or a trampoline function that gates exits from the sandbox. using T_Trampoline = detail::convert_to_sandbox_equivalent_t; T_Trampoline callback_trampoline; // The unique key representing the callback to pass to unregister_callback on // destruction void* key; inline void move_obj(sandbox_callback&& other) { sandbox = other.sandbox; callback = other.callback; callback_interceptor = other.callback_interceptor; callback_trampoline = other.callback_trampoline; key = other.key; other.sandbox = nullptr; other.callback = nullptr; other.callback_interceptor = nullptr; other.callback_trampoline = 0; other.key = nullptr; } template inline void unregister_helper(T_Ret (*)(T_Args...)) { if (callback != nullptr) { // Don't need to worry about race between unregister and move as // 1) this will not happen in a correctly written program // 2) if this does happen, the worst that can happen is an invocation of a // null function pointer, which causes a crash that cannot be exploited // for RCE sandbox->template unregister_callback(key); sandbox = nullptr; callback = nullptr; callback_interceptor = nullptr; callback_trampoline = 0; key = nullptr; } } inline T_Callback get_raw_value() const noexcept { return callback; } inline T_Trampoline get_raw_sandbox_value() const noexcept { return callback_trampoline; } // Keep constructor private as only rlbox_sandbox should be able to create // this object sandbox_callback(rlbox_sandbox* p_sandbox, T_Callback p_callback, T_Interceptor p_callback_interceptor, T_Trampoline p_callback_trampoline, void* p_key) : sandbox(p_sandbox) , callback(p_callback) , callback_interceptor(p_callback_interceptor) , callback_trampoline(p_callback_trampoline) , key(p_key) { detail::dynamic_check(sandbox != nullptr, "Unexpected null sandbox when creating a callback"); } public: sandbox_callback() : sandbox(nullptr) , callback(nullptr) , callback_interceptor(nullptr) , callback_trampoline(0) , key(nullptr) {} sandbox_callback(sandbox_callback&& other) { move_obj(std::forward(other)); } inline sandbox_callback& operator=(sandbox_callback&& other) { if (this != &other) { move_obj(std::forward(other)); } return *this; } void unregister() { T dummy = nullptr; unregister_helper(dummy); } ~sandbox_callback() { unregister(); } /** * @brief Check if callback is _not_ registered. */ inline bool is_unregistered() const noexcept { return get_raw_value() == nullptr; } /** * @brief Unwrap a callback without verification. This is an unsafe operation * and should be used with care. */ inline auto UNSAFE_unverified() const noexcept { return get_raw_value(); } /** * @brief Like UNSAFE_unverified, but get the underlying sandbox * representation. * * @param sandbox Reference to sandbox. */ inline auto UNSAFE_sandboxed(rlbox_sandbox& sandbox) const noexcept { RLBOX_UNUSED(sandbox); return get_raw_sandbox_value(); } }; template class app_pointer { KEEP_CLASSES_FRIENDLY private: app_pointer_map* map; typename T_Sbx::T_PointerType idx; T idx_unsandboxed; inline void move_obj(app_pointer&& other) { map = other.map; idx = other.idx; idx_unsandboxed = other.idx_unsandboxed; other.map = nullptr; other.idx = 0; other.idx_unsandboxed = nullptr; } inline T get_raw_value() const noexcept { return to_tainted().get_raw_value(); } inline typename T_Sbx::T_PointerType get_raw_sandbox_value() const noexcept { return idx; } app_pointer(app_pointer_map* a_map, typename T_Sbx::T_PointerType a_idx, T a_idx_unsandboxed) : map(a_map) , idx(a_idx) , idx_unsandboxed(a_idx_unsandboxed) {} public: app_pointer() : map(nullptr) , idx(0) , idx_unsandboxed(0) {} ~app_pointer() { unregister(); } app_pointer(app_pointer&& other) { move_obj(std::forward(other)); } inline app_pointer& operator=(app_pointer&& other) { if (this != &other) { move_obj(std::forward(other)); } return *this; } void unregister() { if (idx != 0) { map->remove_app_ptr(idx); map = nullptr; idx = 0; idx_unsandboxed = nullptr; } } tainted to_tainted() { return tainted::internal_factory( reinterpret_cast(idx_unsandboxed)); } /** * @brief Check if app pointer is _not_ registered. */ inline bool is_unregistered() const noexcept { return idx == 0; } /** * @brief Unwrap app_pointer without verification. This is an unsafe operation * and should be used with care. */ inline auto UNSAFE_unverified() const noexcept { return get_raw_value(); } /** * @brief Like UNSAFE_unverified, but get the underlying sandbox * representation. * * @param sandbox Reference to sandbox. */ inline auto UNSAFE_sandboxed(rlbox_sandbox& sandbox) const noexcept { RLBOX_UNUSED(sandbox); return get_raw_sandbox_value(); } }; /** * @brief Tainted boolean value that serves as a "hint" and not a definite * answer. Comparisons with a tainted_volatile return such hints. They are * not `tainted` values because a compromised sandbox can modify * tainted_volatile data at any time. */ class tainted_boolean_hint { private: bool val; public: tainted_boolean_hint(bool init) : val(init) {} tainted_boolean_hint(const tainted_boolean_hint&) = default; inline tainted_boolean_hint& operator=(bool rhs) { val = rhs; return *this; } inline tainted_boolean_hint operator!() const { return tainted_boolean_hint(!val); } template inline bool unverified_safe_because(const char (&reason)[N]) const { (void)reason; /* unused */ return val; } inline bool UNSAFE_unverified() const { return val; } inline auto INTERNAL_unverified_safe() const { return UNSAFE_unverified(); } // Add a template parameter to make sure the assert only fires when called template inline bool copy_and_verify(...) const { rlbox_detail_static_fail_because( detail::true_v, "You can't call copy_and_verify on this value, as this is a result of a " "comparison with memory accessible by the sandbox. \n" "The sandbox could unexpectedly change the value leading to " "time-of-check-time-of-use attacks. \n" "You can avoid this by making a local copy of the data." "For example, if your original code, looked like \n" "if ((tainted_ptr->member == 5).copy_and_verify(...)) { ... } \n\n" "Change this to \n\n" "tainted val = tainted_ptr->member\n" "if ((val == 5).copy_and_verify(...)) { ... } \n\n" "tainted foo(rlbox_sandbox& sandbox) {...} \n\n" "Alternately, if you are sure your code is safe you can use the " "unverified_safe_because API to remove tainting\n"); // this is never executed, but we need it for the function to type-check return false; } }; /** * @brief Tainted integer value that serves as a "hint" and not a definite * answer. Comparisons with a tainted_volatile return such hints. They are * not `tainted` values because a compromised sandbox can modify * tainted_volatile data at any time. */ class tainted_int_hint { private: int val; public: tainted_int_hint(int init) : val(init) {} tainted_int_hint(const tainted_int_hint&) = default; inline tainted_int_hint& operator=(int rhs) { val = rhs; return *this; } inline tainted_boolean_hint operator!() const { return tainted_boolean_hint(!val); } template inline int unverified_safe_because(const char (&reason)[N]) const { (void)reason; /* unused */ return val; } inline int UNSAFE_unverified() const { return val; } inline auto INTERNAL_unverified_safe() const { return UNSAFE_unverified(); } // Add a template parameter to make sure the assert only fires when called template inline int copy_and_verify(...) const { rlbox_detail_static_fail_because( detail::true_v, "You can't call copy_and_verify on this value, as this is a result of a " "comparison with memory accessible by the sandbox. \n" "The sandbox could unexpectedly change the value leading to " "time-of-check-time-of-use attacks. \n" "You can avoid this by making a local copy of the data." "For example, if your original code, looked like \n" "if ((tainted_ptr->member == 5).copy_and_verify(...)) { ... } \n\n" "Change this to \n\n" "tainted val = tainted_ptr->member\n" "if ((val == 5).copy_and_verify(...)) { ... } \n\n" "tainted foo(rlbox_sandbox& sandbox) {...} \n\n" "Alternately, if you are sure your code is safe you can use the " "unverified_safe_because API to remove tainting\n"); // this is never executed, but we need it for the function to type-check return 0; } }; }