#pragma once // IWYU pragma: private, include "rlbox.hpp" // IWYU pragma: friend "rlbox_.*\.hpp" #include #include #include "rlbox_helpers.hpp" #include "rlbox_types.hpp" #include "rlbox_unwrap.hpp" #include "rlbox_wrapper_traits.hpp" namespace rlbox { #define KEEP_CAST_FRIENDLY \ template \ typename T_C_Wrap> \ friend inline tainted sandbox_reinterpret_cast( \ const T_C_Wrap& rhs) noexcept; \ \ template \ typename T_C_Wrap> \ friend inline tainted sandbox_const_cast( \ const T_C_Wrap& rhs) noexcept; \ \ template \ typename T_C_Wrap> \ friend inline tainted sandbox_static_cast( \ const T_C_Wrap& rhs) noexcept; /** * @brief The equivalent of a reinterpret_cast but operates on sandboxed values. */ template typename T_Wrap> inline tainted sandbox_reinterpret_cast( const T_Wrap& rhs) noexcept { static_assert(detail::rlbox_is_wrapper_v> && std::is_pointer_v && std::is_pointer_v, "sandbox_reinterpret_cast on incompatible types"); tainted taintedVal = rhs; auto raw = reinterpret_cast(taintedVal.INTERNAL_unverified_safe()); auto ret = tainted::internal_factory(raw); return ret; } /** * @brief The equivalent of a const_cast but operates on sandboxed values. */ template typename T_Wrap> inline tainted sandbox_const_cast( const T_Wrap& rhs) noexcept { static_assert(detail::rlbox_is_wrapper_v>, "sandbox_const_cast on incompatible types"); tainted taintedVal = rhs; auto raw = const_cast(taintedVal.INTERNAL_unverified_safe()); auto ret = tainted::internal_factory(raw); return ret; } /** * @brief The equivalent of a static_cast but operates on sandboxed values. */ template typename T_Wrap> inline tainted sandbox_static_cast( const T_Wrap& rhs) noexcept { static_assert(detail::rlbox_is_wrapper_v>, "sandbox_static_cast on incompatible types"); tainted taintedVal = rhs; auto raw = static_cast(taintedVal.INTERNAL_unverified_safe()); auto ret = tainted::internal_factory(raw); return ret; } /** * @brief Fill sandbox memory with a constant byte. */ template typename T_Wrap> inline T_Wrap memset(rlbox_sandbox& sandbox, T_Wrap ptr, T_Val value, T_Num num) { static_assert(detail::rlbox_is_tainted_or_vol_v>, "memset called on non wrapped type"); static_assert(!std::is_const_v, "Destination is const"); auto num_val = detail::unwrap_value(num); detail::dynamic_check(num_val <= sandbox.get_total_memory(), "Called memset for memory larger than the sandbox"); tainted ptr_tainted = ptr; void* dest_start = ptr_tainted.INTERNAL_unverified_safe(); detail::check_range_doesnt_cross_app_sbx_boundary(dest_start, num_val); std::memset(dest_start, detail::unwrap_value(value), num_val); return ptr; } /** * @brief Copy to sandbox memory area. Note that memcpy is meant to be called on * byte arrays does not adjust data according to ABI differences. If the * programmer does accidentally call memcpy on buffers that needs ABI * adjustment, this may cause compatibility issues, but will not cause a * security issue as the destination is always a tainted or tainted_volatile * pointer */ template typename T_Wrap> inline T_Wrap memcpy(rlbox_sandbox& sandbox, T_Wrap dest, T_Lhs src, T_Num num) { static_assert(detail::rlbox_is_tainted_or_vol_v>, "memcpy called on non wrapped type"); static_assert(!std::is_const_v, "Destination is const"); auto num_val = detail::unwrap_value(num); detail::dynamic_check(num_val <= sandbox.get_total_memory(), "Called memcpy for memory larger than the sandbox"); tainted dest_tainted = dest; void* dest_start = dest_tainted.INTERNAL_unverified_safe(); detail::check_range_doesnt_cross_app_sbx_boundary(dest_start, num_val); // src also needs to be checked, as we don't want to allow a src rand to start // inside the sandbox and end outside, and vice versa // src may or may not be a wrapper, so use unwrap_value const void* src_start = detail::unwrap_value(src); detail::check_range_doesnt_cross_app_sbx_boundary(src_start, num_val); std::memcpy(dest_start, src_start, num_val); return dest; } /** * @brief Compare data in sandbox memory area. */ template inline tainted_int_hint memcmp(rlbox_sandbox& sandbox, T_Rhs&& dest, T_Lhs&& src, T_Num&& num) { static_assert( detail::rlbox_is_tainted_or_vol_v> || detail::rlbox_is_tainted_or_vol_v>, "memcmp called on non wrapped type"); auto num_val = detail::unwrap_value(num); detail::dynamic_check(num_val <= sandbox.get_total_memory(), "Called memcmp for memory larger than the sandbox"); void* dest_start = dest.INTERNAL_unverified_safe(); detail::check_range_doesnt_cross_app_sbx_boundary(dest_start, num_val); // src also needs to be checked, as we don't want to allow a src rand to start // inside the sandbox and end outside, and vice versa // src may or may not be a wrapper, so use unwrap_value const void* src_start = detail::unwrap_value(src); detail::check_range_doesnt_cross_app_sbx_boundary(src_start, num_val); int ret = std::memcmp(dest_start, src_start, num_val); tainted_int_hint converted_ret(ret); return converted_ret; } /** * @brief This function either * - copies the given buffer into the sandbox calling delete on the src * OR * - if the sandbox allows, adds the buffer to the existing sandbox memory * @param sandbox Target sandbox * @param src Raw pointer to the buffer * @param num Number of T-sized elements in the buffer * @param free_source_on_copy If the source buffer was copied, this variable * controls whether copy_memory_or_grant_access should call delete on the src. * This calls delete[] if num > 1. * @param copied out parameter indicating if the source was copied or transfered */ template tainted copy_memory_or_grant_access(rlbox_sandbox& sandbox, T* src, size_t num, bool free_source_on_copy, bool& copied) { copied = false; // Malloc in sandbox takes a uint32_t as the parameter, need a bounds check detail::dynamic_check(num <= std::numeric_limits::max(), "Granting access too large a region"); uint32_t num_trunc = num; // sandbox can grant access if it includes the following line // using can_grant_deny_access = void; if constexpr (detail::has_member_using_can_grant_deny_access_v) { detail::check_range_doesnt_cross_app_sbx_boundary(src, num_trunc); bool success; auto ret = sandbox.INTERNAL_grant_access(src, num_trunc, success); if (success) { return ret; } } using T_nocv = std::remove_cv_t; tainted copy = sandbox.template malloc_in_sandbox(num_trunc); if (!copy) { return nullptr; } rlbox::memcpy(sandbox, copy, src, num * sizeof(T)); if (free_source_on_copy) { free(const_cast(reinterpret_cast(src))); } copied = true; return sandbox_const_cast(copy); } /** * @brief This function either * - copies the given buffer out of the sandbox calling free_in_sandbox on the * src * OR * - if the sandbox allows, moves the buffer out of existing sandbox memory * @param sandbox Target sandbox * @param src Raw pointer to the buffer * @param num Number of bytes in the buffer * @param free_source_on_copy If the source buffer was copied, this variable * controls whether copy_memory_or_grant_access should call delete on the src. * This calls delete[] if num > 1. * @param copied out parameter indicating if the source was copied or transfered */ template typename T_Wrap> T* copy_memory_or_deny_access(rlbox_sandbox& sandbox, T_Wrap src, size_t num, bool free_source_on_copy, bool& copied) { // sandbox can grant access if it includes the following line // using can_grant_deny_access = void; if constexpr (detail::has_member_using_can_grant_deny_access_v) { detail::check_range_doesnt_cross_app_sbx_boundary( src.INTERNAL_unverified_safe(), num); bool success; auto ret = sandbox.INTERNAL_deny_access(src, num, success); if (success) { copied = false; return ret; } } auto copy = static_cast(malloc(num)); tainted src_tainted = src; char* src_raw = src_tainted.copy_and_verify_buffer_address( [](uintptr_t val) { return reinterpret_cast(val); }, num); std::memcpy(copy, src_raw, num); if (free_source_on_copy) { sandbox.free_in_sandbox(src); } copied = true; return copy; } }