From 36d22d82aa202bb199967e9512281e9a53db42c9 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 21:33:14 +0200 Subject: Adding upstream version 115.7.0esr. Signed-off-by: Daniel Baumann --- third_party/rlbox/include/rlbox.hpp | 1349 ++++++++++++++++++++ third_party/rlbox/include/rlbox_app_pointer.hpp | 93 ++ third_party/rlbox/include/rlbox_conversion.hpp | 273 ++++ third_party/rlbox/include/rlbox_dylib_sandbox.hpp | 314 +++++ third_party/rlbox/include/rlbox_helpers.hpp | 216 ++++ third_party/rlbox/include/rlbox_noop_sandbox.hpp | 254 ++++ third_party/rlbox/include/rlbox_policy_types.hpp | 387 ++++++ third_party/rlbox/include/rlbox_range.hpp | 32 + third_party/rlbox/include/rlbox_sandbox.hpp | 1094 ++++++++++++++++ third_party/rlbox/include/rlbox_stdlib.hpp | 329 +++++ .../rlbox/include/rlbox_stdlib_polyfill.hpp | 175 +++ third_party/rlbox/include/rlbox_struct_support.hpp | 353 +++++ third_party/rlbox/include/rlbox_type_traits.hpp | 546 ++++++++ third_party/rlbox/include/rlbox_types.hpp | 87 ++ third_party/rlbox/include/rlbox_unwrap.hpp | 25 + third_party/rlbox/include/rlbox_wrapper_traits.hpp | 171 +++ 16 files changed, 5698 insertions(+) create mode 100644 third_party/rlbox/include/rlbox.hpp create mode 100644 third_party/rlbox/include/rlbox_app_pointer.hpp create mode 100644 third_party/rlbox/include/rlbox_conversion.hpp create mode 100644 third_party/rlbox/include/rlbox_dylib_sandbox.hpp create mode 100644 third_party/rlbox/include/rlbox_helpers.hpp create mode 100644 third_party/rlbox/include/rlbox_noop_sandbox.hpp create mode 100644 third_party/rlbox/include/rlbox_policy_types.hpp create mode 100644 third_party/rlbox/include/rlbox_range.hpp create mode 100644 third_party/rlbox/include/rlbox_sandbox.hpp create mode 100644 third_party/rlbox/include/rlbox_stdlib.hpp create mode 100644 third_party/rlbox/include/rlbox_stdlib_polyfill.hpp create mode 100644 third_party/rlbox/include/rlbox_struct_support.hpp create mode 100644 third_party/rlbox/include/rlbox_type_traits.hpp create mode 100644 third_party/rlbox/include/rlbox_types.hpp create mode 100644 third_party/rlbox/include/rlbox_unwrap.hpp create mode 100644 third_party/rlbox/include/rlbox_wrapper_traits.hpp (limited to 'third_party/rlbox/include') diff --git a/third_party/rlbox/include/rlbox.hpp b/third_party/rlbox/include/rlbox.hpp new file mode 100644 index 0000000000..96daaac233 --- /dev/null +++ b/third_party/rlbox/include/rlbox.hpp @@ -0,0 +1,1349 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "rlbox_app_pointer.hpp" +#include "rlbox_conversion.hpp" +#include "rlbox_helpers.hpp" +#include "rlbox_policy_types.hpp" +#include "rlbox_range.hpp" +#include "rlbox_sandbox.hpp" +#include "rlbox_stdlib.hpp" +#include "rlbox_struct_support.hpp" +#include "rlbox_type_traits.hpp" +#include "rlbox_types.hpp" +#include "rlbox_unwrap.hpp" +#include "rlbox_wrapper_traits.hpp" + +namespace rlbox { + +template typename T_Wrap, + typename T, + typename T_Sbx> +class tainted_base_impl +{ + KEEP_CLASSES_FRIENDLY + KEEP_CAST_FRIENDLY + +public: + inline auto& impl() { return *static_cast*>(this); } + inline auto& impl() const + { + return *static_cast*>(this); + } + + /** + * @brief Unwrap a tainted value without verification. This is an unsafe + * operation and should be used with care. + */ + inline auto UNSAFE_unverified() const { return impl().get_raw_value(); } + /** + * @brief Like UNSAFE_unverified, but get the underlying sandbox + * representation. + * + * @param sandbox Reference to sandbox. + * + * For the Wasm-based sandbox, this function additionally validates the + * unwrapped value against the machine model of the sandbox (LP32). + */ + inline auto UNSAFE_sandboxed(rlbox_sandbox& sandbox) const + { + return impl().get_raw_sandbox_value(sandbox); + } + + /** + * @brief Unwrap a tainted value without verification. This function should + * be used when unwrapping is safe. + * + * @param reason An explanation why the unverified unwrapping is safe. + */ + template + inline auto unverified_safe_because(const char (&reason)[N]) const + { + RLBOX_UNUSED(reason); + static_assert(!std::is_pointer_v, + "unverified_safe_because does not support pointers. Use " + "unverified_safe_pointer_because."); + return UNSAFE_unverified(); + } + + template + inline auto unverified_safe_pointer_because(size_t count, + const char (&reason)[N]) const + { + RLBOX_UNUSED(reason); + + static_assert(std::is_pointer_v, "Expected pointer type"); + using T_Pointed = std::remove_pointer_t; + if_constexpr_named(cond1, std::is_pointer_v) + { + rlbox_detail_static_fail_because( + cond1, + "There is no way to use unverified_safe_pointer_because for " + "'pointers to pointers' safely. Use copy_and_verify instead."); + return nullptr; + } + + auto ret = UNSAFE_unverified(); + if (ret != nullptr) { + size_t bytes = sizeof(T) * count; + detail::check_range_doesnt_cross_app_sbx_boundary(ret, bytes); + } + return ret; + } + + inline auto INTERNAL_unverified_safe() const { return UNSAFE_unverified(); } + +#define BinaryOpValAndPtr(opSymbol) \ + template \ + inline constexpr auto operator opSymbol(const T_Rhs& rhs) \ + const->tainted() opSymbol std::declval< \ + detail::rlbox_remove_wrapper_t>()), \ + T_Sbx> \ + { \ + static_assert(detail::is_basic_type_v, \ + "Operator " #opSymbol \ + " only supported for primitive and pointer types"); \ + \ + auto raw_rhs = detail::unwrap_value(rhs); \ + \ + if constexpr (std::is_pointer_v) { \ + static_assert(std::is_integral_v, \ + "Can only operate on numeric types"); \ + auto ptr = impl().get_raw_value(); \ + detail::dynamic_check(ptr != nullptr, \ + "Pointer arithmetic on a null pointer"); \ + /* increment the target by size of the data structure */ \ + auto target = \ + reinterpret_cast(ptr) opSymbol raw_rhs * sizeof(*impl()); \ + auto no_overflow = rlbox_sandbox::is_in_same_sandbox( \ + reinterpret_cast(ptr), \ + reinterpret_cast(target)); \ + detail::dynamic_check( \ + no_overflow, \ + "Pointer arithmetic overflowed a pointer beyond sandbox memory"); \ + \ + return tainted::internal_factory(reinterpret_cast(target)); \ + } else { \ + auto raw = impl().get_raw_value(); \ + auto ret = raw opSymbol raw_rhs; \ + using T_Ret = decltype(ret); \ + return tainted::internal_factory(ret); \ + } \ + } \ + RLBOX_REQUIRE_SEMI_COLON + + BinaryOpValAndPtr(+); + BinaryOpValAndPtr(-); + +#undef BinaryOpValAndPtr + +#define BinaryOp(opSymbol) \ + template \ + inline constexpr auto operator opSymbol(const T_Rhs& rhs) \ + const->tainted() opSymbol std::declval< \ + detail::rlbox_remove_wrapper_t>()), \ + T_Sbx> \ + { \ + static_assert(detail::is_fundamental_or_enum_v, \ + "Operator " #opSymbol \ + " only supported for primitive types"); \ + \ + auto raw = impl().get_raw_value(); \ + auto raw_rhs = detail::unwrap_value(rhs); \ + static_assert(std::is_integral_v \ + || std::is_floating_point_v, \ + "Can only operate on numeric types"); \ + \ + auto ret = raw opSymbol raw_rhs; \ + using T_Ret = decltype(ret); \ + return tainted::internal_factory(ret); \ + } \ + RLBOX_REQUIRE_SEMI_COLON + + BinaryOp(*); + BinaryOp(/); + BinaryOp(%); + BinaryOp(^); + BinaryOp(&); + BinaryOp(|); + BinaryOp(<<); + BinaryOp(>>); + +#undef BinaryOp + +#define CompoundAssignmentOp(opSymbol) \ + template \ + inline constexpr T_Wrap& operator opSymbol##=(const T_Rhs& rhs) \ + { \ + auto& this_ref = impl(); \ + this_ref = this_ref opSymbol rhs; \ + return this_ref; \ + } \ + RLBOX_REQUIRE_SEMI_COLON + + CompoundAssignmentOp(+); + CompoundAssignmentOp(-); + CompoundAssignmentOp(*); + CompoundAssignmentOp(/); + CompoundAssignmentOp(%); + CompoundAssignmentOp(^); + CompoundAssignmentOp(&); + CompoundAssignmentOp(|); + CompoundAssignmentOp(<<); + CompoundAssignmentOp(>>); + +#undef CompoundAssignmentOp + +#define PreIncDecOps(opSymbol) \ + inline constexpr T_Wrap& operator opSymbol##opSymbol() \ + { \ + auto& this_ref = impl(); \ + this_ref = this_ref opSymbol 1; \ + return this_ref; \ + } \ + RLBOX_REQUIRE_SEMI_COLON + + PreIncDecOps(+); + PreIncDecOps(-); + +#undef PreIncDecOps + +#define PostIncDecOps(opSymbol) \ + inline constexpr T_Wrap operator opSymbol##opSymbol(int) \ + { \ + tainted ret = impl(); \ + operator++(); \ + return ret; \ + } \ + RLBOX_REQUIRE_SEMI_COLON + + PostIncDecOps(+); + PostIncDecOps(-); + +#undef PostIncDecOps + +#define BooleanBinaryOp(opSymbol) \ + template \ + inline constexpr auto operator opSymbol(const T_Rhs& rhs) \ + const->tainted() opSymbol std::declval< \ + detail::rlbox_remove_wrapper_t>()), \ + T_Sbx> \ + { \ + static_assert(detail::is_fundamental_or_enum_v, \ + "Operator " #opSymbol \ + " only supported for primitive types"); \ + \ + auto raw = impl().get_raw_value(); \ + auto raw_rhs = detail::unwrap_value(rhs); \ + static_assert(std::is_integral_v, \ + "Can only operate on numeric types"); \ + \ + auto ret = raw opSymbol raw_rhs; \ + using T_Ret = decltype(ret); \ + return tainted::internal_factory(ret); \ + } \ + \ + template \ + inline constexpr auto operator opSymbol(const T_Rhs&&) \ + const->tainted() opSymbol std::declval< \ + detail::rlbox_remove_wrapper_t>()), \ + T_Sbx> \ + { \ + rlbox_detail_static_fail_because( \ + detail::true_v, \ + "C++ does not permit safe overloading of && and || operations as this " \ + "affects the short circuiting behaviour of these operations. RLBox " \ + "does let you use && and || with tainted in limited situations - when " \ + "all arguments starting from the second are local variables. It does " \ + "not allow it if arguments starting from the second are expressions.\n" \ + "For example the following is not allowed\n" \ + "\n" \ + "tainted a = true;\n" \ + "auto r = a && true && sandbox.invoke_sandbox_function(getBool);\n" \ + "\n" \ + "However the following would be allowed\n" \ + "tainted a = true;\n" \ + "auto b = true\n" \ + "auto c = sandbox.invoke_sandbox_function(getBool);\n" \ + "auto r = a && b && c;\n" \ + "\n" \ + "Note that these 2 programs are not identical. The first program may " \ + "or may not call getBool, while second program always calls getBool"); \ + return tainted(false); \ + } \ + RLBOX_REQUIRE_SEMI_COLON + + BooleanBinaryOp(&&); + BooleanBinaryOp(||); + +#undef BooleanBinaryOp + +#define UnaryOp(opSymbol) \ + inline auto operator opSymbol() \ + { \ + static_assert(detail::is_fundamental_or_enum_v, \ + "Operator " #opSymbol " only supported for primitive"); \ + \ + auto raw = impl().get_raw_value(); \ + auto ret = opSymbol raw; \ + using T_Ret = decltype(ret); \ + return tainted::internal_factory(ret); \ + } \ + RLBOX_REQUIRE_SEMI_COLON + + UnaryOp(-); + UnaryOp(~); + +#undef UnaryOp + +/** + * @brief Comparison operators. Comparisons to values in sandbox memory can + * only return a "tainted_boolean_hint" as the values in memory can be + * incorrect or malicously change in the future. + * + * @tparam T_Rhs + * @param rhs + * @return One of either a bool, tainted, or a tainted_boolean_hint + * depending on the arguments to the binary expression. + */ +#define CompareOp(opSymbol, permit_pointers) \ + template \ + inline constexpr auto operator opSymbol(const T_Rhs& rhs) const \ + { \ + using T_RhsNoQ = detail::remove_cv_ref_t; \ + constexpr bool check_rhs_hint = \ + detail::rlbox_is_tainted_volatile_v || \ + detail::rlbox_is_tainted_boolean_hint_v; \ + constexpr bool check_lhs_hint = \ + detail::rlbox_is_tainted_volatile_v>; \ + constexpr bool is_hint = check_lhs_hint || check_rhs_hint; \ + \ + constexpr bool is_unwrapped = \ + detail::rlbox_is_tainted_v> && \ + std::is_null_pointer_v; \ + \ + /* Sanity check - can't be a hint and unwrapped */ \ + static_assert(is_hint ? !is_unwrapped : true, \ + "Internal error: Could not deduce type for comparison. " \ + "Please file a bug."); \ + \ + if constexpr (!permit_pointers && std::is_pointer_v) { \ + rlbox_detail_static_fail_because( \ + std::is_pointer_v, \ + "Only == and != comparisons are allowed for pointers"); \ + } \ + \ + bool ret = (impl().get_raw_value() opSymbol detail::unwrap_value(rhs)); \ + \ + if constexpr (is_hint) { \ + return tainted_boolean_hint(ret); \ + } else if constexpr (is_unwrapped) { \ + return ret; \ + } else { \ + return tainted(ret); \ + } \ + } \ + RLBOX_REQUIRE_SEMI_COLON + + CompareOp(==, true /* permit_pointers */); + CompareOp(!=, true /* permit_pointers */); + CompareOp(<, false /* permit_pointers */); + CompareOp(<=, false /* permit_pointers */); + CompareOp(>, false /* permit_pointers */); + CompareOp(>=, false /* permit_pointers */); + +#undef CompareOp + +private: + using T_OpSubscriptArrRet = std::conditional_t< + std::is_pointer_v, + tainted_volatile, T_Sbx>, // is_pointer + T_Wrap, T_Sbx> // is_array + >; + +public: + template + inline const T_OpSubscriptArrRet& operator[](T_Rhs&& rhs) const + { + static_assert(std::is_pointer_v || detail::is_c_or_std_array_v, + "Operator [] supports pointers and arrays only"); + + auto raw_rhs = detail::unwrap_value(rhs); + static_assert(std::is_integral_v, + "Can only index with numeric types"); + + if constexpr (std::is_pointer_v) { + auto ptr = this->impl().get_raw_value(); + + // increment the target by size of the data structure + auto target = + reinterpret_cast(ptr) + raw_rhs * sizeof(*this->impl()); + auto no_overflow = rlbox_sandbox::is_in_same_sandbox( + ptr, reinterpret_cast(target)); + detail::dynamic_check( + no_overflow, + "Pointer arithmetic overflowed a pointer beyond sandbox memory"); + + auto target_wrap = tainted::internal_factory( + reinterpret_cast(target)); + return *target_wrap; + } else { + using T_Rhs_Unsigned = std::make_unsigned_t; + detail::dynamic_check( + raw_rhs >= 0 && static_cast(raw_rhs) < + std::extent_v, 0>, + "Static array indexing overflow"); + + const void* target_ptr; + if constexpr (detail::rlbox_is_tainted_v>) { + auto& data_ref = impl().get_raw_value_ref(); + target_ptr = &(data_ref[raw_rhs]); + } else { + auto& data_ref = impl().get_sandbox_value_ref(); + auto target_ptr_vol = &(data_ref[raw_rhs]); + // target_ptr is a volatile... remove this. + // Safe as we will return a tainted_volatile if this is the case + target_ptr = detail::remove_volatile_from_ptr_cast(target_ptr_vol); + } + + using T_Target = const T_Wrap, T_Sbx>; + auto wrapped_target_ptr = reinterpret_cast(target_ptr); + return *wrapped_target_ptr; + } + } + + template + inline T_OpSubscriptArrRet& operator[](T_Rhs&& rhs) + { + return const_cast(std::as_const(*this)[rhs]); + } + +private: + using T_OpDerefRet = tainted_volatile, T_Sbx>; + +public: + inline T_OpDerefRet& operator*() const + { + static_assert(std::is_pointer_v, "Operator * only allowed on pointers"); + auto ret_ptr_const = + reinterpret_cast(impl().get_raw_value()); + // Safe - If T_OpDerefRet is not a const ptr, this is trivially safe + // If T_OpDerefRet is a const ptr, then the const is captured + // inside the wrapper + auto ret_ptr = const_cast(ret_ptr_const); + return *ret_ptr; + } + + // We need to implement the -> operator even if T is not a struct + // So that we can support code patterns such as the below + // tainted a; + // a->UNSAFE_unverified(); + inline const T_OpDerefRet* operator->() const + { + static_assert(std::is_pointer_v, + "Operator -> only supported for pointer types"); + return reinterpret_cast(impl().get_raw_value()); + } + + inline T_OpDerefRet* operator->() + { + return const_cast(std::as_const(*this).operator->()); + } + + inline auto operator!() + { + if_constexpr_named(cond1, std::is_pointer_v) + { + return impl() == nullptr; + } + else if_constexpr_named(cond2, std::is_same_v, bool>) + { + return impl() == false; + } + else + { + auto unknownCase = !(cond1 || cond2); + rlbox_detail_static_fail_because( + unknownCase, + "Operator ! only permitted for pointer or boolean types. For other" + "types, unwrap the tainted value with the copy_and_verify API and then" + "use operator !"); + } + } + + /** + * @brief Copy tainted value from sandbox and verify it. + * + * @param verifier Function used to verify the copied value. + * @tparam T_Func the type of the verifier. + * @return Whatever the verifier function returns. + */ + template + inline auto copy_and_verify(T_Func verifier) const + { + using T_Deref = std::remove_cv_t>; + + if_constexpr_named(cond1, detail::is_fundamental_or_enum_v) + { + auto val = impl().get_raw_value(); + return verifier(val); + } + else if_constexpr_named( + cond2, detail::is_one_level_ptr_v && !std::is_class_v) + { + // Some paths don't use the verifier + RLBOX_UNUSED(verifier); + + if_constexpr_named(subcond1, std::is_void_v) + { + rlbox_detail_static_fail_because( + subcond1, + "copy_and_verify not recommended for void* as it could lead to some " + "anti-patterns in verifiers. Cast it to a different tainted pointer " + "with sandbox_reinterpret_cast and then call copy_and_verify. " + "Alternately, you can use the UNSAFE_unverified API to do this " + "without casting."); + return nullptr; + } + // Test with detail::is_func_ptr_v to check for member funcs also + else if_constexpr_named(subcond2, detail::is_func_ptr_v) + { + rlbox_detail_static_fail_because( + subcond2, + "copy_and_verify cannot be applied to function pointers as this " + "makes a deep copy. This is not possible for function pointers. " + "Consider copy_and_verify_address instead."); + return nullptr; + } + else + { + auto val = impl().get_raw_value(); + if (val == nullptr) { + return verifier(nullptr); + } else { + // Important to assign to a local variable (i.e. make a copy) + // Else, for tainted_volatile, this will allow a + // time-of-check-time-of-use attack + auto val_copy = std::make_unique(); + *val_copy = *val; + return verifier(std::move(val_copy)); + } + } + } + else if_constexpr_named( + cond3, detail::is_one_level_ptr_v && std::is_class_v) + { + auto val_copy = std::make_unique>(*impl()); + return verifier(std::move(val_copy)); + } + else if_constexpr_named(cond4, std::is_array_v) + { + static_assert( + detail::is_fundamental_or_enum_v>, + "copy_and_verify on arrays is only safe for fundamental or enum types. " + "For arrays of other types, apply copy_and_verify on each element " + "individually --- a[i].copy_and_verify(...)"); + + auto copy = impl().get_raw_value(); + return verifier(copy); + } + else + { + auto unknownCase = !(cond1 || cond2 || cond3 || cond4); + rlbox_detail_static_fail_because( + unknownCase, + "copy_and_verify not supported for this type as it may be unsafe"); + } + } + +private: + using T_CopyAndVerifyRangeEl = + detail::valid_array_el_t>>; + + // Template needed to ensure that function isn't instantiated for unsupported + // types like function pointers which causes compile errors... + template + inline const void* verify_range_helper(std::size_t count) const + { + static_assert(std::is_pointer_v); + static_assert(detail::is_fundamental_or_enum_v); + + detail::dynamic_check( + count != 0, + "Called copy_and_verify_range/copy_and_verify_string with count 0"); + + auto start = reinterpret_cast(impl().get_raw_value()); + if (start == nullptr) { + return nullptr; + } + + detail::check_range_doesnt_cross_app_sbx_boundary( + start, count * sizeof(T_CopyAndVerifyRangeEl)); + + return start; + } + + template + inline std::unique_ptr copy_and_verify_range_helper( + std::size_t count) const + { + const void* start = verify_range_helper(count); + if (start == nullptr) { + return nullptr; + } + + auto target = std::make_unique(count); + + for (size_t i = 0; i < count; i++) { + auto p_src_i_tainted = &(impl()[i]); + auto p_src_i = p_src_i_tainted.get_raw_value(); + detail::convert_type_fundamental_or_array(target[i], *p_src_i); + } + + return target; + } + +public: + /** + * @brief Copy a range of tainted values from sandbox and verify them. + * + * @param verifier Function used to verify the copied value. + * @param count Number of elements to copy. + * @tparam T_Func the type of the verifier. If the tainted type is ``int*`` + * then ``T_Func = T_Ret(*)(unique_ptr)``. + * @return Whatever the verifier function returns. + */ + template + inline auto copy_and_verify_range(T_Func verifier, std::size_t count) const + { + static_assert(std::is_pointer_v, + "Can only call copy_and_verify_range on pointers"); + + static_assert( + detail::is_fundamental_or_enum_v, + "copy_and_verify_range is only safe for ranges of " + "fundamental or enum types. For other types, call " + "copy_and_verify on each element --- a[i].copy_and_verify(...)"); + + std::unique_ptr target = + copy_and_verify_range_helper(count); + return verifier(std::move(target)); + } + + /** + * @brief Copy a tainted string from sandbox and verify it. + * + * @param verifier Function used to verify the copied value. + * @tparam T_Func the type of the verifier either + * ``T_Ret(*)(unique_ptr)`` or ``T_Ret(*)(std::string)`` + * @return Whatever the verifier function returns. + */ + template + inline auto copy_and_verify_string(T_Func verifier) const + { + static_assert(std::is_pointer_v, + "Can only call copy_and_verify_string on pointers"); + + static_assert(std::is_same_v, + "copy_and_verify_string only allows char*"); + + using T_VerifParam = detail::func_first_arg_t; + + auto start = impl().get_raw_value(); + if_constexpr_named( + cond1, + std::is_same_v> || + std::is_same_v>) + { + if (start == nullptr) { + return verifier(nullptr); + } + + // it is safe to run strlen on a tainted as worst case, the string + // does not have a null and we try to copy all the memory out of the + // sandbox however, copy_and_verify_range ensures that we never copy + // memory outsider the range + auto str_len = std::strlen(start) + 1; + std::unique_ptr target = + copy_and_verify_range_helper(str_len); + + // ensure the string has a trailing null + target[str_len - 1] = '\0'; + + return verifier(std::move(target)); + } + else if_constexpr_named(cond2, std::is_same_v) + { + if (start == nullptr) { + std::string param = ""; + return verifier(param); + } + + // it is safe to run strlen on a tainted as worst case, the string + // does not have a null and we try to copy all the memory out of the + // sandbox however, copy_and_verify_range ensures that we never copy + // memory outsider the range + auto str_len = std::strlen(start) + 1; + + const char* checked_start = (const char*)verify_range_helper(str_len); + if (checked_start == nullptr) { + std::string param = ""; + return verifier(param); + } + + std::string copy(checked_start, str_len - 1); + return verifier(std::move(copy)); + } + else + { + constexpr bool unknownCase = !(cond1 || cond2); + rlbox_detail_static_fail_because( + unknownCase, + "copy_and_verify_string verifier parameter should either be " + "unique_ptr, unique_ptr or std::string"); + } + } + + /** + * @brief Copy a tainted pointer from sandbox and verify the address. + * + * This function is useful if you need to verify physical bits representing + * the address of a pointer. Other APIs such as copy_and_verify performs a + * deep copy and changes the address bits. + * + * @param verifier Function used to verify the copied value. + * @tparam T_Func the type of the verifier ``T_Ret(*)(uintptr_t)`` + * @return Whatever the verifier function returns. + */ + template + inline auto copy_and_verify_address(T_Func verifier) const + { + static_assert(std::is_pointer_v, + "copy_and_verify_address must be used on pointers"); + auto val = reinterpret_cast(impl().get_raw_value()); + return verifier(val); + } + + /** + * @brief Copy a tainted pointer to a buffer from sandbox and verify the + * address. + * + * This function is useful if you need to verify physical bits representing + * the address of a buffer. Other APIs such as copy_and_verify performs a + * deep copy and changes the address bits. + * + * @param verifier Function used to verify the copied value. + * @param size Size of the buffer. Buffer with length size is expected to fit + * inside sandbox memory. + * @tparam T_Func the type of the verifier ``T_Ret(*)(uintptr_t)`` + * @return Whatever the verifier function returns. + */ + template + inline auto copy_and_verify_buffer_address(T_Func verifier, + std::size_t size) const + { + static_assert(std::is_pointer_v, + "copy_and_verify_address must be used on pointers"); + auto val = reinterpret_cast(verify_range_helper(size)); + return verifier(val); + } +}; + +#define BinaryOpWrappedRhs(opSymbol) \ + template typename T_Wrap, \ + typename T, \ + typename T_Sbx, \ + typename T_Lhs, \ + RLBOX_ENABLE_IF(!detail::rlbox_is_wrapper_v && \ + !detail::rlbox_is_tainted_boolean_hint_v)> \ + inline constexpr auto operator opSymbol( \ + const T_Lhs& lhs, const tainted_base_impl& rhs) \ + { \ + /* Handles the case for "3 + tainted", where + is a binary op */ \ + /* Technically pointer arithmetic can be performed as 3 + tainted_ptr */ \ + /* as well. However, this is unusual and to keep the code simple we do */ \ + /* not support this. */ \ + static_assert( \ + std::is_arithmetic_v, \ + "Binary expressions between an non tainted type and tainted" \ + "type is only permitted if the first value is the tainted type. Try " \ + "changing the order of the binary expression accordingly"); \ + auto ret = tainted(lhs) opSymbol rhs.impl(); \ + return ret; \ + } \ + RLBOX_REQUIRE_SEMI_COLON + +BinaryOpWrappedRhs(+); +BinaryOpWrappedRhs(-); +BinaryOpWrappedRhs(*); +BinaryOpWrappedRhs(/); +BinaryOpWrappedRhs(%); +BinaryOpWrappedRhs(^); +BinaryOpWrappedRhs(&); +BinaryOpWrappedRhs(|); +BinaryOpWrappedRhs(<<); +BinaryOpWrappedRhs(>>); +BinaryOpWrappedRhs(==); +BinaryOpWrappedRhs(!=); +BinaryOpWrappedRhs(<); +BinaryOpWrappedRhs(<=); +BinaryOpWrappedRhs(>); +BinaryOpWrappedRhs(>=); +#undef BinaryOpWrappedRhs + +#define BooleanBinaryOpWrappedRhs(opSymbol) \ + template typename T_Wrap, \ + typename T, \ + typename T_Sbx, \ + typename T_Lhs, \ + RLBOX_ENABLE_IF(!detail::rlbox_is_wrapper_v && \ + !detail::rlbox_is_tainted_boolean_hint_v)> \ + inline constexpr auto operator opSymbol( \ + const T_Lhs& lhs, const tainted_base_impl& rhs) \ + { \ + static_assert( \ + std::is_arithmetic_v, \ + "Binary expressions between an non tainted type and tainted" \ + "type is only permitted if the first value is the tainted type. Try " \ + "changing the order of the binary expression accordingly"); \ + auto ret = tainted(lhs) opSymbol rhs.impl(); \ + return ret; \ + } \ + \ + template typename T_Wrap, \ + typename T, \ + typename T_Sbx, \ + typename T_Lhs, \ + RLBOX_ENABLE_IF(!detail::rlbox_is_wrapper_v && \ + !detail::rlbox_is_tainted_boolean_hint_v)> \ + inline constexpr auto operator opSymbol( \ + const T_Lhs&, const tainted_base_impl&&) \ + { \ + rlbox_detail_static_fail_because( \ + detail::true_v, \ + "C++ does not permit safe overloading of && and || operations as this " \ + "affects the short circuiting behaviour of these operations. RLBox " \ + "does let you use && and || with tainted in limited situations - when " \ + "all arguments starting from the second are local variables. It does " \ + "not allow it if arguments starting from the second are expressions.\n" \ + "For example the following is not allowed\n" \ + "\n" \ + "tainted a = true;\n" \ + "auto r = a && true && sandbox.invoke_sandbox_function(getBool);\n" \ + "\n" \ + "However the following would be allowed\n" \ + "tainted a = true;\n" \ + "auto b = true\n" \ + "auto c = sandbox.invoke_sandbox_function(getBool);\n" \ + "auto r = a && b && c;\n" \ + "\n" \ + "Note that these 2 programs are not identical. The first program may " \ + "or may not call getBool, while second program always calls getBool"); \ + return tainted(false); \ + } \ + RLBOX_REQUIRE_SEMI_COLON + +BooleanBinaryOpWrappedRhs(&&); +BooleanBinaryOpWrappedRhs(||); +#undef BooleanBinaryOpWrappedRhs + +namespace tainted_detail { + template + using tainted_repr_t = detail::c_to_std_array_t; + + template + using tainted_vol_repr_t = + detail::c_to_std_array_t::template convert_to_sandbox_equivalent_nonclass_t>>; +} + +/** + * @brief Tainted values represent untrusted values that originate from the + * sandbox. + */ +template +class tainted : public tainted_base_impl +{ + KEEP_CLASSES_FRIENDLY + KEEP_CAST_FRIENDLY + + // Classes recieve their own specialization + static_assert( + !std::is_class_v, + "Missing definition for class T. This error occurs for one " + "of 2 reasons.\n" + " 1) Make sure you have include a call rlbox_load_structs_from_library " + "for this library with this class included.\n" + " 2) Make sure you run (re-run) the struct-dump tool to list " + "all structs in use by your program.\n"); + + static_assert( + detail::is_basic_type_v || std::is_array_v, + "Tainted types only support fundamental, enum, pointer, array and struct " + "types. Please file a bug if more support is needed."); + +private: + using T_ClassBase = tainted_base_impl; + using T_AppType = tainted_detail::tainted_repr_t; + using T_SandboxedType = tainted_detail::tainted_vol_repr_t; + T_AppType data; + + inline auto& get_raw_value_ref() noexcept { return data; } + inline auto& get_raw_value_ref() const noexcept { return data; } + + inline std::remove_cv_t get_raw_value() const noexcept + { + return data; + } + + inline std::remove_cv_t get_raw_sandbox_value( + rlbox_sandbox& sandbox) const + { + std::remove_cv_t ret; + + using namespace detail; + convert_type_non_class( + ret, data, nullptr /* example_unsandboxed_ptr */, &sandbox); + return ret; + }; + + inline const void* find_example_pointer_or_null() const noexcept + { + if constexpr (std::is_array_v) { + auto& data_ref = get_raw_value_ref(); + + for (size_t i = 0; i < std::extent_v; i++) { + const void* ret = data[i].find_example_pointer_or_null(); + if (ret != nullptr) { + return ret; + } + } + } else if constexpr (std::is_pointer_v && !detail::is_func_ptr_v) { + auto data = get_raw_value(); + return data; + } + return nullptr; + } + + // Initializing with a pointer is dangerous and permitted only internally + template)> + tainted(T2 val, const void* /* internal_tag */) + : data(val) + { + // Sanity check + static_assert(std::is_pointer_v); + } + + template + static inline tainted internal_factory(T_Rhs&& rhs) + { + if constexpr (std::is_pointer_v>) { + const void* internal_tag = nullptr; + return tainted(std::forward(rhs), internal_tag); + } else { + return tainted(std::forward(rhs)); + } + } + +public: + tainted() = default; + tainted(const tainted& p) = default; + + tainted(const tainted_volatile& p) + { + // Need to construct an example_unsandboxed_ptr for pointers or arrays of + // pointers. Since tainted_volatile is the type of data in sandbox memory, + // the address of data (&data) refers to a location in sandbox memory and + // can thus be the example_unsandboxed_ptr + const volatile void* p_data_ref = &p.get_sandbox_value_ref(); + const void* example_unsandboxed_ptr = const_cast(p_data_ref); + using namespace detail; + convert_type_non_class( + get_raw_value_ref(), + p.get_sandbox_value_ref(), + example_unsandboxed_ptr, + nullptr /* sandbox_ptr */); + } + + // Initializing with a pointer is dangerous and permitted only internally + template)> + tainted(T2 val) + : data(val) + { + rlbox_detail_static_fail_because( + std::is_pointer_v, + "Assignment of pointers is not safe as it could\n " + "1) Leak pointers from the appliction to the sandbox which may break " + "ASLR\n " + "2) Pass inaccessible pointers to the sandbox leading to crash\n " + "3) Break sandboxes that require pointers to be swizzled first\n " + "\n " + "Instead, if you want to pass in a pointer, do one of the following\n " + "1) Allocate with malloc_in_sandbox, and pass in a tainted pointer\n " + "2) For pointers that point to functions in the application, register " + "with sandbox.register_callback(\"foo\"), and pass in the registered " + "value\n " + "3) For pointers that point to functions in the sandbox, get the " + "address with get_sandbox_function_address(sandbox, foo), and pass in " + "the " + "address\n " + "4) For raw pointers, use assign_raw_pointer which performs required " + "safety checks\n "); + } + + tainted( + const sandbox_callback< + detail::function_ptr_t // Need to ensure we never generate code that + // creates a sandbox_callback of a non function + , + T_Sbx>&) + { + rlbox_detail_static_fail_because( + detail::true_v, + "RLBox does not support assigning sandbox_callback values to tainted " + "types (i.e. types that live in application memory).\n" + "If you still want to do this, consider changing your code to store the " + "value in sandbox memory as follows. Convert\n\n" + "sandbox_callback cb = ...;\n" + "tainted foo = cb;\n\n" + "to\n\n" + "tainted foo_ptr = sandbox.malloc_in_sandbox();\n" + "*foo_ptr = cb;\n\n" + "This would keep the assignment in sandbox memory"); + } + + tainted(const std::nullptr_t& arg) + : data(arg) + { + static_assert(std::is_pointer_v); + } + + // We explicitly disable this constructor if it has one of the signatures + // above, so that we give the above constructors a higher priority. We only + // allow this for fundamental types as this is potentially unsafe for pointers + // and structs + template> && + detail::is_fundamental_or_enum_v && + detail::is_fundamental_or_enum_v>)> + tainted(T_Arg&& arg) + : data(std::forward(arg)) + {} + + template + void assign_raw_pointer(rlbox_sandbox& sandbox, T_Rhs val) + { + static_assert(std::is_pointer_v, "Must be a pointer"); + static_assert(std::is_assignable_v, + "Should assign pointers of compatible types."); + // Maybe a function pointer, so we need to cast + const void* cast_val = reinterpret_cast(val); + bool safe = sandbox.is_pointer_in_sandbox_memory(cast_val); + detail::dynamic_check( + safe, + "Tried to assign a pointer that is not in the sandbox.\n " + "This is not safe as it could\n " + "1) Leak pointers from the appliction to the sandbox which may break " + "ASLR\n " + "2) Pass inaccessible pointers to the sandbox leading to crash\n " + "3) Break sandboxes that require pointers to be swizzled first\n " + "\n " + "Instead, if you want to pass in a pointer, do one of the following\n " + "1) Allocate with malloc_in_sandbox, and pass in a tainted pointer\n " + "2) For pointers that point to functions in the application, register " + "with sandbox.register_callback(\"foo\"), and pass in the registered " + "value\n " + "3) For pointers that point to functions in the sandbox, get the " + "address with get_sandbox_function_address(sandbox, foo), and pass in " + "the " + "address\n "); + data = val; + } + + inline tainted_opaque to_opaque() + { + return *reinterpret_cast*>(this); + } + + template + operator bool() const + { + if_constexpr_named(cond1, std::is_pointer_v) + { + // We return this without the tainted wrapper as the checking for null + // doesn't really "induce" tainting in the application If the + // application is checking this pointer for null, then it is robust to + // this pointer being null or not null + return get_raw_value() != nullptr; + } + else + { + auto unknownCase = !(cond1); + rlbox_detail_static_fail_because( + unknownCase, + "Implicit conversion to bool is only permitted for pointer types. For " + "other types, unwrap the tainted value with the copy_and_verify API " + "and then perform the required checks"); + } + } +}; + +template +inline tainted from_opaque(tainted_opaque val) +{ + return *reinterpret_cast*>(&val); +} + +/** + * @brief Tainted volatile values are like tainted values but still point to + * sandbox memory. Dereferencing a tainted pointer produces a tainted_volatile. + */ +template +class tainted_volatile : public tainted_base_impl +{ + KEEP_CLASSES_FRIENDLY + KEEP_CAST_FRIENDLY + + // Classes recieve their own specialization + static_assert( + !std::is_class_v, + "Missing definition for class T. This error occurs for one " + "of 2 reasons.\n" + " 1) Make sure you have include a call rlbox_load_structs_from_library " + "for this library with this class included.\n" + " 2) Make sure you run (re-run) the struct-dump tool to list " + "all structs in use by your program.\n"); + + static_assert( + detail::is_basic_type_v || std::is_array_v, + "Tainted types only support fundamental, enum, pointer, array and struct " + "types. Please file a bug if more support is needed."); + +private: + using T_ClassBase = tainted_base_impl; + using T_AppType = tainted_detail::tainted_repr_t; + using T_SandboxedType = tainted_detail::tainted_vol_repr_t; + T_SandboxedType data; + + inline auto& get_sandbox_value_ref() noexcept { return data; } + inline auto& get_sandbox_value_ref() const noexcept { return data; } + + inline std::remove_cv_t get_raw_value() const + { + std::remove_cv_t ret; + // Need to construct an example_unsandboxed_ptr for pointers or arrays of + // pointers. Since tainted_volatile is the type of data in sandbox memory, + // the address of data (&data) refers to a location in sandbox memory and + // can thus be the example_unsandboxed_ptr + const volatile void* data_ref = &data; + const void* example_unsandboxed_ptr = const_cast(data_ref); + using namespace detail; + convert_type_non_class( + ret, data, example_unsandboxed_ptr, nullptr /* sandbox_ptr */); + return ret; + } + + inline std::remove_cv_t get_raw_sandbox_value() + const noexcept + { + return data; + }; + + inline std::remove_cv_t get_raw_sandbox_value( + rlbox_sandbox& sandbox) const noexcept + { + RLBOX_UNUSED(sandbox); + return data; + }; + + tainted_volatile() = default; + tainted_volatile(const tainted_volatile& p) = default; + +public: + inline tainted operator&() const noexcept + { + auto ref = + detail::remove_volatile_from_ptr_cast(&this->get_sandbox_value_ref()); + auto ref_cast = reinterpret_cast(ref); + return tainted::internal_factory(ref_cast); + } + + inline tainted operator&() noexcept + { + return sandbox_const_cast(&std::as_const(*this)); + } + + // Needed as the definition of unary & above shadows the base's binary & + rlbox_detail_forward_binop_to_base(&, T_ClassBase); + + template + inline tainted_volatile& operator=(T_RhsRef&& val) + { + using T_Rhs = std::remove_reference_t; + using T_Rhs_El = std::remove_all_extents_t; + + // Need to construct an example_unsandboxed_ptr for pointers or arrays of + // pointers. Since tainted_volatile is the type of data in sandbox memory, + // the address of data (&data) refers to a location in sandbox memory and + // can thus be the example_unsandboxed_ptr + const volatile void* data_ref = &get_sandbox_value_ref(); + const void* example_unsandboxed_ptr = const_cast(data_ref); + // Some branches don't use this + RLBOX_UNUSED(example_unsandboxed_ptr); + + if_constexpr_named( + cond1, std::is_same_v, std::nullptr_t>) + { + static_assert(std::is_pointer_v, + "Null pointer can only be assigned to pointers"); + // assign using an integer instead of nullptr, as the pointer field may be + // represented as integer + data = 0; + } + else if_constexpr_named(cond2, detail::rlbox_is_tainted_v) + { + using namespace detail; + convert_type_non_class( + get_sandbox_value_ref(), + val.get_raw_value_ref(), + example_unsandboxed_ptr, + nullptr /* sandbox_ptr */); + } + else if_constexpr_named(cond3, detail::rlbox_is_tainted_volatile_v) + { + using namespace detail; + convert_type_non_class( + get_sandbox_value_ref(), + val.get_sandbox_value_ref(), + example_unsandboxed_ptr, + nullptr /* sandbox_ptr */); + } + else if_constexpr_named(cond4, detail::rlbox_is_sandbox_callback_v) + { + using T_RhsFunc = detail::rlbox_remove_wrapper_t; + + // need to perform some typechecking to ensure we are assigning compatible + // function pointer types only + if_constexpr_named(subcond1, !std::is_assignable_v) + { + rlbox_detail_static_fail_because( + subcond1, + "Trying to assign function pointer to field of incompatible types"); + } + else + { + // Need to reinterpret_cast as the representation of the signature of a + // callback uses the machine model of the sandbox, while the field uses + // that of the application. But we have already checked above that this + // is safe. + auto func = val.get_raw_sandbox_value(); + using T_Cast = std::remove_volatile_t; + get_sandbox_value_ref() = (T_Cast)func; + } + } + else if_constexpr_named( + cond5, + detail::is_fundamental_or_enum_v || + (std::is_array_v && !std::is_pointer_v)) + { + detail::convert_type_fundamental_or_array(get_sandbox_value_ref(), val); + } + else if_constexpr_named( + cond6, std::is_pointer_v || std::is_pointer_v) + { + rlbox_detail_static_fail_because( + cond6, + "Assignment of pointers is not safe as it could\n " + "1) Leak pointers from the appliction to the sandbox which may break " + "ASLR\n " + "2) Pass inaccessible pointers to the sandbox leading to crash\n " + "3) Break sandboxes that require pointers to be swizzled first\n " + "\n " + "Instead, if you want to pass in a pointer, do one of the following\n " + "1) Allocate with malloc_in_sandbox, and pass in a tainted pointer\n " + "2) For pointers that point to functions in the application, register " + "with sandbox.register_callback(\"foo\"), and pass in the registered " + "value\n " + "3) For pointers that point to functions in the sandbox, get the " + "address with get_sandbox_function_address(sandbox, foo), and pass in " + "the " + "address\n " + "4) For raw pointers, use assign_raw_pointer which performs required " + "safety checks\n "); + } + else + { + auto unknownCase = + !(cond1 || cond2 || cond3 || cond4 || cond5 /* || cond6 */); + rlbox_detail_static_fail_because( + unknownCase, "Assignment of the given type of value is not supported"); + } + + return *this; + } + + template + void assign_raw_pointer(rlbox_sandbox& sandbox, T_Rhs val) + { + static_assert(std::is_pointer_v, "Must be a pointer"); + static_assert(std::is_assignable_v, + "Should assign pointers of compatible types."); + // Maybe a function pointer, so we need to cast + const void* cast_val = reinterpret_cast(val); + bool safe = sandbox.is_pointer_in_sandbox_memory(cast_val); + detail::dynamic_check( + safe, + "Tried to assign a pointer that is not in the sandbox.\n " + "This is not safe as it could\n " + "1) Leak pointers from the appliction to the sandbox which may break " + "ASLR\n " + "2) Pass inaccessible pointers to the sandbox leading to crash\n " + "3) Break sandboxes that require pointers to be swizzled first\n " + "\n " + "Instead, if you want to pass in a pointer, do one of the following\n " + "1) Allocate with malloc_in_sandbox, and pass in a tainted pointer\n " + "2) For pointers that point to functions in the application, register " + "with sandbox.register_callback(\"foo\"), and pass in the registered " + "value\n " + "3) For pointers that point to functions in the sandbox, get the " + "address with get_sandbox_function_address(sandbox, foo), and pass in " + "the " + "address\n "); + get_sandbox_value_ref() = + sandbox.template get_sandboxed_pointer(cast_val); + } + + template + operator bool() const + { + rlbox_detail_static_fail_because( + detail::true_v, + "Cannot apply implicit conversion to bool on values that are located in " + "sandbox memory. This error occurs if you compare a dereferenced value " + "such as the code shown below\n\n" + "tainted a = ...;\n" + "assert(*a);\n\n" + "Instead you can write this code as \n" + "tainted temp = *a;\n" + "assert(temp);\n"); + return false; + } +}; + +} diff --git a/third_party/rlbox/include/rlbox_app_pointer.hpp b/third_party/rlbox/include/rlbox_app_pointer.hpp new file mode 100644 index 0000000000..5af4876867 --- /dev/null +++ b/third_party/rlbox/include/rlbox_app_pointer.hpp @@ -0,0 +1,93 @@ +#pragma once +// IWYU pragma: private, include "rlbox.hpp" +// IWYU pragma: friend "rlbox_.*\.hpp" + +#include +#ifndef RLBOX_USE_CUSTOM_SHARED_LOCK +# include +#endif +#include + +#include "rlbox_helpers.hpp" +#include "rlbox_type_traits.hpp" +#include "rlbox_types.hpp" + +namespace rlbox { + +template +class app_pointer_map +{ + +private: + using T_PointerTypeUnsigned = detail::unsigned_int_of_size_t; + + std::map pointer_map; + T_PointerTypeUnsigned counter = 1; +#ifndef RLBOX_SINGLE_THREADED_INVOCATIONS + RLBOX_SHARED_LOCK(map_mutex); +#endif + + T_PointerType get_unused_index(T_PointerType max_ptr_val) + { + const auto max_val = (T_PointerTypeUnsigned)max_ptr_val; + for (T_PointerTypeUnsigned i = counter; i <= max_val; i++) { + if (pointer_map.find(i) == pointer_map.end()) { + counter = i + 1; + return (T_PointerType)i; + } + } + for (T_PointerTypeUnsigned i = 1; i < counter; i++) { + if (pointer_map.find(i) == pointer_map.end()) { + counter = i + 1; + return (T_PointerType)i; + } + } + detail::dynamic_check(false, "Could not find free app pointer slot"); + return 0; + } + +public: + app_pointer_map() + { + // ensure we can't use app pointer 0 as this is sometimes confused as null + // by the sandbox + pointer_map[0] = nullptr; + } + + T_PointerType get_app_pointer_idx(void* ptr, T_PointerType max_ptr_val) + { +#ifndef RLBOX_SINGLE_THREADED_INVOCATIONS + RLBOX_ACQUIRE_UNIQUE_GUARD(lock, map_mutex); +#endif + T_PointerType idx = get_unused_index(max_ptr_val); + T_PointerTypeUnsigned idx_int = (T_PointerTypeUnsigned)idx; + pointer_map[idx_int] = ptr; + return idx; + } + + void remove_app_ptr(T_PointerType idx) + { +#ifndef RLBOX_SINGLE_THREADED_INVOCATIONS + RLBOX_ACQUIRE_UNIQUE_GUARD(lock, map_mutex); +#endif + T_PointerTypeUnsigned idx_int = (T_PointerTypeUnsigned)idx; + auto it = pointer_map.find(idx_int); + detail::dynamic_check(it != pointer_map.end(), + "Error: removing a non-existing app pointer"); + pointer_map.erase(it); + } + + void* lookup_index(T_PointerType idx) + { +#ifndef RLBOX_SINGLE_THREADED_INVOCATIONS + RLBOX_ACQUIRE_SHARED_GUARD(lock, map_mutex); +#endif + T_PointerTypeUnsigned idx_int = (T_PointerTypeUnsigned)idx; + auto it = pointer_map.find(idx_int); + detail::dynamic_check(it != pointer_map.end(), + "Error: looking up a non-existing app pointer"); + return it->second; + } +}; + +} \ No newline at end of file diff --git a/third_party/rlbox/include/rlbox_conversion.hpp b/third_party/rlbox/include/rlbox_conversion.hpp new file mode 100644 index 0000000000..e82d0d5da0 --- /dev/null +++ b/third_party/rlbox/include/rlbox_conversion.hpp @@ -0,0 +1,273 @@ +#pragma once +// IWYU pragma: private, include "rlbox.hpp" +// IWYU pragma: friend "rlbox_.*\.hpp" + +#include +#include +#include +#include + +#include "rlbox_helpers.hpp" +#include "rlbox_type_traits.hpp" +#include "rlbox_types.hpp" + +namespace rlbox::detail { + +template +inline constexpr void convert_type_fundamental(T_To& to, + const volatile T_From& from) +{ + using namespace std; + + if_constexpr_named(cond1, !is_fundamental_or_enum_v) + { + rlbox_detail_static_fail_because( + cond1, "Conversion target should be fundamental or enum type"); + } + else if_constexpr_named(cond2, !is_fundamental_or_enum_v) + { + rlbox_detail_static_fail_because( + cond2, "Conversion source should be fundamental or enum type"); + } + else if_constexpr_named(cond3, is_enum_v || is_enum_v) + { + static_assert(std::is_same_v, + detail::remove_cv_ref_t>); + to = from; + } + else if_constexpr_named( + cond4, is_floating_point_v || is_floating_point_v) + { + static_assert(is_floating_point_v && is_floating_point_v); + // language coerces different float types + to = from; + } + else if_constexpr_named(cond5, is_integral_v || is_integral_v) + { + static_assert(is_integral_v && is_integral_v); + + const char* err_msg = + "Over/Underflow when converting between integer types"; + + // Some branches don't use the param + RLBOX_UNUSED(err_msg); + + if constexpr (is_signed_v == is_signed_v && + sizeof(T_To) >= sizeof(T_From)) { + // Eg: int64_t from int32_t, uint64_t from uint32_t + } else if constexpr (is_unsigned_v && is_unsigned_v) { + // Eg: uint32_t from uint64_t + dynamic_check(from <= numeric_limits::max(), err_msg); + } else if constexpr (is_signed_v && is_signed_v) { + // Eg: int32_t from int64_t + dynamic_check(from >= numeric_limits::min(), err_msg); + dynamic_check(from <= numeric_limits::max(), err_msg); + } else if constexpr (is_unsigned_v && is_signed_v) { + if constexpr (sizeof(T_To) < sizeof(T_From)) { + // Eg: uint32_t from int64_t + dynamic_check(from >= 0, err_msg); + auto to_max = numeric_limits::max(); + dynamic_check(from <= static_cast(to_max), err_msg); + } else { + // Eg: uint32_t from int32_t, uint64_t from int32_t + dynamic_check(from >= 0, err_msg); + } + } else if constexpr (is_signed_v && is_unsigned_v) { + if constexpr (sizeof(T_To) <= sizeof(T_From)) { + // Eg: int32_t from uint32_t, int32_t from uint64_t + auto to_max = numeric_limits::max(); + dynamic_check(from <= static_cast(to_max), err_msg); + } else { + // Eg: int64_t from uint32_t + } + } + to = static_cast(from); + } + else + { + constexpr auto unknownCase = !(cond1 || cond2 || cond3 || cond4 || cond5); + rlbox_detail_static_fail_because( + unknownCase, "Unexpected case for convert_type_fundamental"); + } +} + +template +inline constexpr void convert_type_fundamental_or_array(T_To& to, + const T_From& from) +{ + using namespace std; + + using T_To_C = std_array_to_c_arr_t; + using T_From_C = std_array_to_c_arr_t; + using T_To_El = remove_all_extents_t; + using T_From_El = remove_all_extents_t; + + if_constexpr_named(cond1, is_array_v != is_array_v) + { + rlbox_detail_static_fail_because( + cond1, "Conversion should not go between array and non array types"); + } + else if constexpr (!is_array_v) + { + convert_type_fundamental(to, from); + } + else if_constexpr_named(cond2, !all_extents_same) + { + rlbox_detail_static_fail_because( + cond2, "Conversion between arrays should have same dimensions"); + } + else if_constexpr_named(cond3, + is_pointer_v || is_pointer_v) + { + rlbox_detail_static_fail_because(cond3, + "convert_type_fundamental_or_array " + "does not allow arrays of pointers"); + } + else + { + // Explicitly using size to check for element type as we may be going across + // different types of the same width such as void* and uintptr_t + if constexpr (sizeof(T_To_El) == sizeof(T_From_El) && + is_signed_v == is_signed_v) { + // Sanity check - this should definitely be true + static_assert(sizeof(T_From_C) == sizeof(T_To_C)); + std::memcpy(&to, &from, sizeof(T_To_C)); + } else { + for (size_t i = 0; i < std::extent_v; i++) { + convert_type_fundamental_or_array(to[i], from[i]); + } + } + } +} + +enum class adjust_type_direction +{ + TO_SANDBOX, + TO_APPLICATION, + NO_CHANGE +}; + +enum class adjust_type_context +{ + EXAMPLE, + SANDBOX +}; + +template +inline constexpr void convert_type_non_class( + T_To& to, + const T_From& from, + const void* example_unsandboxed_ptr, + rlbox_sandbox* sandbox_ptr) +{ + using namespace std; + + // Some branches don't use the param + RLBOX_UNUSED(example_unsandboxed_ptr); + RLBOX_UNUSED(sandbox_ptr); + + using T_To_C = std_array_to_c_arr_t; + using T_From_C = std_array_to_c_arr_t; + using T_To_El = remove_all_extents_t; + using T_From_El = remove_all_extents_t; + + if constexpr (is_pointer_v || is_pointer_v) { + + if constexpr (Direction == adjust_type_direction::NO_CHANGE) { + + static_assert(is_pointer_v && is_pointer_v && + sizeof(T_To_C) == sizeof(T_From_C)); + to = from; + + } else if constexpr (Direction == adjust_type_direction::TO_SANDBOX) { + + static_assert(is_pointer_v); + // Maybe a function pointer, so convert + auto from_c = reinterpret_cast(from); + if constexpr (Context == adjust_type_context::SANDBOX) { + RLBOX_DEBUG_ASSERT(sandbox_ptr != nullptr); + to = sandbox_ptr->template get_sandboxed_pointer(from_c); + } else { + RLBOX_DEBUG_ASSERT(from_c == nullptr || + example_unsandboxed_ptr != nullptr); + to = + rlbox_sandbox::template get_sandboxed_pointer_no_ctx( + from_c, example_unsandboxed_ptr); + } + + } else if constexpr (Direction == adjust_type_direction::TO_APPLICATION) { + + static_assert(is_pointer_v); + if constexpr (Context == adjust_type_context::SANDBOX) { + RLBOX_DEBUG_ASSERT(sandbox_ptr != nullptr); + to = sandbox_ptr->template get_unsandboxed_pointer(from); + } else { + RLBOX_DEBUG_ASSERT(from == 0 || example_unsandboxed_ptr != nullptr); + to = + rlbox_sandbox::template get_unsandboxed_pointer_no_ctx( + from, example_unsandboxed_ptr); + } + } + + } else if constexpr (is_pointer_v || is_pointer_v) { + + if constexpr (Direction == adjust_type_direction::NO_CHANGE) { + // Sanity check - this should definitely be true + static_assert(sizeof(T_To_El) == sizeof(T_From_El) && + sizeof(T_From_C) == sizeof(T_To_C)); + memcpy(&to, &from, sizeof(T_To_C)); + } else { + for (size_t i = 0; i < std::extent_v; i++) { + convert_type_non_class( + to[i], from[i], example_unsandboxed_ptr, sandbox_ptr); + } + } + + } else { + convert_type_fundamental_or_array(to, from); + } +} + +// Structs implement their own convert_type by specializing this class +// Have to do this via a class, as functions can't be partially specialized +template +class convert_type_class; +// The specialization implements the following +// { +// static inline void run(T_To& to, +// const T_From& from, +// const void* example_unsandboxed_ptr); +// } + +template +inline void convert_type(T_To& to, + const T_From& from, + const void* example_unsandboxed_ptr, + rlbox_sandbox* sandbox_ptr) +{ + if constexpr ((std::is_class_v || + std::is_class_v)&&!detail::is_std_array_v && + !detail::is_std_array_v) { + // Sanity check + static_assert(std::is_class_v && std::is_class_v); + convert_type_class::run( + to, from, example_unsandboxed_ptr, sandbox_ptr); + } else { + convert_type_non_class( + to, from, example_unsandboxed_ptr, sandbox_ptr); + } +} + +} \ No newline at end of file diff --git a/third_party/rlbox/include/rlbox_dylib_sandbox.hpp b/third_party/rlbox/include/rlbox_dylib_sandbox.hpp new file mode 100644 index 0000000000..9878674d71 --- /dev/null +++ b/third_party/rlbox/include/rlbox_dylib_sandbox.hpp @@ -0,0 +1,314 @@ +#pragma once + +#include +#include +#include +#ifndef RLBOX_USE_CUSTOM_SHARED_LOCK +# include +#endif +#include + +#if defined(_WIN32) +// Ensure the min/max macro in the header doesn't collide with functions in +// std:: +# ifndef NOMINMAX +# define NOMINMAX +# endif +# include +#else +# include +#endif + +#include "rlbox_helpers.hpp" + +namespace rlbox { + +class rlbox_dylib_sandbox; + +struct rlbox_dylib_sandbox_thread_data +{ + rlbox_dylib_sandbox* sandbox; + uint32_t last_callback_invoked; +}; + +#ifdef RLBOX_EMBEDDER_PROVIDES_TLS_STATIC_VARIABLES + +rlbox_dylib_sandbox_thread_data* get_rlbox_dylib_sandbox_thread_data(); +# define RLBOX_DYLIB_SANDBOX_STATIC_VARIABLES() \ + thread_local rlbox::rlbox_dylib_sandbox_thread_data \ + rlbox_dylib_sandbox_thread_info{ 0, 0 }; \ + namespace rlbox { \ + rlbox_dylib_sandbox_thread_data* get_rlbox_dylib_sandbox_thread_data() \ + { \ + return &rlbox_dylib_sandbox_thread_info; \ + } \ + } \ + static_assert(true, "Enforce semi-colon") + +#endif + +/** + * @brief Class that implements the null sandbox. This sandbox doesn't actually + * provide any isolation and only serves as a stepping stone towards migrating + * an application to use the RLBox API. + */ +class rlbox_dylib_sandbox +{ +public: + // Stick with the system defaults + using T_LongLongType = long long; + using T_LongType = long; + using T_IntType = int; + using T_PointerType = void*; + using T_ShortType = short; + // no-op sandbox can transfer buffers as there is no sandboxings + // Thus transfer is a noop + using can_grant_deny_access = void; + // if this plugin uses a separate function to lookup internal callbacks + using needs_internal_lookup_symbol = void; + +private: + void* sandbox = nullptr; + + RLBOX_SHARED_LOCK(callback_mutex); + static inline const uint32_t MAX_CALLBACKS = 64; + void* callback_unique_keys[MAX_CALLBACKS]{ 0 }; + void* callbacks[MAX_CALLBACKS]{ 0 }; + +#ifndef RLBOX_EMBEDDER_PROVIDES_TLS_STATIC_VARIABLES + thread_local static inline rlbox_dylib_sandbox_thread_data thread_data{ 0, + 0 }; +#endif + + template + static T_Ret callback_trampoline(T_Args... params) + { +#ifdef RLBOX_EMBEDDER_PROVIDES_TLS_STATIC_VARIABLES + auto& thread_data = *get_rlbox_dylib_sandbox_thread_data(); +#endif + thread_data.last_callback_invoked = N; + using T_Func = T_Ret (*)(T_Args...); + T_Func func; + { +#ifndef RLBOX_SINGLE_THREADED_INVOCATIONS + RLBOX_ACQUIRE_SHARED_GUARD(lock, thread_data.sandbox->callback_mutex); +#endif + func = reinterpret_cast(thread_data.sandbox->callbacks[N]); + } + // Callbacks are invoked through function pointers, cannot use std::forward + // as we don't have caller context for T_Args, which means they are all + // effectively passed by value + return func(params...); + } + +protected: +#if defined(_WIN32) + using path_buf = const LPCWSTR; +#else + using path_buf = const char*; +#endif + + inline void impl_create_sandbox(path_buf path) + { +#if defined(_WIN32) + sandbox = (void*)LoadLibraryW(path); +#else + sandbox = dlopen(path, RTLD_LAZY | RTLD_LOCAL); +#endif + + if (!sandbox) { + std::string error_msg = "Could not load dynamic library: "; +#if defined(_WIN32) + DWORD errorMessageID = GetLastError(); + if (errorMessageID != 0) { + LPSTR messageBuffer = nullptr; + // The api creates the buffer that holds the message + size_t size = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + errorMessageID, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPSTR)&messageBuffer, + 0, + NULL); + // Copy the error message into a std::string. + std::string message(messageBuffer, size); + error_msg += message; + LocalFree(messageBuffer); + } +#else + error_msg += dlerror(); +#endif + detail::dynamic_check(false, error_msg.c_str()); + } + } + + inline void impl_destroy_sandbox() + { +#if defined(_WIN32) + FreeLibrary((HMODULE)sandbox); +#else + dlclose(sandbox); +#endif + sandbox = nullptr; + } + + template + inline void* impl_get_unsandboxed_pointer(T_PointerType p) const + { + return p; + } + + template + inline T_PointerType impl_get_sandboxed_pointer(const void* p) const + { + return const_cast(p); + } + + template + static inline void* impl_get_unsandboxed_pointer_no_ctx( + T_PointerType p, + const void* /* example_unsandboxed_ptr */, + rlbox_dylib_sandbox* (* // Func ptr + /* param: expensive_sandbox_finder */)( + const void* example_unsandboxed_ptr)) + { + return p; + } + + template + static inline T_PointerType impl_get_sandboxed_pointer_no_ctx( + const void* p, + const void* /* example_unsandboxed_ptr */, + rlbox_dylib_sandbox* (* // Func ptr + /* param: expensive_sandbox_finder */)( + const void* example_unsandboxed_ptr)) + { + return const_cast(p); + } + + inline T_PointerType impl_malloc_in_sandbox(size_t size) + { + void* p = malloc(size); + return p; + } + + inline void impl_free_in_sandbox(T_PointerType p) { free(p); } + + static inline bool impl_is_in_same_sandbox(const void*, const void*) + { + return true; + } + + inline bool impl_is_pointer_in_sandbox_memory(const void*) { return true; } + inline bool impl_is_pointer_in_app_memory(const void*) { return true; } + + inline size_t impl_get_total_memory() + { + return std::numeric_limits::max(); + } + + inline void* impl_get_memory_location() + { + // There isn't any sandbox memory for the dylib_sandbox as we just redirect + // to the app. Also, this is mostly used for pointer swizzling or sandbox + // bounds checks which is also not present/not required. So we can just + // return null + return nullptr; + } + + void* impl_lookup_symbol(const char* func_name) + { +#if defined(_WIN32) + void* ret = GetProcAddress((HMODULE)sandbox, func_name); +#else + void* ret = dlsym(sandbox, func_name); +#endif + detail::dynamic_check(ret != nullptr, "Symbol not found"); + return ret; + } + + void* impl_internal_lookup_symbol(const char* func_name) + { + return impl_lookup_symbol(func_name); + } + + template + auto impl_invoke_with_func_ptr(T_Converted* func_ptr, T_Args&&... params) + { +#ifdef RLBOX_EMBEDDER_PROVIDES_TLS_STATIC_VARIABLES + auto& thread_data = *get_rlbox_dylib_sandbox_thread_data(); +#endif + auto old_sandbox = thread_data.sandbox; + thread_data.sandbox = this; + auto on_exit = detail::make_scope_exit([&] { + thread_data.sandbox = old_sandbox; + }); + return (*func_ptr)(params...); + } + + template + inline T_PointerType impl_register_callback(void* key, void* callback) + { + RLBOX_ACQUIRE_UNIQUE_GUARD(lock, callback_mutex); + + void* chosen_trampoline = nullptr; + + // need a compile time for loop as we we need I to be a compile time value + // this is because we are returning the I'th callback trampoline + detail::compile_time_for([&](auto I) { + if (!chosen_trampoline && callback_unique_keys[I.value] == nullptr) { + callback_unique_keys[I.value] = key; + callbacks[I.value] = callback; + chosen_trampoline = reinterpret_cast( + callback_trampoline); + } + }); + + return reinterpret_cast(chosen_trampoline); + } + + static inline std::pair + impl_get_executed_callback_sandbox_and_key() + { +#ifdef RLBOX_EMBEDDER_PROVIDES_TLS_STATIC_VARIABLES + auto& thread_data = *get_rlbox_dylib_sandbox_thread_data(); +#endif + auto sandbox = thread_data.sandbox; + auto callback_num = thread_data.last_callback_invoked; + void* key = sandbox->callback_unique_keys[callback_num]; + return std::make_pair(sandbox, key); + } + + template + inline void impl_unregister_callback(void* key) + { + RLBOX_ACQUIRE_UNIQUE_GUARD(lock, callback_mutex); + for (uint32_t i = 0; i < MAX_CALLBACKS; i++) { + if (callback_unique_keys[i] == key) { + callback_unique_keys[i] = nullptr; + callbacks[i] = nullptr; + break; + } + } + } + + template + inline T* impl_grant_access(T* src, size_t num, bool& success) + { + RLBOX_UNUSED(num); + success = true; + return src; + } + + template + inline T* impl_deny_access(T* src, size_t num, bool& success) + { + RLBOX_UNUSED(num); + success = true; + return src; + } +}; + +} diff --git a/third_party/rlbox/include/rlbox_helpers.hpp b/third_party/rlbox/include/rlbox_helpers.hpp new file mode 100644 index 0000000000..04c3294693 --- /dev/null +++ b/third_party/rlbox/include/rlbox_helpers.hpp @@ -0,0 +1,216 @@ +#pragma once +// IWYU pragma: private, include "rlbox.hpp" +// IWYU pragma: friend "rlbox_.*\.hpp" + +#include +#include +#include +#include +#include +#ifndef RLBOX_USE_CUSTOM_SHARED_LOCK +# include +#endif + +#include "rlbox_stdlib_polyfill.hpp" + +namespace rlbox { +namespace detail { + const int CompileErrorCode = 42; + + inline void dynamic_check(bool check, const char* const msg) + { + // clang-format off + if (!check) { + #if __cpp_exceptions && defined(RLBOX_USE_EXCEPTIONS) + throw std::runtime_error(msg); + #else + #ifdef RLBOX_CUSTOM_ABORT + RLBOX_CUSTOM_ABORT(msg); + #else + std::cerr << msg << std::endl; + std::abort(); + #endif + #endif + } + // clang-format on + } + +#ifdef RLBOX_NO_COMPILE_CHECKS +# if __cpp_exceptions && defined(RLBOX_USE_EXCEPTIONS) +# define rlbox_detail_static_fail_because(CondExpr, Message) \ + ((void)(CondExpr)), throw std::runtime_error(Message) +# else +# define rlbox_detail_static_fail_because(CondExpr, Message) abort() +# endif +#else +# define rlbox_detail_static_fail_because(CondExpr, Message) \ + static_assert(!(CondExpr), Message) +#endif + +#ifdef RLBOX_ENABLE_DEBUG_ASSERTIONS +# define RLBOX_DEBUG_ASSERT(...) \ + ::rlbox::detail::dynamic_check(__VA_ARGS__, "Debug assertion failed") +#else +# define RLBOX_DEBUG_ASSERT(...) (void)0 +#endif + +#define RLBOX_UNUSED(...) (void)__VA_ARGS__ + +#define RLBOX_REQUIRE_SEMI_COLON static_assert(true) + +#define if_constexpr_named(varName, ...) \ + if constexpr (constexpr auto varName = __VA_ARGS__; varName) + + template + void printTypes() + { +#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) + std::cout << __PRETTY_FUNCTION__ << std::endl; // NOLINT +#elif defined(_MSC_VER) + std::cout << __FUNCSIG__ << std::endl; // NOLINT +#else + std::cout << "Unsupported" << std::endl; +#endif + } + +// Create an extension point so applications can provide their own shared lock +// implementation +#ifndef RLBOX_USE_CUSTOM_SHARED_LOCK +# define RLBOX_SHARED_LOCK(name) std::shared_timed_mutex name +# define RLBOX_ACQUIRE_SHARED_GUARD(name, ...) \ + std::shared_lock name(__VA_ARGS__) +# define RLBOX_ACQUIRE_UNIQUE_GUARD(name, ...) \ + std::unique_lock name(__VA_ARGS__) +#else +# if !defined(RLBOX_SHARED_LOCK) || !defined(RLBOX_ACQUIRE_SHARED_GUARD) || \ + !defined(RLBOX_ACQUIRE_UNIQUE_GUARD) +# error \ + "RLBOX_USE_CUSTOM_SHARED_LOCK defined but missing definitions for RLBOX_SHARED_LOCK, RLBOX_ACQUIRE_SHARED_GUARD, RLBOX_ACQUIRE_UNIQUE_GUARD" +# endif +#endif + +#define rlbox_detail_forward_binop_to_base(opSymbol, ...) \ + template \ + inline auto operator opSymbol(T_Rhs rhs) \ + { \ + auto b = static_cast<__VA_ARGS__*>(this); \ + return (*b)opSymbol rhs; \ + } \ + RLBOX_REQUIRE_SEMI_COLON + + template + inline auto remove_volatile_from_ptr_cast(T* ptr) + { + using T_Result = std::add_pointer_t>; + return const_cast(ptr); + } + + // https://stackoverflow.com/questions/37602057/why-isnt-a-for-loop-a-compile-time-expression + namespace compile_time_for_detail { + template + struct num + { + static const constexpr auto value = N; + }; + + template + inline void compile_time_for_helper(F func, std::index_sequence) + { + (func(num{}), ...); + } + } + + template + inline void compile_time_for(F func) + { + compile_time_for_detail::compile_time_for_helper( + func, std::make_index_sequence()); + } + + template + [[nodiscard]] inline auto return_first_result(T first_task, T2 second_task) + { + using T_Result = rlbox::detail::polyfill::invoke_result_t; + + if constexpr (std::is_void_v) { + first_task(); + second_task(); + } else { + auto val = first_task(); + second_task(); + return val; + } + } + + // Scope Exit guards + template + class scope_exit + { + T_ExitFunc exit_func; + bool released; + + public: + explicit scope_exit(T_ExitFunc&& cleanup) + : exit_func(cleanup) + , released(true) + {} + + scope_exit(scope_exit&& rhs) + : exit_func(std::move(rhs.exit_func)) + , released(rhs.released) + { + rhs.release(); + } + + ~scope_exit() + { + if (released) { + exit_func(); + } + } + + void release() { released = false; } + + private: + explicit scope_exit(const scope_exit&) = delete; + scope_exit& operator=(const scope_exit&) = delete; + scope_exit& operator=(scope_exit&&) = delete; + }; + + template + [[nodiscard]] scope_exit make_scope_exit( + T_ExitFunc&& exitFunction) + { + return scope_exit(std::move(exitFunction)); + } + +/* +Make sure classes can access the private memmbers of tainted and +tainted_volatile. Ideally, this should be + +template +friend class tainted; + +But C++ doesn't seem to allow the above +*/ +#define KEEP_CLASSES_FRIENDLY \ + template typename U1, typename U2, typename U3> \ + friend class tainted_base_impl; \ + \ + template \ + friend class tainted; \ + \ + template \ + friend class tainted_volatile; \ + \ + template \ + friend class rlbox_sandbox; \ + \ + template \ + friend class sandbox_callback; \ + \ + template \ + friend class app_pointer; +} + +} diff --git a/third_party/rlbox/include/rlbox_noop_sandbox.hpp b/third_party/rlbox/include/rlbox_noop_sandbox.hpp new file mode 100644 index 0000000000..d1e48edb4f --- /dev/null +++ b/third_party/rlbox/include/rlbox_noop_sandbox.hpp @@ -0,0 +1,254 @@ +#pragma once + +#include +#include +#include +#ifndef RLBOX_USE_CUSTOM_SHARED_LOCK +# include +#endif +#include + +#include "rlbox_helpers.hpp" + +namespace rlbox { + +class rlbox_noop_sandbox; + +struct rlbox_noop_sandbox_thread_data +{ + rlbox_noop_sandbox* sandbox; + uint32_t last_callback_invoked; +}; + +#ifdef RLBOX_EMBEDDER_PROVIDES_TLS_STATIC_VARIABLES + +rlbox_noop_sandbox_thread_data* get_rlbox_noop_sandbox_thread_data(); +# define RLBOX_NOOP_SANDBOX_STATIC_VARIABLES() \ + thread_local rlbox::rlbox_noop_sandbox_thread_data \ + rlbox_noop_sandbox_thread_info{ 0, 0 }; \ + namespace rlbox { \ + rlbox_noop_sandbox_thread_data* get_rlbox_noop_sandbox_thread_data() \ + { \ + return &rlbox_noop_sandbox_thread_info; \ + } \ + } \ + static_assert(true, "Enforce semi-colon") + +#endif + +/** + * @brief Class that implements the null sandbox. This sandbox doesn't actually + * provide any isolation and only serves as a stepping stone towards migrating + * an application to use the RLBox API. + */ +class rlbox_noop_sandbox +{ +public: + // Stick with the system defaults + using T_LongLongType = long long; + using T_LongType = long; + using T_IntType = int; + using T_PointerType = void*; + using T_ShortType = short; + // no-op sandbox can transfer buffers as there is no sandboxings + // Thus transfer is a noop + using can_grant_deny_access = void; + +private: + RLBOX_SHARED_LOCK(callback_mutex); + static inline const uint32_t MAX_CALLBACKS = 64; + void* callback_unique_keys[MAX_CALLBACKS]{ 0 }; + void* callbacks[MAX_CALLBACKS]{ 0 }; + +#ifndef RLBOX_EMBEDDER_PROVIDES_TLS_STATIC_VARIABLES + thread_local static inline rlbox_noop_sandbox_thread_data thread_data{ 0, 0 }; +#endif + + template + static T_Ret callback_trampoline(T_Args... params) + { +#ifdef RLBOX_EMBEDDER_PROVIDES_TLS_STATIC_VARIABLES + auto& thread_data = *get_rlbox_noop_sandbox_thread_data(); +#endif + thread_data.last_callback_invoked = N; + using T_Func = T_Ret (*)(T_Args...); + T_Func func; + { +#ifndef RLBOX_SINGLE_THREADED_INVOCATIONS + RLBOX_ACQUIRE_SHARED_GUARD(lock, thread_data.sandbox->callback_mutex); +#endif + func = reinterpret_cast(thread_data.sandbox->callbacks[N]); + } + // Callbacks are invoked through function pointers, cannot use std::forward + // as we don't have caller context for T_Args, which means they are all + // effectively passed by value + return func(params...); + } + +protected: + inline void impl_create_sandbox() {} + + inline void impl_destroy_sandbox() {} + + template + inline void* impl_get_unsandboxed_pointer(T_PointerType p) const + { + return p; + } + + template + inline T_PointerType impl_get_sandboxed_pointer(const void* p) const + { + return const_cast(p); + } + + template + static inline void* impl_get_unsandboxed_pointer_no_ctx( + T_PointerType p, + const void* /* example_unsandboxed_ptr */, + rlbox_noop_sandbox* (* // Func ptr + /* param: expensive_sandbox_finder */)( + const void* example_unsandboxed_ptr)) + { + return p; + } + + template + static inline T_PointerType impl_get_sandboxed_pointer_no_ctx( + const void* p, + const void* /* example_unsandboxed_ptr */, + rlbox_noop_sandbox* (* // Func ptr + /* param: expensive_sandbox_finder */)( + const void* example_unsandboxed_ptr)) + { + return const_cast(p); + } + + inline T_PointerType impl_malloc_in_sandbox(size_t size) + { + void* p = malloc(size); + return p; + } + + inline void impl_free_in_sandbox(T_PointerType p) { free(p); } + + static inline bool impl_is_in_same_sandbox(const void*, const void*) + { + return true; + } + + inline bool impl_is_pointer_in_sandbox_memory(const void*) { return true; } + inline bool impl_is_pointer_in_app_memory(const void*) { return true; } + + inline size_t impl_get_total_memory() + { + return std::numeric_limits::max(); + } + + inline void* impl_get_memory_location() + { + // There isn't any sandbox memory for the noop_sandbox as we just redirect + // to the app. Also, this is mostly used for pointer swizzling or sandbox + // bounds checks which is also not present/not required. So we can just + // return null + return nullptr; + } + + // adding a template so that we can use static_assert to fire only if this + // function is invoked + template + void* impl_lookup_symbol(const char* /* func_name */) + { + // Will fire if this impl_lookup_symbol is ever called for the static + // sandbox + constexpr bool fail = std::is_same_v; + rlbox_detail_static_fail_because( + fail, + "The no_op_sandbox uses static calls and thus developers should add\n\n" + "#define RLBOX_USE_STATIC_CALLS() rlbox_noop_sandbox_lookup_symbol\n\n" + "to their code, to ensure that static calls are handled correctly."); + + return nullptr; + } + +#define rlbox_noop_sandbox_lookup_symbol(func_name) \ + reinterpret_cast(&func_name) /* NOLINT */ + + template + auto impl_invoke_with_func_ptr(T_Converted* func_ptr, T_Args&&... params) + { +#ifdef RLBOX_EMBEDDER_PROVIDES_TLS_STATIC_VARIABLES + auto& thread_data = *get_rlbox_noop_sandbox_thread_data(); +#endif + auto old_sandbox = thread_data.sandbox; + thread_data.sandbox = this; + auto on_exit = detail::make_scope_exit([&] { + thread_data.sandbox = old_sandbox; + }); + return (*func_ptr)(params...); + } + + template + inline T_PointerType impl_register_callback(void* key, void* callback) + { + RLBOX_ACQUIRE_UNIQUE_GUARD(lock, callback_mutex); + + void* chosen_trampoline = nullptr; + + // need a compile time for loop as we we need I to be a compile time value + // this is because we are returning the I'th callback trampoline + detail::compile_time_for([&](auto I) { + if (!chosen_trampoline && callback_unique_keys[I.value] == nullptr) { + callback_unique_keys[I.value] = key; + callbacks[I.value] = callback; + chosen_trampoline = reinterpret_cast( + callback_trampoline); + } + }); + + return reinterpret_cast(chosen_trampoline); + } + + static inline std::pair + impl_get_executed_callback_sandbox_and_key() + { +#ifdef RLBOX_EMBEDDER_PROVIDES_TLS_STATIC_VARIABLES + auto& thread_data = *get_rlbox_noop_sandbox_thread_data(); +#endif + auto sandbox = thread_data.sandbox; + auto callback_num = thread_data.last_callback_invoked; + void* key = sandbox->callback_unique_keys[callback_num]; + return std::make_pair(sandbox, key); + } + + template + inline void impl_unregister_callback(void* key) + { + RLBOX_ACQUIRE_UNIQUE_GUARD(lock, callback_mutex); + for (uint32_t i = 0; i < MAX_CALLBACKS; i++) { + if (callback_unique_keys[i] == key) { + callback_unique_keys[i] = nullptr; + callbacks[i] = nullptr; + break; + } + } + } + + template + inline T* impl_grant_access(T* src, size_t num, bool& success) + { + RLBOX_UNUSED(num); + success = true; + return src; + } + + template + inline T* impl_deny_access(T* src, size_t num, bool& success) + { + RLBOX_UNUSED(num); + success = true; + return src; + } +}; + +} diff --git a/third_party/rlbox/include/rlbox_policy_types.hpp b/third_party/rlbox/include/rlbox_policy_types.hpp new file mode 100644 index 0000000000..b5530dedcc --- /dev/null +++ b/third_party/rlbox/include/rlbox_policy_types.hpp @@ -0,0 +1,387 @@ +#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; + } +}; + +} diff --git a/third_party/rlbox/include/rlbox_range.hpp b/third_party/rlbox/include/rlbox_range.hpp new file mode 100644 index 0000000000..3dafcbd024 --- /dev/null +++ b/third_party/rlbox/include/rlbox_range.hpp @@ -0,0 +1,32 @@ + +#pragma once +// IWYU pragma: private, include "rlbox.hpp" +// IWYU pragma: friend "rlbox_.*\.hpp" + +#include + +#include "rlbox_types.hpp" + +namespace rlbox::detail { + +// Checks that a given range is either entirely in a sandbox or entirely +// outside +template +inline void check_range_doesnt_cross_app_sbx_boundary(const void* ptr, + size_t size) +{ + auto ptr_start_val = reinterpret_cast(ptr); + detail::dynamic_check( + ptr_start_val, + "Performing memory operation memset/memcpy on a null pointer"); + auto ptr_end_val = ptr_start_val + size - 1; + + auto ptr_start = reinterpret_cast(ptr_start_val); + auto ptr_end = reinterpret_cast(ptr_end_val); + + detail::dynamic_check( + rlbox_sandbox::is_in_same_sandbox(ptr_start, ptr_end), + "range has overflowed sandbox bounds"); +} + +} \ No newline at end of file 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 +#include +#ifdef RLBOX_MEASURE_TRANSITION_TIMES +# include +#endif +#include +#include +#include +#include +#ifndef RLBOX_USE_CUSTOM_SHARED_LOCK +# include +#endif +#ifdef RLBOX_MEASURE_TRANSITION_TIMES +# include +# include +#endif +#include +#include +#include +#include + +#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 + using conv = ::rlbox::detail::convert_to_sandbox_equivalent_t; + + template + using T_Func = T_Ret (*)(T_Args...); + + template + T_Func, conv...> 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 +class rlbox_sandbox : protected T_Sbx +{ + KEEP_CLASSES_FRIENDLY + +private: +#ifdef RLBOX_MEASURE_TRANSITION_TIMES + std::vector transition_times; +#endif + + static inline RLBOX_SHARED_LOCK(sandbox_list_lock); + // The actual type of the vector is std::vector*> + // However clang 5, 6 have bugs where compilation seg-faults on this type + // So we just use this std::vector + static inline std::vector sandbox_list; + + RLBOX_SHARED_LOCK(func_ptr_cache_lock); + std::map func_ptr_map; + + app_pointer_map 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_created = Sandbox_Status::NOT_CREATED; + + std::mutex callback_lock; + std::vector callback_keys; + + void* transition_state = nullptr; + + template + using convert_fn_ptr_to_sandbox_equivalent_t = + decltype(::rlbox::convert_fn_ptr_to_sandbox_equivalent_detail::helper< + T_Sbx>(std::declval())); + + template + inline constexpr void check_invoke_param_type_is_ok() + { + using T_NoRef = std::remove_reference_t; + + if_constexpr_named(cond1, detail::rlbox_is_wrapper_v) + { + if_constexpr_named( + subcond1, + !std::is_same_v>) + { + 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 || + detail::is_fundamental_or_enum_v) + {} + 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 + inline auto invoke_process_param(T&& param) + { + check_invoke_param_type_is_ok(); + + using T_NoRef = std::remove_reference_t; + + if constexpr (detail::rlbox_is_tainted_opaque_v) { + auto ret = from_opaque(param); + return ret.UNSAFE_sandboxed(*this); + } else if constexpr (detail::rlbox_is_wrapper_v) { + return param.UNSAFE_sandboxed(*this); + } else if constexpr (std::is_null_pointer_v) { + tainted ret = nullptr; + return ret.UNSAFE_sandboxed(*this); + } else if constexpr (detail::is_fundamental_or_enum_v) { + // For unwrapped primitives, assign to a tainted var and then unwrap so + // that we adjust for machine model + tainted ret = param; + return ret.UNSAFE_sandboxed(*this); + } else { + rlbox_detail_static_fail_because( + detail::true_v, + "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 + inline tainted sandbox_callback_intercept_convert_param( + rlbox_sandbox& sandbox, + const T_Arg& arg) + { + tainted ret; + using namespace detail; + convert_type( + ret.get_raw_value_ref(), + arg, + nullptr /* example_unsandboxed_ptr */, + &sandbox); + return ret; + } + + template + static detail::convert_to_sandbox_equivalent_t + sandbox_callback_interceptor( + detail::convert_to_sandbox_equivalent_t... args) + { + std::pair context = + T_Sbx::impl_get_executed_callback_sandbox_and_key(); + auto& sandbox = *(reinterpret_cast*>(context.first)); + auto key = context.second; + + using T_Func_Ret = + std::conditional_t, void, tainted>; + using T_Func = + T_Func_Ret (*)(rlbox_sandbox&, tainted...); + auto target_fn_ptr = reinterpret_cast(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(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) { + (*target_fn_ptr)( + sandbox, + sandbox.template sandbox_callback_intercept_convert_param( + sandbox, args)...); + return; + } else { + auto tainted_ret = (*target_fn_ptr)( + sandbox, + sandbox.template sandbox_callback_intercept_convert_param( + sandbox, args)...); + + using namespace detail; + convert_to_sandbox_equivalent_t ret; + convert_type( + 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 + 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, + detail::convert_to_sandbox_equivalent_t...>(key); + + std::lock_guard 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*>(sandbox_v); + if (sandbox->is_pointer_in_sandbox_memory(example_sandbox_ptr)) { + return sandbox; + } + } + + return nullptr; + } + + template + static auto impl_create_sandbox_helper(rlbox_sandbox* this_ptr, + T_Args... args) + { + return this_ptr->impl_create_sandbox(std::forward(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 + using convert_to_sandbox_equivalent_nonclass_t = + detail::convert_base_types_t; + + 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 + 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), + decltype(this), + T_Args...>; + + bool created = true; + if constexpr (std::is_same_v) { + this->impl_create_sandbox(std::forward(args)...); + } else if constexpr (std::is_same_v) { + created = this->impl_create_sandbox(std::forward(args)...); + } else { + rlbox_detail_static_fail_because( + (!std::is_same_v && !std::is_same_v), + "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 + inline T get_unsandboxed_pointer( + convert_to_sandbox_equivalent_nonclass_t p) const + { + static_assert(std::is_pointer_v); + if (p == 0) { + return nullptr; + } + auto ret = this->template impl_get_unsandboxed_pointer(p); + return reinterpret_cast(ret); + } + + template + inline convert_to_sandbox_equivalent_nonclass_t get_sandboxed_pointer( + const void* p) const + { + static_assert(std::is_pointer_v); + if (p == nullptr) { + return 0; + } + return this->template impl_get_sandboxed_pointer(p); + } + + template + static inline T get_unsandboxed_pointer_no_ctx( + convert_to_sandbox_equivalent_nonclass_t p, + const void* example_unsandboxed_ptr) + { + static_assert(std::is_pointer_v); + if (p == 0) { + return nullptr; + } + auto ret = T_Sbx::template impl_get_unsandboxed_pointer_no_ctx( + p, example_unsandboxed_ptr, find_sandbox_from_example); + return reinterpret_cast(ret); + } + + template + static inline convert_to_sandbox_equivalent_nonclass_t + get_sandboxed_pointer_no_ctx(const void* p, + const void* example_unsandboxed_ptr) + { + static_assert(std::is_pointer_v); + if (p == nullptr) { + return 0; + } + return T_Sbx::template impl_get_sandboxed_pointer_no_ctx( + 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 Tainted pointer accessible to the application + * and sandbox. + */ + template + inline tainted malloc_in_sandbox() + { + const uint32_t defaultCount = 1; + return malloc_in_sandbox(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 Tainted pointer accessible to the application + * and sandbox. + */ + template + inline tainted 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::internal_factory(nullptr); + } + + detail::dynamic_check(count != 0, "Malloc tried to allocate 0 bytes"); + if constexpr (sizeof(T) >= std::numeric_limits::max()) { + rlbox_detail_static_fail_because(sizeof(T) >= + std::numeric_limits::max(), + "Tried to allocate an object over 4GB."); + } + auto total_size = static_cast(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::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(ptr_in_sandbox); + if (!ptr) { + return tainted(nullptr); + } + detail::dynamic_check(is_pointer_in_sandbox_memory(ptr), + "Malloc returned pointer outside the sandbox memory"); + auto ptr_end = reinterpret_cast(ptr + (count - 1)); + detail::dynamic_check( + is_in_same_sandbox(ptr, reinterpret_cast(ptr_end)), + "Malloc returned a pointer whose range goes beyond sandbox memory"); + auto cast_ptr = reinterpret_cast(ptr); + return tainted::internal_factory(cast_ptr); + } + + /** + * @brief Free the memory referenced by the tainted pointer. + * + * @param ptr Pointer to sandbox memory to free. + */ + template + inline void free_in_sandbox(tainted 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 + inline void free_in_sandbox(tainted_volatile& ptr_ref) + { + tainted 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 + inline void free_in_sandbox(tainted_opaque ptr_opaque) + { + tainted 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; + 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 + inline tainted INTERNAL_grant_access(T* src, + size_t num, + bool& success) + { + auto ret = this->impl_grant_access(src, num, success); + return tainted::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 + inline T* INTERNAL_deny_access(tainted 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) { + 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 + inline auto INTERNAL_invoke_with_func_name(const char* func_name, + T_Args&&... params) + { + return INTERNAL_invoke_with_func_ptr( + func_name, lookup_symbol(func_name), std::forward(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 + 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(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(), ...); + + static_assert( + rlbox::detail::polyfill::is_invocable_v< + T, + detail::rlbox_remove_wrapper_t>...>, + "Mismatched arguments types for function"); + + using T_Result = rlbox::detail::polyfill::invoke_result_t< + T, + detail::rlbox_remove_wrapper_t>...>; + + using T_Converted = + std::remove_pointer_t>; + + if constexpr (std::is_void_v) { + this->template impl_invoke_with_func_ptr( + reinterpret_cast(func_ptr), + invoke_process_param(params)...); + return; + } else { + auto raw_result = this->template impl_invoke_with_func_ptr( + reinterpret_cast(func_ptr), + invoke_process_param(params)...); + tainted wrapped_result; + using namespace detail; + convert_type( + 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 + tainted UNSAFE_accept_pointer(T2 ptr) + { + static_assert(std::is_pointer_v, + "UNSAFE_accept_pointer expects a pointer param"); + tainted ret; + ret.assign_raw_pointer(*this, ptr); + return ret; + } + + template + using T_Cb_no_wrap = detail::rlbox_remove_wrapper_t( + detail::rlbox_remove_wrapper_t...); + + template + sandbox_callback*, T_Sbx> register_callback(T_Ret (*)()) + { + rlbox_detail_static_fail_because( + detail::true_v, + "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 foo(rlbox_sandbox& 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 + sandbox_callback*, 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&>) + { + 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 foo(rlbox_sandbox& sandbox, " + "tainted a, tainted b) {...}\n"); + } + else if_constexpr_named( + cond2, !(detail::rlbox_is_tainted_or_opaque_v && ...)) + { + 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 foo(rlbox_sandbox& sandbox, " + "tainted a, tainted b) {...}\n"); + } + else if_constexpr_named( + cond3, (std::is_array_v> || ...)) + { + 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 foo(rlbox_sandbox& sandbox, " + "tainted a) {...}\n"); + } + else if_constexpr_named( + cond4, + !(std::is_void_v || detail::rlbox_is_tainted_or_opaque_v)) + { + 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 foo(rlbox_sandbox& sandbox, " + "tainted a, tainted 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(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 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...>; + + auto callback_trampoline = this->template impl_register_callback< + detail::convert_to_sandbox_equivalent_t< + detail::rlbox_remove_wrapper_t, + T_Sbx>, + detail::convert_to_sandbox_equivalent_t< + detail::rlbox_remove_wrapper_t, + T_Sbx>...>(unique_key, reinterpret_cast(callback_interceptor)); + + auto tainted_func_ptr = reinterpret_cast< + detail::rlbox_tainted_opaque_to_tainted_t (*)( + T_RL, detail::rlbox_tainted_opaque_to_tainted_t...)>( + reinterpret_cast(func_ptr)); + + auto ret = sandbox_callback*, 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 + inline tainted INTERNAL_get_sandbox_function_name( + const char* func_name) + { + return INTERNAL_get_sandbox_function_ptr( + internal_lookup_symbol(func_name)); + } + + // this is an internal function invoked from macros, so it has be public + template + inline tainted INTERNAL_get_sandbox_function_ptr(void* func_ptr) + { + return tainted::internal_factory(reinterpret_cast(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 + app_pointer 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(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( + &app_ptr_map, idx, reinterpret_cast(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 + T* lookup_app_ptr(tainted tainted_ptr) + { + auto idx = tainted_ptr.get_raw_sandbox_value(*this); + void* ret = app_ptr_map.lookup_index(idx); + return reinterpret_cast(ret); + } + +#ifdef RLBOX_MEASURE_TRANSITION_TIMES + inline std::vector& + 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( \ + #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( \ + sandbox_lookup_symbol_helper(RLBOX_USE_STATIC_CALLS(), func_name)) + +#else + +# define invoke_sandbox_function(func_name, ...) \ + template INTERNAL_invoke_with_func_name( \ + #func_name, ##__VA_ARGS__) + +# define get_sandbox_function_address(func_name) \ + template INTERNAL_get_sandbox_function_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 + +} diff --git a/third_party/rlbox/include/rlbox_stdlib.hpp b/third_party/rlbox/include/rlbox_stdlib.hpp new file mode 100644 index 0000000000..cc60428077 --- /dev/null +++ b/third_party/rlbox/include/rlbox_stdlib.hpp @@ -0,0 +1,329 @@ +#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; + + // This function is meant for byte buffers only - so char and char16 + static_assert(sizeof(T) <= 2); + + // overflow ok + size_t source_size = num * sizeof(T); + + // 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, source_size); + + bool success; + auto ret = sandbox.INTERNAL_grant_access(src, num, success); + if (success) { + return ret; + } + } + + // 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"); + using T_nocv = std::remove_cv_t; + tainted copy = + sandbox.template malloc_in_sandbox(static_cast(num)); + if (!copy) { + return nullptr; + } + + rlbox::memcpy(sandbox, copy, src, source_size); + 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 T-sized elements in the buffer + * @param free_source_on_copy If the source buffer was copied, this variable + * controls whether copy_memory_or_deny_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) +{ + copied = false; + + // This function is meant for byte buffers only - so char and char16 + static_assert(sizeof(T) <= 2); + + // overflow ok + size_t source_size = num * sizeof(T); + + // 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(), source_size); + + bool success; + auto ret = sandbox.INTERNAL_deny_access(src, num, success); + if (success) { + return ret; + } + } + + auto copy = static_cast(malloc(source_size)); + if (!copy) { + return nullptr; + } + + 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, source_size); + if (free_source_on_copy) { + sandbox.free_in_sandbox(src); + } + + copied = true; + return copy; +} + +} diff --git a/third_party/rlbox/include/rlbox_stdlib_polyfill.hpp b/third_party/rlbox/include/rlbox_stdlib_polyfill.hpp new file mode 100644 index 0000000000..cf9c0117d0 --- /dev/null +++ b/third_party/rlbox/include/rlbox_stdlib_polyfill.hpp @@ -0,0 +1,175 @@ +#pragma once + +// This file is a polyfill for parts of the C++ standard library available only +// in newer compilers. Since these are only compile time requirements, we can +// just include these as part of the rlbox library in case the target compiler +// doesn't support these features. For instance clang-5 which rlbox supports +// does not support std::invocable and related functionality in +// and is polyfilled here. +// +// This code was borrowed from clang's standard library - libc++ +// +// Link: +// https://github.com/llvm-mirror/libcxx/blob/master/include/type_traits +// +// libc++ is dual licensed under the MIT license and the UIUC License (a +// BSD-like license) and is therefore compatible with our code base + +// std::invocable and friends + +namespace rlbox::detail::polyfill { + +struct __nat +{ + __nat() = delete; + __nat(const __nat&) = delete; + __nat& operator=(const __nat&) = delete; + ~__nat() = delete; +}; + +template +using _BoolConstant = std::integral_constant; + +template +using _IsNotSame = _BoolConstant::value>; + +#define INVOKE_RETURN(...) \ + noexcept(noexcept(__VA_ARGS__))->decltype(__VA_ARGS__) { return __VA_ARGS__; } + +template +inline auto helper__invoke(_Fp&& __f, _Args&&... __args) + INVOKE_RETURN(std::forward<_Fp>(__f)(std::forward<_Args>(__args)...)) + + template + inline constexpr auto helper__invoke_constexpr(_Fp&& __f, _Args&&... __args) + INVOKE_RETURN(std::forward<_Fp>(__f)(std::forward<_Args>(__args)...)) + +#undef INVOKE_RETURN + + // __invokable + template + struct __invokable_r +{ + template + static auto __try_call(int) + -> decltype(helper__invoke(std::declval<_XFp>(), + std::declval<_XArgs>()...)); + template + static __nat __try_call(...); + + // FIXME: Check that _Ret, _Fp, and _Args... are all complete types, cv void, + // or incomplete array types as required by the standard. + using _Result = decltype(__try_call<_Fp, _Args...>(0)); + + using type = typename std::conditional< + _IsNotSame<_Result, __nat>::value, + typename std::conditional::value, + std::true_type, + std::is_convertible<_Result, _Ret>>::type, + std::false_type>::type; + static const bool value = type::value; +}; +template +using __invokable = __invokable_r; + +template +struct __nothrow_invokable_r_imp +{ + static const bool value = false; +}; + +template +struct __nothrow_invokable_r_imp +{ + typedef __nothrow_invokable_r_imp _ThisT; + + template + static void __test_noexcept(_Tp) noexcept; + + static const bool value = noexcept(_ThisT::__test_noexcept<_Ret>( + helper__invoke(std::declval<_Fp>(), std::declval<_Args>()...))); +}; + +template +struct __nothrow_invokable_r_imp +{ + static const bool value = + noexcept(helper__invoke(std::declval<_Fp>(), std::declval<_Args>()...)); +}; + +template +using __nothrow_invokable_r = + __nothrow_invokable_r_imp<__invokable_r<_Ret, _Fp, _Args...>::value, + std::is_void<_Ret>::value, + _Ret, + _Fp, + _Args...>; + +template +using __nothrow_invokable = + __nothrow_invokable_r_imp<__invokable<_Fp, _Args...>::value, + true, + void, + _Fp, + _Args...>; + +template +struct helper__invoke_of + : public std::enable_if<__invokable<_Fp, _Args...>::value, + typename __invokable_r::_Result> +{}; + +// invoke_result + +template +struct invoke_result : helper__invoke_of<_Fn, _Args...> +{}; + +template +using invoke_result_t = typename invoke_result<_Fn, _Args...>::type; + +// is_invocable + +template +struct is_invocable + : std::integral_constant::value> +{}; + +template +struct is_invocable_r + : std::integral_constant::value> +{}; + +template +inline constexpr bool is_invocable_v = is_invocable<_Fn, _Args...>::value; + +template +inline constexpr bool is_invocable_r_v = + is_invocable_r<_Ret, _Fn, _Args...>::value; + +// is_nothrow_invocable + +template +struct is_nothrow_invocable + : std::integral_constant::value> +{}; + +template +struct is_nothrow_invocable_r + : std::integral_constant::value> +{}; + +template +inline constexpr bool is_nothrow_invocable_v = + is_nothrow_invocable<_Fn, _Args...>::value; + +template +inline constexpr bool is_nothrow_invocable_r_v = + is_nothrow_invocable_r<_Ret, _Fn, _Args...>::value; + +} diff --git a/third_party/rlbox/include/rlbox_struct_support.hpp b/third_party/rlbox/include/rlbox_struct_support.hpp new file mode 100644 index 0000000000..b29d92aa6e --- /dev/null +++ b/third_party/rlbox/include/rlbox_struct_support.hpp @@ -0,0 +1,353 @@ +#pragma once +// IWYU pragma: private, include "rlbox.hpp" +// IWYU pragma: friend "rlbox_.*\.hpp" + +#include +#include +#include + +#include "rlbox_conversion.hpp" +#include "rlbox_helpers.hpp" +#include "rlbox_types.hpp" +#include "rlbox_wrapper_traits.hpp" + +namespace rlbox::detail { + +template +struct convert_to_sandbox_equivalent_helper; + +template +struct convert_to_sandbox_equivalent_helper< + T, + T_Sbx, + std::enable_if_t>> +{ + using type = typename rlbox_sandbox< + T_Sbx>::template convert_to_sandbox_equivalent_nonclass_t; +}; + +template +using convert_to_sandbox_equivalent_t = + typename convert_to_sandbox_equivalent_helper::type; + +// This is used by rlbox_load_structs_from_library to test the current namespace +struct markerStruct +{}; +} + +#define helper_create_converted_field(fieldType, fieldName, isFrozen) \ + typename detail::convert_to_sandbox_equivalent_t fieldName; + +#define helper_no_op() + +#define sandbox_equivalent_specialization(T, libId) \ + template \ + struct Sbx_##libId##_##T \ + { \ + sandbox_fields_reflection_##libId##_class_##T( \ + helper_create_converted_field, \ + helper_no_op) \ + }; \ + \ + /* add convert_to_sandbox_equivalent_t specialization for new struct */ \ + namespace detail { \ + template \ + struct convert_to_sandbox_equivalent_helper< \ + T_Template, \ + T_Sbx, \ + std::enable_if_t>> \ + { \ + using type = Sbx_##libId##_##T; \ + }; \ + } + +#define helper_create_tainted_field( \ + fieldType, fieldName, isFrozen, MaybeConst) \ + MaybeConst tainted fieldName; + +#define helper_create_tainted_vol_field( \ + fieldType, fieldName, isFrozen, MaybeConst) \ + MaybeConst tainted_volatile fieldName; + +#define helper_convert_type(fieldType, fieldName, isFrozen) \ + ::rlbox::detail::convert_type( \ + lhs.fieldName, rhs.fieldName, example_unsandboxed_ptr, sandbox_ptr); + +#define helper_find_example_pointer_or_null(fieldType, fieldName, isFrozen) \ + { \ + const void* ret = fieldName.find_example_pointer_or_null(); \ + if (ret != nullptr) { \ + return ret; \ + } \ + } + +#define tainted_data_specialization_helper(MaybeConst, T, libId) \ + \ + template \ + class tainted_volatile \ + { \ + KEEP_CLASSES_FRIENDLY \ + KEEP_CAST_FRIENDLY \ + \ + private: \ + inline MaybeConst Sbx_##libId##_##T& \ + get_sandbox_value_ref() noexcept \ + { \ + return *reinterpret_cast*>(this); \ + } \ + \ + inline const Sbx_##libId##_##T& get_sandbox_value_ref() \ + const noexcept \ + { \ + return *reinterpret_cast*>(this); \ + } \ + \ + inline T get_raw_value() const noexcept \ + { \ + T lhs; \ + const auto& rhs = get_sandbox_value_ref(); \ + constexpr auto Direction = \ + detail::adjust_type_direction::TO_APPLICATION; \ + constexpr auto Context = detail::adjust_type_context::EXAMPLE; \ + /* This is a tainted_volatile, so its address is a valid example for use \ + * as example_unsandboxed_ptr */ \ + const void* example_unsandboxed_ptr = &rhs; \ + rlbox_sandbox* sandbox_ptr = nullptr; \ + sandbox_fields_reflection_##libId##_class_##T(helper_convert_type, \ + helper_no_op) \ + \ + return lhs; \ + } \ + \ + /* get_raw_sandbox_value has to return a custom struct to deal with the \ + * adjusted machine model, to ensure */ \ + inline Sbx_##libId##_##T get_raw_sandbox_value() const noexcept \ + { \ + auto ret_ptr = reinterpret_cast*>(this); \ + return *ret_ptr; \ + } \ + \ + tainted_volatile() = default; \ + tainted_volatile(const tainted_volatile& p) = \ + default; \ + \ + public: \ + sandbox_fields_reflection_##libId##_class_##T( \ + helper_create_tainted_vol_field, \ + helper_no_op, \ + MaybeConst) \ + \ + inline tainted operator&() const noexcept \ + { \ + auto ref_cast = \ + reinterpret_cast(&get_sandbox_value_ref()); \ + auto ret = tainted::internal_factory(ref_cast); \ + return ret; \ + } \ + \ + inline auto UNSAFE_unverified() const { return get_raw_value(); } \ + inline auto UNSAFE_sandboxed(rlbox_sandbox& sandbox) const \ + { \ + return get_raw_sandbox_value(sandbox); \ + } \ + \ + template \ + inline auto unverified_safe_because(const char (&reason)[N]) const \ + { \ + RLBOX_UNUSED(reason); \ + return UNSAFE_unverified(); \ + } \ + \ + T copy_and_verify(std::function)> verifier) \ + { \ + tainted val(*this); \ + return verifier(val); \ + } \ + \ + /* Can't define this yet due, to mutually dependent definition between \ + tainted and tainted_volatile for structs */ \ + inline tainted_volatile& operator=( \ + const tainted& rhs); \ + }; \ + \ + template \ + class tainted \ + { \ + KEEP_CLASSES_FRIENDLY \ + KEEP_CAST_FRIENDLY \ + \ + private: \ + inline MaybeConst T& get_raw_value_ref() noexcept \ + { \ + return *reinterpret_cast(this); \ + } \ + \ + inline const T& get_raw_value_ref() const noexcept \ + { \ + return *reinterpret_cast(this); \ + } \ + \ + inline T get_raw_value() const noexcept \ + { \ + auto ret_ptr = reinterpret_cast(this); \ + return *ret_ptr; \ + } \ + \ + /* get_raw_sandbox_value has to return a custom struct to deal with the \ + * adjusted machine model, to ensure */ \ + inline Sbx_##libId##_##T get_raw_sandbox_value( \ + rlbox_sandbox& sandbox) const noexcept \ + { \ + Sbx_##libId##_##T lhs; \ + const auto& rhs = get_raw_value_ref(); \ + constexpr auto Direction = detail::adjust_type_direction::TO_SANDBOX; \ + constexpr auto Context = detail::adjust_type_context::SANDBOX; \ + const void* example_unsandboxed_ptr = nullptr; \ + rlbox_sandbox* sandbox_ptr = &sandbox; \ + sandbox_fields_reflection_##libId##_class_##T(helper_convert_type, \ + helper_no_op) \ + \ + return lhs; \ + } \ + \ + inline const void* find_example_pointer_or_null() const noexcept \ + { \ + sandbox_fields_reflection_##libId##_class_##T( \ + helper_find_example_pointer_or_null, helper_no_op) \ + \ + return nullptr; \ + } \ + \ + public: \ + sandbox_fields_reflection_##libId##_class_##T(helper_create_tainted_field, \ + helper_no_op, \ + MaybeConst) \ + \ + tainted() = default; \ + tainted(const tainted& p) = default; \ + \ + tainted(const tainted_volatile& p) \ + { \ + auto& lhs = get_raw_value_ref(); \ + auto& rhs = p.get_sandbox_value_ref(); \ + constexpr auto Direction = \ + detail::adjust_type_direction::TO_APPLICATION; \ + constexpr auto Context = detail::adjust_type_context::EXAMPLE; \ + /* This is a tainted_volatile, so its address is a valid for use as */ \ + /* example_unsandboxed_ptr */ \ + const void* example_unsandboxed_ptr = &rhs; \ + rlbox_sandbox* sandbox_ptr = nullptr; \ + sandbox_fields_reflection_##libId##_class_##T(helper_convert_type, \ + helper_no_op) \ + } \ + \ + inline tainted_opaque to_opaque() \ + { \ + return *reinterpret_cast*>(this); \ + } \ + \ + inline auto UNSAFE_unverified() const { return get_raw_value(); } \ + inline auto UNSAFE_sandboxed(rlbox_sandbox& sandbox) const \ + { \ + return get_raw_sandbox_value(sandbox); \ + } \ + \ + template \ + inline auto unverified_safe_because(const char (&reason)[N]) const \ + { \ + RLBOX_UNUSED(reason); \ + return UNSAFE_unverified(); \ + } \ + \ + T copy_and_verify(std::function)> verifier) \ + { \ + return verifier(*this); \ + } \ + }; \ + \ + /* Had to delay the definition due, to mutually dependence between \ + tainted and tainted_volatile for structs */ \ + template \ + inline tainted_volatile& \ + tainted_volatile::operator=( \ + const tainted& rhs_wrap) \ + { \ + auto& lhs = get_sandbox_value_ref(); \ + auto& rhs = rhs_wrap.get_raw_value_ref(); \ + constexpr auto Direction = detail::adjust_type_direction::TO_SANDBOX; \ + constexpr auto Context = detail::adjust_type_context::EXAMPLE; \ + /* This is a tainted_volatile, so its address is a valid example for */ \ + /* use as example_unsandboxed_ptr */ \ + const void* example_unsandboxed_ptr = &lhs; \ + rlbox_sandbox* sandbox_ptr = nullptr; \ + sandbox_fields_reflection_##libId##_class_##T(helper_convert_type, \ + helper_no_op) \ + \ + return *this; \ + } + +#define tainted_data_specialization(T, libId) \ + tainted_data_specialization_helper( , T, libId) \ + tainted_data_specialization_helper(const, T, libId) + +#define convert_type_specialization(T, libId) \ + namespace detail { \ + template \ + class convert_type_class \ + { \ + public: \ + static inline void run(T& lhs, \ + const T_From& rhs, \ + const void* example_unsandboxed_ptr, \ + rlbox_sandbox* sandbox_ptr) \ + { \ + sandbox_fields_reflection_##libId##_class_##T(helper_convert_type, \ + helper_no_op) \ + } \ + }; \ + \ + template \ + class convert_type_class, \ + T_From> \ + { \ + public: \ + static inline void run(Sbx_##libId##_##T& lhs, \ + const T_From& rhs, \ + const void* example_unsandboxed_ptr, \ + rlbox_sandbox* sandbox_ptr) \ + { \ + sandbox_fields_reflection_##libId##_class_##T(helper_convert_type, \ + helper_no_op) \ + } \ + }; \ + } + +// clang-format off +#define rlbox_load_structs_from_library(libId) \ + namespace rlbox { \ + /* check that this macro is called in a global namespace */ \ + static_assert( \ + ::rlbox::detail::is_member_of_rlbox_detail, \ + "Invoke rlbox_load_structs_from_library in the global namespace"); \ + \ + sandbox_fields_reflection_##libId##_allClasses( \ + sandbox_equivalent_specialization) \ + \ + sandbox_fields_reflection_##libId##_allClasses( \ + tainted_data_specialization) \ + \ + sandbox_fields_reflection_##libId##_allClasses( \ + convert_type_specialization) \ + } \ + RLBOX_REQUIRE_SEMI_COLON + +// clang-format on \ No newline at end of file diff --git a/third_party/rlbox/include/rlbox_type_traits.hpp b/third_party/rlbox/include/rlbox_type_traits.hpp new file mode 100644 index 0000000000..b32e677c32 --- /dev/null +++ b/third_party/rlbox/include/rlbox_type_traits.hpp @@ -0,0 +1,546 @@ +#pragma once +// IWYU pragma: private, include "rlbox.hpp" +// IWYU pragma: friend "rlbox_.*\.hpp" + +#include +#include + +namespace rlbox::detail { + +#define RLBOX_ENABLE_IF(...) std::enable_if_t<__VA_ARGS__>* = nullptr + +template +constexpr bool true_v = true; + +template +constexpr bool is_fundamental_or_enum_v = + std::is_fundamental_v || std::is_enum_v; + +template +constexpr bool is_basic_type_v = + std::is_fundamental_v || std::is_enum_v || std::is_pointer_v; + +template +using valid_return_t = + std::conditional_t, void*, std::decay_t>; + +template +using valid_param_t = std::conditional_t, void*, T>; + +namespace func_first_arg_detail { + template + Arg func_first_arg_t_helper(Ret (*)(Arg, Rest...)); + + template + Arg func_first_arg_t_helper(Ret (F::*)(Arg, Rest...)); + + template + Arg func_first_arg_t_helper(Ret (F::*)(Arg, Rest...) const); + + template + decltype(func_first_arg_t_helper(&F::operator())) first_argument_helper(F); +} + +template +using func_first_arg_t = + decltype(func_first_arg_detail::first_argument_helper(std::declval())); + +namespace func_arg_nums_v_detail { + template + constexpr size_t helper_two(T_Ret (*)(T_Args...)) + { + return sizeof...(T_Args); + } + template + constexpr size_t helper() + { + constexpr T_Func* ptr = nullptr; + return helper_two(ptr); + } +} + +template +constexpr size_t func_arg_nums_v = func_arg_nums_v_detail::helper(); + +template +using valid_array_el_t = + std::conditional_t || std::is_function_v, int, T>; + +template +constexpr bool is_func_ptr_v = (std::is_pointer_v && + std::is_function_v>) || + std::is_member_function_pointer_v; + +template +constexpr bool is_func_or_func_ptr = std::is_function_v || is_func_ptr_v; + +template +constexpr bool is_one_level_ptr_v = + std::is_pointer_v && !std::is_pointer_v>; + +template +using add_const_if_this_const_t = + std::conditional_t>, + std::add_const_t, + T_Target>; + +template +using remove_const_from_pointer = std::conditional_t< + std::is_pointer_v, + std::add_pointer_t>>, + T>; + +template +using add_const_from_pointer = std::conditional_t< + std::is_pointer_v, + std::remove_pointer_t>>, + T>; + +template +using remove_cv_ref_t = std::remove_cv_t>; + +template +using c_to_std_array_t = + std::conditional_t, + std::array, std::extent_v>, + T>; + +namespace std_array_to_c_arr_detail { + template + struct W + { + using type = T; + }; + + template + W std_array_to_c_arr_helper(std::array); + + template + W std_array_to_c_arr_helper(T&&); +} + +template +using std_array_to_c_arr_t = + typename decltype(std_array_to_c_arr_detail::std_array_to_c_arr_helper( + std::declval()))::type; + +template +using dereference_result_t = + std::conditional_t, + std::remove_pointer_t, + std::remove_extent_t> // is_array + >; + +template +using value_type_t = + std::conditional_t, c_to_std_array_t, T>; + +template +using function_ptr_t = + std::conditional_t && + std::is_function_v>, + T, + int (*)(int)>; + +namespace is_c_or_std_array_detail { + template + struct is_c_or_std_array_helper; + + template + struct is_c_or_std_array_helper>> + : std::true_type + {}; + + template + std::true_type is_std_array_helper(std::array*); + + template + std::false_type is_std_array_helper(T*); + + template + constexpr bool is_std_array_v = + decltype(is_std_array_helper(std::declval>()))::value; + + template + struct is_c_or_std_array_helper>> + : std::true_type + {}; + + template + struct is_c_or_std_array_helper< + T, + std::enable_if_t && !is_std_array_v>> + : std::false_type + {}; +} + +template +constexpr bool is_std_array_v = is_c_or_std_array_detail::is_std_array_v; + +template +constexpr bool is_c_or_std_array_v = + is_c_or_std_array_detail::is_c_or_std_array_helper::value; + +namespace std_array_el_detail { + template + struct W + { + using type = T; + }; + + template + W is_std_array_helper(std::array*); + + template + W is_std_array_helper(T*); + + template + using std_array_el_t = decltype(std_array_el_detail::is_std_array_helper( + std::declval>)); +} + +template +using std_array_el_t = typename std_array_el_detail::std_array_el_t::type; + +namespace all_extents_same_detail { + + template + struct all_extents_same_helper; + + template + struct all_extents_same_helper< + T1, + T2, + std::enable_if_t != std::rank_v>> : std::false_type + {}; + + template + struct all_extents_same_helper< + T1, + T2, + std::enable_if_t == std::rank_v && + !std::is_array_v && !std::is_array_v>> + : std::true_type + {}; + + template + struct all_extents_same_helper< + T1, + T2, + std::enable_if_t == std::rank_v && + std::is_array_v && std::is_array_v && + std::extent_v != std::extent_v>> : std::false_type + {}; + + template + struct all_extents_same_helper< + T1, + T2, + std::enable_if_t == std::rank_v && + std::is_array_v && std::is_array_v && + std::extent_v == std::extent_v>> + { + static constexpr bool value = + all_extents_same_helper, + std::remove_extent_t>::value; + }; +} + +template +constexpr bool all_extents_same = + all_extents_same_detail::all_extents_same_helper::value; + +// remove all pointers/extent types +namespace remove_all_pointers_detail { + template + struct remove_all_pointers + { + typedef T type; + }; + + template + struct remove_all_pointers + { + typedef typename remove_all_pointers::type type; + }; +} + +template +using remove_all_pointers_t = + typename remove_all_pointers_detail::remove_all_pointers::type; + +// remove all pointers/extent types +namespace base_type_detail { + template + struct base_type + { + typedef T type; + }; + + template + struct base_type + { + typedef typename base_type::type type; + }; + + template + struct base_type + { + typedef typename base_type::type type; + }; + + template + struct base_type + { + typedef typename base_type::type type; + }; +} + +template +using base_type_t = typename base_type_detail::base_type::type; + +// convert types +namespace convert_detail { + template + struct convert_base_types_t_helper; + + template + struct convert_base_types_t_helper< + T, + T_ShortType, + T_IntType, + T_LongType, + T_LongLongType, + T_PointerType, + std::enable_if_t && !std::is_const_v>> + { + using type = T_ShortType; + }; + + template + struct convert_base_types_t_helper< + T, + T_ShortType, + T_IntType, + T_LongType, + T_LongLongType, + T_PointerType, + std::enable_if_t && !std::is_const_v>> + { + using type = T_IntType; + }; + + template + struct convert_base_types_t_helper< + T, + T_ShortType, + T_IntType, + T_LongType, + T_LongLongType, + T_PointerType, + std::enable_if_t && !std::is_const_v>> + { + using type = T_LongType; + }; + + template + struct convert_base_types_t_helper< + T, + T_ShortType, + T_IntType, + T_LongType, + T_LongLongType, + T_PointerType, + std::enable_if_t && !std::is_const_v>> + { + using type = T_LongLongType; + }; + + template + struct convert_base_types_t_helper< + T, + T_ShortType, + T_IntType, + T_LongType, + T_LongLongType, + T_PointerType, + std::enable_if_t && !std::is_const_v>> + { + using type = T_PointerType; + }; + + template + struct convert_base_types_t_helper< + T, + T_ShortType, + T_IntType, + T_LongType, + T_LongLongType, + T_PointerType, + std::enable_if_t && !std::is_same_v && + !std::is_same_v && !std::is_const_v && + !std::is_enum_v>> + { + using type = std::make_unsigned_t< + typename convert_base_types_t_helper, + T_ShortType, + T_IntType, + T_LongType, + T_LongLongType, + T_PointerType>::type>; + }; + + template + struct convert_base_types_t_helper< + T, + T_ShortType, + T_IntType, + T_LongType, + T_LongLongType, + T_PointerType, + std::enable_if_t<( + std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v || + std::is_floating_point_v || std::is_enum_v)&&!std::is_const_v>> + { + using type = T; + }; + + template + struct convert_base_types_t_helper< + T, + T_ShortType, + T_IntType, + T_LongType, + T_LongLongType, + T_PointerType, + std::enable_if_t && !std::is_const_v>> + { + using type = typename convert_base_types_t_helper< + std::remove_extent_t, + T_ShortType, + T_IntType, + T_LongType, + T_LongLongType, + T_PointerType>::type[std::extent_v]; + }; + + template + struct convert_base_types_t_helper>> + { + using type = std::add_const_t< + typename convert_base_types_t_helper, + T_ShortType, + T_IntType, + T_LongType, + T_LongLongType, + T_PointerType>::type>; + }; +} + +template +using convert_base_types_t = + typename convert_detail::convert_base_types_t_helper::type; + +namespace unsigned_int_of_size_t_detail { + template + struct unsigned_int_of_size_t_helper; + + template + struct unsigned_int_of_size_t_helper> + { + using type = uint8_t; + }; + + template + struct unsigned_int_of_size_t_helper> + { + using type = uint16_t; + }; + + template + struct unsigned_int_of_size_t_helper> + { + using type = uint32_t; + }; + + template + struct unsigned_int_of_size_t_helper> + { + using type = uint64_t; + }; +} + +template +using unsigned_int_of_size_t = + typename unsigned_int_of_size_t_detail::unsigned_int_of_size_t_helper< + T>::type; + +} \ No newline at end of file diff --git a/third_party/rlbox/include/rlbox_types.hpp b/third_party/rlbox/include/rlbox_types.hpp new file mode 100644 index 0000000000..b5821929da --- /dev/null +++ b/third_party/rlbox/include/rlbox_types.hpp @@ -0,0 +1,87 @@ +#pragma once +// IWYU pragma: private, include "rlbox.hpp" +// IWYU pragma: friend "rlbox_.*\.hpp" + +namespace rlbox { + +template +class tainted_opaque +{ +private: + T data{ 0 }; + +public: + template + void set_zero() + { + data = 0; + } +}; + +template +class tainted; + +template +class tainted_volatile; + +class tainted_boolean_hint; + +class tainted_int_hint; + +template +class rlbox_sandbox; + +template +class sandbox_callback; + +template +class app_pointer; + +class rlbox_noop_sandbox; + +class rlbox_dylib_sandbox; +} + +#define RLBOX_DEFINE_BASE_TYPES_FOR(SBXNAME, SBXTYPE) \ + namespace rlbox { \ + class rlbox_##SBXTYPE##_sandbox; \ + } \ + using rlbox_##SBXNAME##_sandbox_type = rlbox::rlbox_##SBXTYPE##_sandbox; \ + using rlbox_sandbox_##SBXNAME = \ + rlbox::rlbox_sandbox; \ + template \ + using sandbox_callback_##SBXNAME = \ + rlbox::sandbox_callback; \ + template \ + using tainted_##SBXNAME = rlbox::tainted; \ + template \ + using tainted_opaque_##SBXNAME = \ + rlbox::tainted_opaque; \ + template \ + using tainted_volatile_##SBXNAME = \ + rlbox::tainted_volatile; \ + using rlbox::tainted_boolean_hint; \ + template \ + using app_pointer_##SBXNAME = \ + rlbox::app_pointer; + +// This is like RLBOX_DEFINE_BASE_TYPES_FOR but with an explicit sandbox type +#define RLBOX_DEFINE_BASE_TYPES_FOR_TYPE(SBXNAME, SBXTYPE) \ + using rlbox_##SBXNAME##_sandbox_type = SBXTYPE; \ + using rlbox_sandbox_##SBXNAME = \ + rlbox::rlbox_sandbox; \ + template \ + using sandbox_callback_##SBXNAME = \ + rlbox::sandbox_callback; \ + template \ + using tainted_##SBXNAME = rlbox::tainted; \ + template \ + using tainted_opaque_##SBXNAME = \ + rlbox::tainted_opaque; \ + template \ + using tainted_volatile_##SBXNAME = \ + rlbox::tainted_volatile; \ + using rlbox::tainted_boolean_hint; \ + template \ + using app_pointer_##SBXNAME = \ + rlbox::app_pointer; diff --git a/third_party/rlbox/include/rlbox_unwrap.hpp b/third_party/rlbox/include/rlbox_unwrap.hpp new file mode 100644 index 0000000000..2cd00fe6ff --- /dev/null +++ b/third_party/rlbox/include/rlbox_unwrap.hpp @@ -0,0 +1,25 @@ +#pragma once +// IWYU pragma: private, include "rlbox.hpp" +// IWYU pragma: friend "rlbox_.*\.hpp" + +#include + +#include "rlbox_type_traits.hpp" +#include "rlbox_types.hpp" + +namespace rlbox::detail { + +template +inline auto unwrap_value(T_Rhs&& rhs) noexcept +{ + using T_RhsNoQ = detail::remove_cv_ref_t; + if constexpr (detail::rlbox_is_wrapper_v) { + return rhs.INTERNAL_unverified_safe(); + } else if constexpr (detail::rlbox_is_tainted_boolean_hint_v) { + return rhs.INTERNAL_unverified_safe(); + } else { + return rhs; + } +} + +} \ No newline at end of file diff --git a/third_party/rlbox/include/rlbox_wrapper_traits.hpp b/third_party/rlbox/include/rlbox_wrapper_traits.hpp new file mode 100644 index 0000000000..45ce5a93da --- /dev/null +++ b/third_party/rlbox/include/rlbox_wrapper_traits.hpp @@ -0,0 +1,171 @@ +#pragma once +// IWYU pragma: private, include "rlbox.hpp" +// IWYU pragma: friend "rlbox_.*\.hpp" + +#include + +#include "rlbox_types.hpp" + +namespace rlbox::detail { + +#define rlbox_generate_wrapper_check(name) \ + namespace detail_rlbox_is_##name \ + { \ + template \ + struct unwrapper : std::false_type \ + {}; \ + \ + template \ + struct unwrapper> : std::true_type \ + {}; \ + } \ + \ + template \ + constexpr bool rlbox_is_##name##_v = \ + detail_rlbox_is_##name::unwrapper::value; \ + RLBOX_REQUIRE_SEMI_COLON + +rlbox_generate_wrapper_check(tainted); +rlbox_generate_wrapper_check(tainted_volatile); +rlbox_generate_wrapper_check(tainted_opaque); +rlbox_generate_wrapper_check(sandbox_callback); + +#undef rlbox_generate_wrapper_check + +namespace detail_rlbox_is_tainted_boolean_hint { + template + struct unwrapper : std::false_type + {}; + + template<> + struct unwrapper : std::true_type + {}; +} + +template +constexpr bool rlbox_is_tainted_boolean_hint_v = + detail_rlbox_is_tainted_boolean_hint::unwrapper::value; + +template +constexpr bool rlbox_is_tainted_or_vol_v = + rlbox_is_tainted_v || rlbox_is_tainted_volatile_v; + +template +constexpr bool rlbox_is_tainted_or_opaque_v = + rlbox_is_tainted_v || rlbox_is_tainted_opaque_v; + +// tainted_hint is NOT considered a wrapper type... This carries no particular +// significant and is just a convention choice +template +constexpr bool rlbox_is_wrapper_v = + rlbox_is_tainted_v || rlbox_is_tainted_volatile_v || + rlbox_is_tainted_opaque_v || rlbox_is_sandbox_callback_v; + +namespace detail_rlbox_remove_wrapper { + template + struct unwrapper + { + using type = T; + using type_sbx = void; + }; + + template + struct unwrapper> + { + using type = T; + using type_sbx = T_Sbx; + }; + + template + struct unwrapper> + { + using type = T; + using type_sbx = T_Sbx; + }; + + template + struct unwrapper> + { + using type = T; + using type_sbx = T_Sbx; + }; + + template + struct unwrapper> + { + using type = T; + using type_sbx = T_Sbx; + }; +} + +template +using rlbox_remove_wrapper_t = + typename detail_rlbox_remove_wrapper::unwrapper::type; + +template +using rlbox_get_wrapper_sandbox_t = + typename detail_rlbox_remove_wrapper::unwrapper::type_sbx; + +template +using rlbox_tainted_opaque_to_tainted_t = + std::conditional_t, + tainted, T_Sbx>, + T>; + +// https://stackoverflow.com/questions/34974844/check-if-a-type-is-from-a-particular-namespace +namespace detail_is_member_of_rlbox_detail { + template + struct is_member_of_rlbox_detail_helper : std::false_type + {}; + + template + struct is_member_of_rlbox_detail_helper< + T, + decltype(struct_is_member_of_rlbox_detail(std::declval()))> + : std::true_type + {}; +} + +template +void struct_is_member_of_rlbox_detail(T&&); + +template +constexpr auto is_member_of_rlbox_detail = + detail_is_member_of_rlbox_detail::is_member_of_rlbox_detail_helper::value; + +// https://stackoverflow.com/questions/9644477/how-to-check-whether-a-class-has-specified-nested-class-definition-or-typedef-in +namespace detail_has_member_using_can_grant_deny_access { + template + struct has_member_using_can_grant_deny_access : std::false_type + {}; + + template + struct has_member_using_can_grant_deny_access< + T, + std::void_t> : std::true_type + {}; +} + +template +constexpr bool has_member_using_can_grant_deny_access_v = + detail_has_member_using_can_grant_deny_access:: + has_member_using_can_grant_deny_access::value; + +namespace detail_has_member_using_needs_internal_lookup_symbol { + template + struct has_member_using_needs_internal_lookup_symbol : std::false_type + {}; + + template + struct has_member_using_needs_internal_lookup_symbol< + T, + std::void_t> : std::true_type + {}; +} + +template +constexpr bool has_member_using_needs_internal_lookup_symbol_v = + detail_has_member_using_needs_internal_lookup_symbol:: + has_member_using_needs_internal_lookup_symbol::value; + +} \ No newline at end of file -- cgit v1.2.3