diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /third_party/rlbox/include | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rlbox/include')
-rw-r--r-- | third_party/rlbox/include/rlbox.hpp | 1349 | ||||
-rw-r--r-- | third_party/rlbox/include/rlbox_app_pointer.hpp | 93 | ||||
-rw-r--r-- | third_party/rlbox/include/rlbox_conversion.hpp | 273 | ||||
-rw-r--r-- | third_party/rlbox/include/rlbox_dylib_sandbox.hpp | 313 | ||||
-rw-r--r-- | third_party/rlbox/include/rlbox_helpers.hpp | 216 | ||||
-rw-r--r-- | third_party/rlbox/include/rlbox_noop_sandbox.hpp | 253 | ||||
-rw-r--r-- | third_party/rlbox/include/rlbox_policy_types.hpp | 392 | ||||
-rw-r--r-- | third_party/rlbox/include/rlbox_range.hpp | 32 | ||||
-rw-r--r-- | third_party/rlbox/include/rlbox_sandbox.hpp | 1094 | ||||
-rw-r--r-- | third_party/rlbox/include/rlbox_stdlib.hpp | 344 | ||||
-rw-r--r-- | third_party/rlbox/include/rlbox_stdlib_polyfill.hpp | 175 | ||||
-rw-r--r-- | third_party/rlbox/include/rlbox_struct_support.hpp | 354 | ||||
-rw-r--r-- | third_party/rlbox/include/rlbox_type_traits.hpp | 546 | ||||
-rw-r--r-- | third_party/rlbox/include/rlbox_types.hpp | 87 | ||||
-rw-r--r-- | third_party/rlbox/include/rlbox_unwrap.hpp | 25 | ||||
-rw-r--r-- | third_party/rlbox/include/rlbox_wrapper_traits.hpp | 171 |
16 files changed, 5717 insertions, 0 deletions
diff --git a/third_party/rlbox/include/rlbox.hpp b/third_party/rlbox/include/rlbox.hpp new file mode 100644 index 0000000000..48361d7a2b --- /dev/null +++ b/third_party/rlbox/include/rlbox.hpp @@ -0,0 +1,1349 @@ +#pragma once + +#include <array> +#include <cstring> +#include <memory> +#include <type_traits> +#include <utility> + +#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<template<typename, typename> 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<T_Wrap<T, T_Sbx>*>(this); } + inline auto& impl() const + { + return *static_cast<const T_Wrap<T, T_Sbx>*>(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<T_Sbx>& 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<size_t N> + inline auto unverified_safe_because(const char (&reason)[N]) const + { + RLBOX_UNUSED(reason); + static_assert(!std::is_pointer_v<T>, + "unverified_safe_because does not support pointers. Use " + "unverified_safe_pointer_because."); + return UNSAFE_unverified(); + } + + template<size_t N> + inline auto unverified_safe_pointer_because(size_t count, + const char (&reason)[N]) const + { + RLBOX_UNUSED(reason); + + static_assert(std::is_pointer_v<T>, "Expected pointer type"); + using T_Pointed = std::remove_pointer_t<T>; + if_constexpr_named(cond1, std::is_pointer_v<T_Pointed>) + { + 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<T_Sbx>(ret, bytes); + } + return ret; + } + + inline auto INTERNAL_unverified_safe() const { return UNSAFE_unverified(); } + +#define BinaryOpValAndPtr(opSymbol) \ + template<typename T_Rhs> \ + inline constexpr auto operator opSymbol(const T_Rhs& rhs) \ + const->tainted<decltype(std::declval<T>() opSymbol std::declval< \ + detail::rlbox_remove_wrapper_t<T_Rhs>>()), \ + T_Sbx> \ + { \ + static_assert(detail::is_basic_type_v<T>, \ + "Operator " #opSymbol \ + " only supported for primitive and pointer types"); \ + \ + auto raw_rhs = detail::unwrap_value(rhs); \ + \ + if constexpr (std::is_pointer_v<T>) { \ + static_assert(std::is_integral_v<decltype(raw_rhs)>, \ + "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<uintptr_t>(ptr) opSymbol raw_rhs * sizeof(*impl()); \ + auto no_overflow = rlbox_sandbox<T_Sbx>::is_in_same_sandbox( \ + reinterpret_cast<const void*>(ptr), \ + reinterpret_cast<const void*>(target)); \ + detail::dynamic_check( \ + no_overflow, \ + "Pointer arithmetic overflowed a pointer beyond sandbox memory"); \ + \ + return tainted<T, T_Sbx>::internal_factory(reinterpret_cast<T>(target)); \ + } else { \ + auto raw = impl().get_raw_value(); \ + auto ret = raw opSymbol raw_rhs; \ + using T_Ret = decltype(ret); \ + return tainted<T_Ret, T_Sbx>::internal_factory(ret); \ + } \ + } \ + RLBOX_REQUIRE_SEMI_COLON + + BinaryOpValAndPtr(+); + BinaryOpValAndPtr(-); + +#undef BinaryOpValAndPtr + +#define BinaryOp(opSymbol) \ + template<typename T_Rhs> \ + inline constexpr auto operator opSymbol(const T_Rhs& rhs) \ + const->tainted<decltype(std::declval<T>() opSymbol std::declval< \ + detail::rlbox_remove_wrapper_t<T_Rhs>>()), \ + T_Sbx> \ + { \ + static_assert(detail::is_fundamental_or_enum_v<T>, \ + "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<decltype(raw_rhs)> || \ + std::is_floating_point_v<decltype(raw_rhs)>, \ + "Can only operate on numeric types"); \ + \ + auto ret = raw opSymbol raw_rhs; \ + using T_Ret = decltype(ret); \ + return tainted<T_Ret, T_Sbx>::internal_factory(ret); \ + } \ + RLBOX_REQUIRE_SEMI_COLON + + BinaryOp(*); + BinaryOp(/); + BinaryOp(%); + BinaryOp(^); + BinaryOp(&); + BinaryOp(|); + BinaryOp(<<); + BinaryOp(>>); + +#undef BinaryOp + +#define CompoundAssignmentOp(opSymbol) \ + template<typename T_Rhs> \ + inline constexpr T_Wrap<T, T_Sbx>& 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<T, T_Sbx>& 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<T, T_Sbx> operator opSymbol##opSymbol(int) \ + { \ + tainted<T, T_Sbx> ret = impl(); \ + operator++(); \ + return ret; \ + } \ + RLBOX_REQUIRE_SEMI_COLON + + PostIncDecOps(+); + PostIncDecOps(-); + +#undef PostIncDecOps + +#define BooleanBinaryOp(opSymbol) \ + template<typename T_Rhs> \ + inline constexpr auto operator opSymbol(const T_Rhs& rhs) \ + const->tainted<decltype(std::declval<T>() opSymbol std::declval< \ + detail::rlbox_remove_wrapper_t<T_Rhs>>()), \ + T_Sbx> \ + { \ + static_assert(detail::is_fundamental_or_enum_v<T>, \ + "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<decltype(raw_rhs)>, \ + "Can only operate on numeric types"); \ + \ + auto ret = raw opSymbol raw_rhs; \ + using T_Ret = decltype(ret); \ + return tainted<T_Ret, T_Sbx>::internal_factory(ret); \ + } \ + \ + template<typename T_Rhs> \ + inline constexpr auto operator opSymbol(const T_Rhs&&) \ + const->tainted<decltype(std::declval<T>() opSymbol std::declval< \ + detail::rlbox_remove_wrapper_t<T_Rhs>>()), \ + T_Sbx> \ + { \ + rlbox_detail_static_fail_because( \ + detail::true_v<T_Rhs>, \ + "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<bool, T_Sbx> a = true;\n" \ + "auto r = a && true && sandbox.invoke_sandbox_function(getBool);\n" \ + "\n" \ + "However the following would be allowed\n" \ + "tainted<bool, T_Sbx> 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<bool, T_Sbx>(false); \ + } \ + RLBOX_REQUIRE_SEMI_COLON + + BooleanBinaryOp(&&); + BooleanBinaryOp(||); + +#undef BooleanBinaryOp + +#define UnaryOp(opSymbol) \ + inline auto operator opSymbol() \ + { \ + static_assert(detail::is_fundamental_or_enum_v<T>, \ + "Operator " #opSymbol " only supported for primitive"); \ + \ + auto raw = impl().get_raw_value(); \ + auto ret = opSymbol raw; \ + using T_Ret = decltype(ret); \ + return tainted<T_Ret, T_Sbx>::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<bool>, or a tainted_boolean_hint + * depending on the arguments to the binary expression. + */ +#define CompareOp(opSymbol, permit_pointers) \ + template<typename T_Rhs> \ + inline constexpr auto operator opSymbol(const T_Rhs& rhs) const \ + { \ + using T_RhsNoQ = detail::remove_cv_ref_t<T_Rhs>; \ + constexpr bool check_rhs_hint = \ + detail::rlbox_is_tainted_volatile_v<T_RhsNoQ> || \ + detail::rlbox_is_tainted_boolean_hint_v<T_RhsNoQ>; \ + constexpr bool check_lhs_hint = \ + detail::rlbox_is_tainted_volatile_v<T_Wrap<T, T_Sbx>>; \ + constexpr bool is_hint = check_lhs_hint || check_rhs_hint; \ + \ + constexpr bool is_unwrapped = \ + detail::rlbox_is_tainted_v<T_Wrap<T, T_Sbx>> && \ + std::is_null_pointer_v<T_RhsNoQ>; \ + \ + /* 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<T>) { \ + rlbox_detail_static_fail_because( \ + std::is_pointer_v<T>, \ + "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<bool, T_Sbx>(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<T>, + tainted_volatile<detail::dereference_result_t<T>, T_Sbx>, // is_pointer + T_Wrap<detail::dereference_result_t<T>, T_Sbx> // is_array + >; + +public: + template<typename T_Rhs> + inline const T_OpSubscriptArrRet& operator[](T_Rhs&& rhs) const + { + static_assert(std::is_pointer_v<T> || detail::is_c_or_std_array_v<T>, + "Operator [] supports pointers and arrays only"); + + auto raw_rhs = detail::unwrap_value(rhs); + static_assert(std::is_integral_v<decltype(raw_rhs)>, + "Can only index with numeric types"); + + if constexpr (std::is_pointer_v<T>) { + auto ptr = this->impl().get_raw_value(); + + // increment the target by size of the data structure + auto target = + reinterpret_cast<uintptr_t>(ptr) + raw_rhs * sizeof(*this->impl()); + auto no_overflow = rlbox_sandbox<T_Sbx>::is_in_same_sandbox( + ptr, reinterpret_cast<const void*>(target)); + detail::dynamic_check( + no_overflow, + "Pointer arithmetic overflowed a pointer beyond sandbox memory"); + + auto target_wrap = tainted<const T, T_Sbx>::internal_factory( + reinterpret_cast<const T>(target)); + return *target_wrap; + } else { + using T_Rhs_Unsigned = std::make_unsigned_t<decltype(raw_rhs)>; + detail::dynamic_check( + raw_rhs >= 0 && static_cast<T_Rhs_Unsigned>(raw_rhs) < + std::extent_v<detail::std_array_to_c_arr_t<T>, 0>, + "Static array indexing overflow"); + + const void* target_ptr; + if constexpr (detail::rlbox_is_tainted_v<T_Wrap<T, T_Sbx>>) { + 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<detail::dereference_result_t<T>, T_Sbx>; + auto wrapped_target_ptr = reinterpret_cast<T_Target*>(target_ptr); + return *wrapped_target_ptr; + } + } + + template<typename T_Rhs> + inline T_OpSubscriptArrRet& operator[](T_Rhs&& rhs) + { + return const_cast<T_OpSubscriptArrRet&>(std::as_const(*this)[rhs]); + } + +private: + using T_OpDerefRet = tainted_volatile<std::remove_pointer_t<T>, T_Sbx>; + +public: + inline T_OpDerefRet& operator*() const + { + static_assert(std::is_pointer_v<T>, "Operator * only allowed on pointers"); + auto ret_ptr_const = + reinterpret_cast<const T_OpDerefRet*>(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<T_OpDerefRet*>(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<T*> a; + // a->UNSAFE_unverified(); + inline const T_OpDerefRet* operator->() const + { + static_assert(std::is_pointer_v<T>, + "Operator -> only supported for pointer types"); + return reinterpret_cast<const T_OpDerefRet*>(impl().get_raw_value()); + } + + inline T_OpDerefRet* operator->() + { + return const_cast<T_OpDerefRet*>(std::as_const(*this).operator->()); + } + + inline auto operator!() + { + if_constexpr_named(cond1, std::is_pointer_v<T>) + { + return impl() == nullptr; + } + else if_constexpr_named(cond2, std::is_same_v<std::remove_cv_t<T>, 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<typename T_Func> + inline auto copy_and_verify(T_Func verifier) const + { + using T_Deref = std::remove_cv_t<std::remove_pointer_t<T>>; + + if_constexpr_named(cond1, detail::is_fundamental_or_enum_v<T>) + { + auto val = impl().get_raw_value(); + return verifier(val); + } + else if_constexpr_named( + cond2, detail::is_one_level_ptr_v<T> && !std::is_class_v<T_Deref>) + { + // Some paths don't use the verifier + RLBOX_UNUSED(verifier); + + if_constexpr_named(subcond1, std::is_void_v<T_Deref>) + { + 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<T>) + { + 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<T_Deref>(); + *val_copy = *val; + return verifier(std::move(val_copy)); + } + } + } + else if_constexpr_named( + cond3, detail::is_one_level_ptr_v<T> && std::is_class_v<T_Deref>) + { + auto val_copy = std::make_unique<tainted<T_Deref, T_Sbx>>(*impl()); + return verifier(std::move(val_copy)); + } + else if_constexpr_named(cond4, std::is_array_v<T>) + { + static_assert( + detail::is_fundamental_or_enum_v<std::remove_all_extents_t<T>>, + "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<std::remove_cv_t<std::remove_pointer_t<T>>>; + + // Template needed to ensure that function isn't instantiated for unsupported + // types like function pointers which causes compile errors... + template<typename T2 = T> + inline const void* verify_range_helper(std::size_t count) const + { + static_assert(std::is_pointer_v<T>); + static_assert(detail::is_fundamental_or_enum_v<T_CopyAndVerifyRangeEl>); + + detail::dynamic_check( + count != 0, + "Called copy_and_verify_range/copy_and_verify_string with count 0"); + + auto start = reinterpret_cast<const void*>(impl().get_raw_value()); + if (start == nullptr) { + return nullptr; + } + + detail::check_range_doesnt_cross_app_sbx_boundary<T_Sbx>( + start, count * sizeof(T_CopyAndVerifyRangeEl)); + + return start; + } + + template<typename T2 = T> + inline std::unique_ptr<T_CopyAndVerifyRangeEl[]> 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<T_CopyAndVerifyRangeEl[]>(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<int[]>)``. + * @return Whatever the verifier function returns. + */ + template<typename T_Func> + inline auto copy_and_verify_range(T_Func verifier, std::size_t count) const + { + static_assert(std::is_pointer_v<T>, + "Can only call copy_and_verify_range on pointers"); + + static_assert( + detail::is_fundamental_or_enum_v<T_CopyAndVerifyRangeEl>, + "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<T_CopyAndVerifyRangeEl[]> 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<char[]>)`` or ``T_Ret(*)(std::string)`` + * @return Whatever the verifier function returns. + */ + template<typename T_Func> + inline auto copy_and_verify_string(T_Func verifier) const + { + static_assert(std::is_pointer_v<T>, + "Can only call copy_and_verify_string on pointers"); + + static_assert(std::is_same_v<char, T_CopyAndVerifyRangeEl>, + "copy_and_verify_string only allows char*"); + + using T_VerifParam = detail::func_first_arg_t<T_Func>; + + auto start = impl().get_raw_value(); + if_constexpr_named( + cond1, + std::is_same_v<T_VerifParam, std::unique_ptr<char[]>> || + std::is_same_v<T_VerifParam, std::unique_ptr<const char[]>>) + { + if (start == nullptr) { + return verifier(nullptr); + } + + // it is safe to run strlen on a tainted<string> 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<T_CopyAndVerifyRangeEl[]> 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<T_VerifParam, std::string>) + { + if (start == nullptr) { + std::string param = ""; + return verifier(param); + } + + // it is safe to run strlen on a tainted<string> 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<char[]>, unique_ptr<const char[]> 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<typename T_Func> + inline auto copy_and_verify_address(T_Func verifier) const + { + static_assert(std::is_pointer_v<T>, + "copy_and_verify_address must be used on pointers"); + auto val = reinterpret_cast<uintptr_t>(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<typename T_Func> + inline auto copy_and_verify_buffer_address(T_Func verifier, + std::size_t size) const + { + static_assert(std::is_pointer_v<T>, + "copy_and_verify_address must be used on pointers"); + auto val = reinterpret_cast<uintptr_t>(verify_range_helper(size)); + return verifier(val); + } +}; + +#define BinaryOpWrappedRhs(opSymbol) \ + template<template<typename, typename> typename T_Wrap, \ + typename T, \ + typename T_Sbx, \ + typename T_Lhs, \ + RLBOX_ENABLE_IF(!detail::rlbox_is_wrapper_v<T_Lhs> && \ + !detail::rlbox_is_tainted_boolean_hint_v<T_Lhs>)> \ + inline constexpr auto operator opSymbol( \ + const T_Lhs& lhs, const tainted_base_impl<T_Wrap, T, T_Sbx>& 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<T_Lhs>, \ + "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<T_Lhs, T_Sbx>(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<template<typename, typename> typename T_Wrap, \ + typename T, \ + typename T_Sbx, \ + typename T_Lhs, \ + RLBOX_ENABLE_IF(!detail::rlbox_is_wrapper_v<T_Lhs> && \ + !detail::rlbox_is_tainted_boolean_hint_v<T_Lhs>)> \ + inline constexpr auto operator opSymbol( \ + const T_Lhs& lhs, const tainted_base_impl<T_Wrap, T, T_Sbx>& rhs) \ + { \ + static_assert( \ + std::is_arithmetic_v<T_Lhs>, \ + "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<T_Lhs, T_Sbx>(lhs) opSymbol rhs.impl(); \ + return ret; \ + } \ + \ + template<template<typename, typename> typename T_Wrap, \ + typename T, \ + typename T_Sbx, \ + typename T_Lhs, \ + RLBOX_ENABLE_IF(!detail::rlbox_is_wrapper_v<T_Lhs> && \ + !detail::rlbox_is_tainted_boolean_hint_v<T_Lhs>)> \ + inline constexpr auto operator opSymbol( \ + const T_Lhs&, const tainted_base_impl<T_Wrap, T, T_Sbx>&&) \ + { \ + rlbox_detail_static_fail_because( \ + detail::true_v<T_Lhs>, \ + "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<bool, T_Sbx> a = true;\n" \ + "auto r = a && true && sandbox.invoke_sandbox_function(getBool);\n" \ + "\n" \ + "However the following would be allowed\n" \ + "tainted<bool, T_Sbx> 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<bool, T_Sbx>(false); \ + } \ + RLBOX_REQUIRE_SEMI_COLON + +BooleanBinaryOpWrappedRhs(&&); +BooleanBinaryOpWrappedRhs(||); +#undef BooleanBinaryOpWrappedRhs + +namespace tainted_detail { + template<typename T, typename T_Sbx> + using tainted_repr_t = detail::c_to_std_array_t<T>; + + template<typename T, typename T_Sbx> + using tainted_vol_repr_t = + detail::c_to_std_array_t<std::add_volatile_t<typename rlbox_sandbox< + T_Sbx>::template convert_to_sandbox_equivalent_nonclass_t<T>>>; +} + +/** + * @brief Tainted values represent untrusted values that originate from the + * sandbox. + */ +template<typename T, typename T_Sbx> +class tainted : public tainted_base_impl<tainted, T, T_Sbx> +{ + KEEP_CLASSES_FRIENDLY + KEEP_CAST_FRIENDLY + + // Classes recieve their own specialization + static_assert( + !std::is_class_v<T>, + "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<T> || std::is_array_v<T>, + "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<tainted, T, T_Sbx>; + using T_AppType = tainted_detail::tainted_repr_t<T, T_Sbx>; + using T_SandboxedType = tainted_detail::tainted_vol_repr_t<T, T_Sbx>; + 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<T_AppType> get_raw_value() const noexcept + { + return data; + } + + inline std::remove_cv_t<T_SandboxedType> get_raw_sandbox_value( + rlbox_sandbox<T_Sbx>& sandbox) const + { + std::remove_cv_t<T_SandboxedType> ret; + + using namespace detail; + convert_type_non_class<T_Sbx, + adjust_type_direction::TO_SANDBOX, + adjust_type_context::SANDBOX>( + 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<T>) { + auto& data_ref = get_raw_value_ref(); + + for (size_t i = 0; i < std::extent_v<T>; i++) { + const void* ret = data[i].find_example_pointer_or_null(); + if (ret != nullptr) { + return ret; + } + } + } else if constexpr (std::is_pointer_v<T> && !detail::is_func_ptr_v<T>) { + auto data = get_raw_value(); + return data; + } + return nullptr; + } + + // Initializing with a pointer is dangerous and permitted only internally + template<typename T2 = T, RLBOX_ENABLE_IF(std::is_pointer_v<T2>)> + tainted(T2 val, const void* /* internal_tag */) + : data(val) + { + // Sanity check + static_assert(std::is_pointer_v<T>); + } + + template<typename T_Rhs> + static inline tainted<T, T_Sbx> internal_factory(T_Rhs&& rhs) + { + if constexpr (std::is_pointer_v<std::remove_reference_t<T_Rhs>>) { + const void* internal_tag = nullptr; + return tainted(std::forward<T_Rhs>(rhs), internal_tag); + } else { + return tainted(std::forward<T_Rhs>(rhs)); + } + } + +public: + tainted() = default; + tainted(const tainted<T, T_Sbx>& p) = default; + + tainted(const tainted_volatile<T, T_Sbx>& 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<const void*>(p_data_ref); + using namespace detail; + convert_type_non_class<T_Sbx, + adjust_type_direction::TO_APPLICATION, + adjust_type_context::EXAMPLE>( + 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<typename T2 = T, RLBOX_ENABLE_IF(std::is_pointer_v<T2>)> + tainted(T2 val) + : data(val) + { + rlbox_detail_static_fail_because( + std::is_pointer_v<T2>, + "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<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<T>, + "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<T_Func, Sbx> cb = ...;\n" + "tainted<T_Func, Sbx> foo = cb;\n\n" + "to\n\n" + "tainted<T_Func*, Sbx> foo_ptr = sandbox.malloc_in_sandbox<T_Func*>();\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<T>); + } + + // 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<typename T_Arg, + RLBOX_ENABLE_IF( + !detail::rlbox_is_wrapper_v<std::remove_reference_t<T_Arg>> && + detail::is_fundamental_or_enum_v<T> && + detail::is_fundamental_or_enum_v<std::remove_reference_t<T_Arg>>)> + tainted(T_Arg&& arg) + : data(std::forward<T_Arg>(arg)) + {} + + template<typename T_Rhs> + void assign_raw_pointer(rlbox_sandbox<T_Sbx>& sandbox, T_Rhs val) + { + static_assert(std::is_pointer_v<T_Rhs>, "Must be a pointer"); + static_assert(std::is_assignable_v<T&, T_Rhs>, + "Should assign pointers of compatible types."); + // Maybe a function pointer, so we need to cast + const void* cast_val = reinterpret_cast<const void*>(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<T, T_Sbx> to_opaque() + { + return *reinterpret_cast<tainted_opaque<T, T_Sbx>*>(this); + } + + template<typename T_Dummy = void> + operator bool() const + { + if_constexpr_named(cond1, std::is_pointer_v<T>) + { + // 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<typename T, typename T_Sbx> +inline tainted<T, T_Sbx> from_opaque(tainted_opaque<T, T_Sbx> val) +{ + return *reinterpret_cast<tainted<T, T_Sbx>*>(&val); +} + +/** + * @brief Tainted volatile values are like tainted values but still point to + * sandbox memory. Dereferencing a tainted pointer produces a tainted_volatile. + */ +template<typename T, typename T_Sbx> +class tainted_volatile : public tainted_base_impl<tainted_volatile, T, T_Sbx> +{ + KEEP_CLASSES_FRIENDLY + KEEP_CAST_FRIENDLY + + // Classes recieve their own specialization + static_assert( + !std::is_class_v<T>, + "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<T> || std::is_array_v<T>, + "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<tainted_volatile, T, T_Sbx>; + using T_AppType = tainted_detail::tainted_repr_t<T, T_Sbx>; + using T_SandboxedType = tainted_detail::tainted_vol_repr_t<T, T_Sbx>; + 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<T_AppType> get_raw_value() const + { + std::remove_cv_t<T_AppType> 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<const void*>(data_ref); + using namespace detail; + convert_type_non_class<T_Sbx, + adjust_type_direction::TO_APPLICATION, + adjust_type_context::EXAMPLE>( + ret, data, example_unsandboxed_ptr, nullptr /* sandbox_ptr */); + return ret; + } + + inline std::remove_cv_t<T_SandboxedType> get_raw_sandbox_value() + const noexcept + { + return data; + }; + + inline std::remove_cv_t<T_SandboxedType> get_raw_sandbox_value( + rlbox_sandbox<T_Sbx>& sandbox) const noexcept + { + RLBOX_UNUSED(sandbox); + return data; + }; + + tainted_volatile() = default; + tainted_volatile(const tainted_volatile<T, T_Sbx>& p) = default; + +public: + inline tainted<const T*, T_Sbx> operator&() const noexcept + { + auto ref = + detail::remove_volatile_from_ptr_cast(&this->get_sandbox_value_ref()); + auto ref_cast = reinterpret_cast<const T*>(ref); + return tainted<const T*, T_Sbx>::internal_factory(ref_cast); + } + + inline tainted<T*, T_Sbx> operator&() noexcept + { + return sandbox_const_cast<T*>(&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<typename T_RhsRef> + inline tainted_volatile<T, T_Sbx>& operator=(T_RhsRef&& val) + { + using T_Rhs = std::remove_reference_t<T_RhsRef>; + using T_Rhs_El = std::remove_all_extents_t<T_Rhs>; + + // 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<const void*>(data_ref); + // Some branches don't use this + RLBOX_UNUSED(example_unsandboxed_ptr); + + if_constexpr_named( + cond1, std::is_same_v<std::remove_const_t<T_Rhs>, std::nullptr_t>) + { + static_assert(std::is_pointer_v<T>, + "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<T_Rhs>) + { + using namespace detail; + convert_type_non_class<T_Sbx, + adjust_type_direction::TO_SANDBOX, + adjust_type_context::EXAMPLE>( + 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<T_Rhs>) + { + using namespace detail; + convert_type_non_class<T_Sbx, + adjust_type_direction::NO_CHANGE, + adjust_type_context::EXAMPLE>( + 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<T_Rhs>) + { + using T_RhsFunc = detail::rlbox_remove_wrapper_t<T_Rhs>; + + // need to perform some typechecking to ensure we are assigning compatible + // function pointer types only + if_constexpr_named(subcond1, !std::is_assignable_v<T&, T_RhsFunc>) + { + 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<T_SandboxedType>; + get_sandbox_value_ref() = (T_Cast)func; + } + } + else if_constexpr_named( + cond5, + detail::is_fundamental_or_enum_v<T> || + (std::is_array_v<T> && !std::is_pointer_v<T_Rhs_El>)) + { + detail::convert_type_fundamental_or_array(get_sandbox_value_ref(), val); + } + else if_constexpr_named( + cond6, std::is_pointer_v<T_Rhs> || std::is_pointer_v<T_Rhs_El>) + { + 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<typename T_Rhs> + void assign_raw_pointer(rlbox_sandbox<T_Sbx>& sandbox, T_Rhs val) + { + static_assert(std::is_pointer_v<T_Rhs>, "Must be a pointer"); + static_assert(std::is_assignable_v<T&, T_Rhs>, + "Should assign pointers of compatible types."); + // Maybe a function pointer, so we need to cast + const void* cast_val = reinterpret_cast<const void*>(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<T_Rhs>(cast_val); + } + + template<typename T_Dummy = void> + operator bool() const + { + rlbox_detail_static_fail_because( + detail::true_v<T_Dummy>, + "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<int**> a = ...;\n" + "assert(*a);\n\n" + "Instead you can write this code as \n" + "tainted<int*> 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 <map> +#ifndef RLBOX_USE_CUSTOM_SHARED_LOCK +# include <shared_mutex> +#endif +#include <type_traits> + +#include "rlbox_helpers.hpp" +#include "rlbox_type_traits.hpp" +#include "rlbox_types.hpp" + +namespace rlbox { + +template<typename T_PointerType> +class app_pointer_map +{ + +private: + using T_PointerTypeUnsigned = detail::unsigned_int_of_size_t<T_PointerType>; + + std::map<T_PointerTypeUnsigned, void*> 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 <array> +#include <cstring> +#include <limits> +#include <type_traits> + +#include "rlbox_helpers.hpp" +#include "rlbox_type_traits.hpp" +#include "rlbox_types.hpp" + +namespace rlbox::detail { + +template<typename T_To, typename T_From> +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<T_To>) + { + rlbox_detail_static_fail_because( + cond1, "Conversion target should be fundamental or enum type"); + } + else if_constexpr_named(cond2, !is_fundamental_or_enum_v<T_From>) + { + rlbox_detail_static_fail_because( + cond2, "Conversion source should be fundamental or enum type"); + } + else if_constexpr_named(cond3, is_enum_v<T_To> || is_enum_v<T_From>) + { + static_assert(std::is_same_v<detail::remove_cv_ref_t<T_To>, + detail::remove_cv_ref_t<T_From>>); + to = from; + } + else if_constexpr_named( + cond4, is_floating_point_v<T_To> || is_floating_point_v<T_From>) + { + static_assert(is_floating_point_v<T_To> && is_floating_point_v<T_From>); + // language coerces different float types + to = from; + } + else if_constexpr_named(cond5, is_integral_v<T_To> || is_integral_v<T_From>) + { + static_assert(is_integral_v<T_To> && is_integral_v<T_From>); + + 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<T_To> == is_signed_v<T_From> && + sizeof(T_To) >= sizeof(T_From)) { + // Eg: int64_t from int32_t, uint64_t from uint32_t + } else if constexpr (is_unsigned_v<T_To> && is_unsigned_v<T_From>) { + // Eg: uint32_t from uint64_t + dynamic_check(from <= numeric_limits<T_To>::max(), err_msg); + } else if constexpr (is_signed_v<T_To> && is_signed_v<T_From>) { + // Eg: int32_t from int64_t + dynamic_check(from >= numeric_limits<T_To>::min(), err_msg); + dynamic_check(from <= numeric_limits<T_To>::max(), err_msg); + } else if constexpr (is_unsigned_v<T_To> && is_signed_v<T_From>) { + 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<T_To>::max(); + dynamic_check(from <= static_cast<T_From>(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<T_To> && is_unsigned_v<T_From>) { + if constexpr (sizeof(T_To) <= sizeof(T_From)) { + // Eg: int32_t from uint32_t, int32_t from uint64_t + auto to_max = numeric_limits<T_To>::max(); + dynamic_check(from <= static_cast<T_From>(to_max), err_msg); + } else { + // Eg: int64_t from uint32_t + } + } + to = static_cast<T_To>(from); + } + else + { + constexpr auto unknownCase = !(cond1 || cond2 || cond3 || cond4 || cond5); + rlbox_detail_static_fail_because( + unknownCase, "Unexpected case for convert_type_fundamental"); + } +} + +template<typename T_To, typename T_From> +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<T_To>; + using T_From_C = std_array_to_c_arr_t<T_From>; + using T_To_El = remove_all_extents_t<T_To_C>; + using T_From_El = remove_all_extents_t<T_From_C>; + + if_constexpr_named(cond1, is_array_v<T_To_C> != is_array_v<T_From_C>) + { + rlbox_detail_static_fail_because( + cond1, "Conversion should not go between array and non array types"); + } + else if constexpr (!is_array_v<T_To_C>) + { + convert_type_fundamental(to, from); + } + else if_constexpr_named(cond2, !all_extents_same<T_To_C, T_From_C>) + { + rlbox_detail_static_fail_because( + cond2, "Conversion between arrays should have same dimensions"); + } + else if_constexpr_named(cond3, + is_pointer_v<T_To_El> || is_pointer_v<T_From_El>) + { + 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<T_To_El> == is_signed_v<T_From_El>) { + // 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<T_To_C>; 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<typename T_Sbx, + adjust_type_direction Direction, + adjust_type_context Context, + typename T_To, + typename T_From> +inline constexpr void convert_type_non_class( + T_To& to, + const T_From& from, + const void* example_unsandboxed_ptr, + rlbox_sandbox<T_Sbx>* 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<T_To>; + using T_From_C = std_array_to_c_arr_t<T_From>; + using T_To_El = remove_all_extents_t<T_To_C>; + using T_From_El = remove_all_extents_t<T_From_C>; + + if constexpr (is_pointer_v<T_To_C> || is_pointer_v<T_From_C>) { + + if constexpr (Direction == adjust_type_direction::NO_CHANGE) { + + static_assert(is_pointer_v<T_To_C> && is_pointer_v<T_From_C> && + sizeof(T_To_C) == sizeof(T_From_C)); + to = from; + + } else if constexpr (Direction == adjust_type_direction::TO_SANDBOX) { + + static_assert(is_pointer_v<T_From_C>); + // Maybe a function pointer, so convert + auto from_c = reinterpret_cast<const void*>(from); + if constexpr (Context == adjust_type_context::SANDBOX) { + RLBOX_DEBUG_ASSERT(sandbox_ptr != nullptr); + to = sandbox_ptr->template get_sandboxed_pointer<T_From_C>(from_c); + } else { + RLBOX_DEBUG_ASSERT(from_c == nullptr || + example_unsandboxed_ptr != nullptr); + to = + rlbox_sandbox<T_Sbx>::template get_sandboxed_pointer_no_ctx<T_From_C>( + from_c, example_unsandboxed_ptr); + } + + } else if constexpr (Direction == adjust_type_direction::TO_APPLICATION) { + + static_assert(is_pointer_v<T_To_C>); + if constexpr (Context == adjust_type_context::SANDBOX) { + RLBOX_DEBUG_ASSERT(sandbox_ptr != nullptr); + to = sandbox_ptr->template get_unsandboxed_pointer<T_To_C>(from); + } else { + RLBOX_DEBUG_ASSERT(from == 0 || example_unsandboxed_ptr != nullptr); + to = + rlbox_sandbox<T_Sbx>::template get_unsandboxed_pointer_no_ctx<T_To_C>( + from, example_unsandboxed_ptr); + } + } + + } else if constexpr (is_pointer_v<T_To_El> || is_pointer_v<T_From_El>) { + + 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<T_To_C>; i++) { + convert_type_non_class<T_Sbx, Direction, Context>( + 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<typename T_Sbx, + adjust_type_direction Direction, + adjust_type_context Context, + typename T_To, + typename T_From> +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<typename T_Sbx, + adjust_type_direction Direction, + adjust_type_context Context, + typename T_To, + typename T_From> +inline void convert_type(T_To& to, + const T_From& from, + const void* example_unsandboxed_ptr, + rlbox_sandbox<T_Sbx>* sandbox_ptr) +{ + if constexpr ((std::is_class_v<T_To> || + std::is_class_v<T_From>)&&!detail::is_std_array_v<T_To> && + !detail::is_std_array_v<T_From>) { + // Sanity check + static_assert(std::is_class_v<T_From> && std::is_class_v<T_To>); + convert_type_class<T_Sbx, Direction, Context, T_To, T_From>::run( + to, from, example_unsandboxed_ptr, sandbox_ptr); + } else { + convert_type_non_class<T_Sbx, Direction, Context>( + 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..0f156e44f3 --- /dev/null +++ b/third_party/rlbox/include/rlbox_dylib_sandbox.hpp @@ -0,0 +1,313 @@ +#pragma once + +#include <cstdint> +#include <cstdlib> +#include <mutex> +#ifndef RLBOX_USE_CUSTOM_SHARED_LOCK +# include <shared_mutex> +#endif +#include <utility> + +#if defined(_WIN32) +// Ensure the min/max macro in the header doesn't collide with functions in +// std:: +# ifndef NOMINMAX +# define NOMINMAX +# endif +# include <windows.h> +#else +# include <dlfcn.h> +#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<uint32_t N, typename T_Ret, typename... T_Args> + 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<T_Func>(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<typename T> + inline void* impl_get_unsandboxed_pointer(T_PointerType p) const + { + return p; + } + + template<typename T> + inline T_PointerType impl_get_sandboxed_pointer(const void* p) const + { + return const_cast<T_PointerType>(p); + } + + template<typename T> + 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<typename T> + 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<T_PointerType>(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<size_t>::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<typename T, typename T_Converted, typename... T_Args> + 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<typename T_Ret, typename... T_Args> + 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<MAX_CALLBACKS>([&](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<void*>( + callback_trampoline<I.value, T_Ret, T_Args...>); + } + }); + + return reinterpret_cast<T_PointerType>(chosen_trampoline); + } + + static inline std::pair<rlbox_dylib_sandbox*, void*> + 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<typename T_Ret, typename... T_Args> + 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<typename T> + inline T* impl_grant_access(T* src, size_t num, bool& success) + { + RLBOX_UNUSED(num); + success = true; + return src; + } + + template<typename T> + 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 <cstdlib> +#include <iostream> +#include <stdexcept> +#include <type_traits> +#include <utility> +#ifndef RLBOX_USE_CUSTOM_SHARED_LOCK +# include <mutex> +#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<typename... TArgs> + 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<std::shared_timed_mutex> name(__VA_ARGS__) +# define RLBOX_ACQUIRE_UNIQUE_GUARD(name, ...) \ + std::unique_lock<std::shared_timed_mutex> 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<typename T_Rhs> \ + inline auto operator opSymbol(T_Rhs rhs) \ + { \ + auto b = static_cast<__VA_ARGS__*>(this); \ + return (*b)opSymbol rhs; \ + } \ + RLBOX_REQUIRE_SEMI_COLON + + template<typename T> + inline auto remove_volatile_from_ptr_cast(T* ptr) + { + using T_Result = std::add_pointer_t<std::remove_volatile_t<T>>; + return const_cast<T_Result>(ptr); + } + + // https://stackoverflow.com/questions/37602057/why-isnt-a-for-loop-a-compile-time-expression + namespace compile_time_for_detail { + template<std::size_t N> + struct num + { + static const constexpr auto value = N; + }; + + template<class F, std::size_t... Is> + inline void compile_time_for_helper(F func, std::index_sequence<Is...>) + { + (func(num<Is>{}), ...); + } + } + + template<std::size_t N, typename F> + inline void compile_time_for(F func) + { + compile_time_for_detail::compile_time_for_helper( + func, std::make_index_sequence<N>()); + } + + template<typename T, typename T2> + [[nodiscard]] inline auto return_first_result(T first_task, T2 second_task) + { + using T_Result = rlbox::detail::polyfill::invoke_result_t<T>; + + if constexpr (std::is_void_v<T_Result>) { + first_task(); + second_task(); + } else { + auto val = first_task(); + second_task(); + return val; + } + } + + // Scope Exit guards + template<typename T_ExitFunc> + 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<typename T_ExitFunc> + [[nodiscard]] scope_exit<T_ExitFunc> make_scope_exit( + T_ExitFunc&& exitFunction) + { + return scope_exit<T_ExitFunc>(std::move(exitFunction)); + } + +/* +Make sure classes can access the private memmbers of tainted<T1> and +tainted_volatile. Ideally, this should be + +template <typename U1> +friend class tainted<U1, T_Sandbox>; + +But C++ doesn't seem to allow the above +*/ +#define KEEP_CLASSES_FRIENDLY \ + template<template<typename, typename> typename U1, typename U2, typename U3> \ + friend class tainted_base_impl; \ + \ + template<typename U1, typename U2> \ + friend class tainted; \ + \ + template<typename U1, typename U2> \ + friend class tainted_volatile; \ + \ + template<typename U1> \ + friend class rlbox_sandbox; \ + \ + template<typename U1, typename U2> \ + friend class sandbox_callback; \ + \ + template<typename U1, typename U2> \ + 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..007f083991 --- /dev/null +++ b/third_party/rlbox/include/rlbox_noop_sandbox.hpp @@ -0,0 +1,253 @@ +#pragma once + +#include <cstdint> +#include <cstdlib> +#include <mutex> +#ifndef RLBOX_USE_CUSTOM_SHARED_LOCK +# include <shared_mutex> +#endif +#include <utility> + +#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<uint32_t N, typename T_Ret, typename... T_Args> + 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<T_Func>(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<typename T> + inline void* impl_get_unsandboxed_pointer(T_PointerType p) const + { + return p; + } + + template<typename T> + inline T_PointerType impl_get_sandboxed_pointer(const void* p) const + { + return const_cast<T_PointerType>(p); + } + + template<typename T> + 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<typename T> + 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<T_PointerType>(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<size_t>::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<typename T = void> + 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<T, void>; + 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<void*>(&func_name) /* NOLINT */ + + template<typename T, typename T_Converted, typename... T_Args> + 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<typename T_Ret, typename... T_Args> + 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<MAX_CALLBACKS>([&](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<void*>( + callback_trampoline<I.value, T_Ret, T_Args...>); + } + }); + + return reinterpret_cast<T_PointerType>(chosen_trampoline); + } + + static inline std::pair<rlbox_noop_sandbox*, void*> + 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<typename T_Ret, typename... T_Args> + 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<typename T> + inline T* impl_grant_access(T* src, size_t num, bool& success) + { + RLBOX_UNUSED(num); + success = true; + return src; + } + + template<typename T> + 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..d2fbc500ee --- /dev/null +++ b/third_party/rlbox/include/rlbox_policy_types.hpp @@ -0,0 +1,392 @@ +#pragma once +// IWYU pragma: private, include "rlbox.hpp" +// IWYU pragma: friend "rlbox_.*\.hpp" + +#include <type_traits> +#include <utility> + +#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<typename T_Sbx, typename T_Ret, typename... T_Args> + using T_Cb = + std::conditional_t<std::is_void_v<T_Ret>, void, tainted<T_Ret, T_Sbx>> (*)( + rlbox_sandbox<T_Sbx>&, + tainted<T_Args, T_Sbx>...); + + template<typename T_Sbx, typename T_Ret, typename... T_Args> + T_Cb<T_Sbx, T_Ret, T_Args...> callback_type_helper(T_Ret (*)(T_Args...)); + + // Compute the expected type of the interceptor + template<typename T_Sbx, typename T_Ret, typename... T_Args> + using T_I = detail::convert_to_sandbox_equivalent_t<T_Ret, T_Sbx> (*)( + detail::convert_to_sandbox_equivalent_t<T_Args, T_Sbx>...); + + template<typename T_Sbx, typename T_Ret, typename... T_Args> + T_I<T_Sbx, T_Ret, T_Args...> interceptor_type_helper(T_Ret (*)(T_Args...)); +} + +template<typename T, typename T_Sbx> +class sandbox_callback +{ + KEEP_CLASSES_FRIENDLY + +private: + rlbox_sandbox<T_Sbx>* sandbox; + + using T_Callback = + decltype(callback_detail::callback_type_helper<T_Sbx>(std::declval<T>())); + 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<T_Sbx>(std::declval<T>())); + 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, T_Sbx>; + 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<typename T_Ret, typename... T_Args> + 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<T_Ret, T_Args...>(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<T_Sbx>* 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<sandbox_callback>(other)); + } + + inline sandbox_callback& operator=(sandbox_callback&& other) + { + if (this != &other) { + move_obj(std::forward<sandbox_callback>(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<T_Sbx>& sandbox) const noexcept + { + RLBOX_UNUSED(sandbox); + return get_raw_sandbox_value(); + } +}; + +template<typename T, typename T_Sbx> +class app_pointer +{ + KEEP_CLASSES_FRIENDLY + +private: + app_pointer_map<typename T_Sbx::T_PointerType>* 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<typename T_Sbx::T_PointerType>* 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<app_pointer>(other)); + } + + inline app_pointer& operator=(app_pointer&& other) + { + if (this != &other) { + move_obj(std::forward<app_pointer>(other)); + } + return *this; + } + + void unregister() + { + if (idx != 0) { + map->remove_app_ptr(idx); + map = nullptr; + idx = 0; + idx_unsandboxed = nullptr; + } + } + + tainted<T, T_Sbx> to_tainted() + { + return tainted<T, T_Sbx>::internal_factory( + reinterpret_cast<T>(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<T_Sbx>& 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<bool>` 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<size_t N> + 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<typename T = void> + inline bool copy_and_verify(...) const + { + rlbox_detail_static_fail_because( + detail::true_v<T>, + "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<int> val = tainted_ptr->member\n" + "if ((val == 5).copy_and_verify(...)) { ... } \n\n" + "tainted<int, T_Sbx> foo(rlbox_sandbox<T_Sbx>& 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<int>` 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<size_t N> + 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<typename T = void> + inline int copy_and_verify(...) const + { + rlbox_detail_static_fail_because( + detail::true_v<T>, + "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<int> val = tainted_ptr->member\n" + "if ((val == 5).copy_and_verify(...)) { ... } \n\n" + "tainted<int, T_Sbx> foo(rlbox_sandbox<T_Sbx>& 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 <cstdint> + +#include "rlbox_types.hpp" + +namespace rlbox::detail { + +// Checks that a given range is either entirely in a sandbox or entirely +// outside +template<typename T_Sbx> +inline void check_range_doesnt_cross_app_sbx_boundary(const void* ptr, + size_t size) +{ + auto ptr_start_val = reinterpret_cast<uintptr_t>(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<void*>(ptr_start_val); + auto ptr_end = reinterpret_cast<void*>(ptr_end_val); + + detail::dynamic_check( + rlbox_sandbox<T_Sbx>::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..63995c89ad --- /dev/null +++ b/third_party/rlbox/include/rlbox_sandbox.hpp @@ -0,0 +1,1094 @@ +#pragma once +// IWYU pragma: private, include "rlbox.hpp" +// IWYU pragma: friend "rlbox_.*\.hpp" + +#include <algorithm> +#include <atomic> +#ifdef RLBOX_MEASURE_TRANSITION_TIMES +# include <chrono> +#endif +#include <cstdlib> +#include <limits> +#include <map> +#include <mutex> +#ifndef RLBOX_USE_CUSTOM_SHARED_LOCK +# include <shared_mutex> +#endif +#ifdef RLBOX_MEASURE_TRANSITION_TIMES +# include <sstream> +# include <string> +#endif +#include <stdint.h> +#include <type_traits> +#include <utility> +#include <vector> + +#include "rlbox_conversion.hpp" +#include "rlbox_helpers.hpp" +#include "rlbox_stdlib_polyfill.hpp" +#include "rlbox_struct_support.hpp" +#include "rlbox_type_traits.hpp" +#include "rlbox_wrapper_traits.hpp" + +#ifdef RLBOX_MEASURE_TRANSITION_TIMES +using namespace std::chrono; +#endif + +namespace rlbox { + +namespace convert_fn_ptr_to_sandbox_equivalent_detail { + template<typename T, typename T_Sbx> + using conv = ::rlbox::detail::convert_to_sandbox_equivalent_t<T, T_Sbx>; + + template<typename T_Ret, typename... T_Args> + using T_Func = T_Ret (*)(T_Args...); + + template<typename T_Sbx, typename T_Ret, typename... T_Args> + T_Func<conv<T_Ret, T_Sbx>, conv<T_Args, T_Sbx>...> helper( + T_Ret (*)(T_Args...)); +} + +#if defined(RLBOX_MEASURE_TRANSITION_TIMES) || \ + defined(RLBOX_TRANSITION_ACTION_OUT) || defined(RLBOX_TRANSITION_ACTION_IN) +enum class rlbox_transition +{ + INVOKE, + CALLBACK +}; +#endif +#ifdef RLBOX_MEASURE_TRANSITION_TIMES +struct rlbox_transition_timing +{ + rlbox_transition invoke; + const char* name; + void* ptr; + int64_t time; + + std::string to_string() + { + std::ostringstream ret; + if (invoke == rlbox_transition::INVOKE) { + ret << name; + } else { + ret << "Callback " << ptr; + } + ret << " : " << time << "\n"; + + return ret.str(); + } +}; +#endif + +#ifndef RLBOX_SINGLE_THREADED_INVOCATIONS +# error \ + "RLBox does not yet support threading. Please define RLBOX_SINGLE_THREADED_INVOCATIONS prior to including RLBox and ensure you are only using it from a single thread. If threading is required, please file a bug." +#endif + +/** + * @brief Encapsulation for sandboxes. + * + * @tparam T_Sbx Type of sandbox. For the null sandbox this is + * `rlbox_noop_sandbox` + */ +template<typename T_Sbx> +class rlbox_sandbox : protected T_Sbx +{ + KEEP_CLASSES_FRIENDLY + +private: +#ifdef RLBOX_MEASURE_TRANSITION_TIMES + std::vector<rlbox_transition_timing> transition_times; +#endif + + static inline RLBOX_SHARED_LOCK(sandbox_list_lock); + // The actual type of the vector is std::vector<rlbox_sandbox<T_Sbx>*> + // However clang 5, 6 have bugs where compilation seg-faults on this type + // So we just use this std::vector<void*> + static inline std::vector<void*> sandbox_list; + + RLBOX_SHARED_LOCK(func_ptr_cache_lock); + std::map<std::string, void*> func_ptr_map; + + app_pointer_map<typename T_Sbx::T_PointerType> app_ptr_map; + + // This variable tracks of the sandbox has already been created/destroyed. + // APIs in this class should be called only when the sandbox is created. + // However, it is expensive to check in APIs such as invoke or in the callback + // interceptor. What's more, there could be time of check time of use issues + // in the checks as well. + // In general, we leave it up to the user to ensure these APIs are never + // called prior to sandbox construction or after destruction. We perform some + // conservative sanity checks, where they would not add too much overhead. + enum class Sandbox_Status + { + NOT_CREATED, + INITIALIZING, + CREATED, + CLEANING_UP + }; + std::atomic<Sandbox_Status> sandbox_created = Sandbox_Status::NOT_CREATED; + + std::mutex callback_lock; + std::vector<void*> callback_keys; + + void* transition_state = nullptr; + + template<typename T> + using convert_fn_ptr_to_sandbox_equivalent_t = decltype( + ::rlbox::convert_fn_ptr_to_sandbox_equivalent_detail::helper<T_Sbx>( + std::declval<T>())); + + template<typename T> + inline constexpr void check_invoke_param_type_is_ok() + { + using T_NoRef = std::remove_reference_t<T>; + + if_constexpr_named(cond1, detail::rlbox_is_wrapper_v<T_NoRef>) + { + if_constexpr_named( + subcond1, + !std::is_same_v<T_Sbx, detail::rlbox_get_wrapper_sandbox_t<T_NoRef>>) + { + rlbox_detail_static_fail_because( + cond1 && subcond1, + "Mixing tainted data from a different sandbox types. This could " + "happen due to couple of different reasons.\n" + "1. You are using 2 sandbox types for example'rlbox_noop_sandbox' " + "and 'rlbox_lucet_sandbox', and are passing tainted data from one " + "sandbox as parameters into a function call to the other sandbox. " + "This is not allowed, unwrap the tainted data with copy_and_verify " + "or other unwrapping APIs first.\n" + "2. You have inadvertantly forgotten to set/remove " + "RLBOX_USE_STATIC_CALLS depending on the sandbox type. Some sandbox " + "types like rlbox_noop_sandbox require this to be set to a given " + "value, while other types like rlbox_lucet_sandbox, require this not " + "to be set."); + } + } + else if_constexpr_named(cond2, + std::is_null_pointer_v<T_NoRef> || + detail::is_fundamental_or_enum_v<T_NoRef>) + {} + else + { + constexpr auto unknownCase = !(cond1 || cond2); + rlbox_detail_static_fail_because( + unknownCase, + "Arguments to a sandbox function call should be primitives or wrapped " + "types like tainted, callbacks etc."); + } + } + + template<typename T> + inline auto invoke_process_param(T&& param) + { + check_invoke_param_type_is_ok<T>(); + + using T_NoRef = std::remove_reference_t<T>; + + if constexpr (detail::rlbox_is_tainted_opaque_v<T_NoRef>) { + auto ret = from_opaque(param); + return ret.UNSAFE_sandboxed(*this); + } else if constexpr (detail::rlbox_is_wrapper_v<T_NoRef>) { + return param.UNSAFE_sandboxed(*this); + } else if constexpr (std::is_null_pointer_v<T_NoRef>) { + tainted<void*, T_Sbx> ret = nullptr; + return ret.UNSAFE_sandboxed(*this); + } else if constexpr (detail::is_fundamental_or_enum_v<T_NoRef>) { + // For unwrapped primitives, assign to a tainted var and then unwrap so + // that we adjust for machine model + tainted<T_NoRef, T_Sbx> ret = param; + return ret.UNSAFE_sandboxed(*this); + } else { + rlbox_detail_static_fail_because( + detail::true_v<T_NoRef>, + "Only tainted types, callbacks or primitive values such as ints can be " + "passed as parameters.\n" + "To make a parameter tainted, try moving the allocation into the " + "sandbox.\n" + "If the parameter is a callback, try registering the callback via the " + "register_callback API."); + } + } + + template<typename T, typename T_Arg> + inline tainted<T, T_Sbx> sandbox_callback_intercept_convert_param( + rlbox_sandbox<T_Sbx>& sandbox, + const T_Arg& arg) + { + tainted<T, T_Sbx> ret; + using namespace detail; + convert_type<T_Sbx, + adjust_type_direction::TO_APPLICATION, + adjust_type_context::SANDBOX>( + ret.get_raw_value_ref(), + arg, + nullptr /* example_unsandboxed_ptr */, + &sandbox); + return ret; + } + + template<typename T_Ret, typename... T_Args> + static detail::convert_to_sandbox_equivalent_t<T_Ret, T_Sbx> + sandbox_callback_interceptor( + detail::convert_to_sandbox_equivalent_t<T_Args, T_Sbx>... args) + { + std::pair<T_Sbx*, void*> context = + T_Sbx::impl_get_executed_callback_sandbox_and_key(); + auto& sandbox = *(reinterpret_cast<rlbox_sandbox<T_Sbx>*>(context.first)); + auto key = context.second; + + using T_Func_Ret = + std::conditional_t<std::is_void_v<T_Ret>, void, tainted<T_Ret, T_Sbx>>; + using T_Func = + T_Func_Ret (*)(rlbox_sandbox<T_Sbx>&, tainted<T_Args, T_Sbx>...); + auto target_fn_ptr = reinterpret_cast<T_Func>(key); + +#ifdef RLBOX_MEASURE_TRANSITION_TIMES + high_resolution_clock::time_point enter_time = high_resolution_clock::now(); + auto on_exit = rlbox::detail::make_scope_exit([&] { + auto exit_time = high_resolution_clock::now(); + int64_t ns = duration_cast<nanoseconds>(exit_time - enter_time).count(); + sandbox.transition_times.push_back( + rlbox_transition_timing{ rlbox_transition::CALLBACK, + nullptr /* func_name */, + key /* func_ptr */, + ns }); + }); +#endif +#ifdef RLBOX_TRANSITION_ACTION_OUT + RLBOX_TRANSITION_ACTION_OUT(rlbox_transition::CALLBACK, + nullptr /* func_name */, + key /* func_ptr */, + sandbox.transition_state); +#endif +#ifdef RLBOX_TRANSITION_ACTION_IN + auto on_exit_transition = rlbox::detail::make_scope_exit([&] { + RLBOX_TRANSITION_ACTION_IN(rlbox_transition::CALLBACK, + nullptr /* func_name */, + key /* func_ptr */, + sandbox.transition_state); + }); +#endif + if constexpr (std::is_void_v<T_Func_Ret>) { + (*target_fn_ptr)( + sandbox, + sandbox.template sandbox_callback_intercept_convert_param<T_Args>( + sandbox, args)...); + return; + } else { + auto tainted_ret = (*target_fn_ptr)( + sandbox, + sandbox.template sandbox_callback_intercept_convert_param<T_Args>( + sandbox, args)...); + + using namespace detail; + convert_to_sandbox_equivalent_t<T_Ret, T_Sbx> ret; + convert_type<T_Sbx, + adjust_type_direction::TO_SANDBOX, + adjust_type_context::SANDBOX>( + ret, + tainted_ret.get_raw_value_ref(), + nullptr /* example_unsandboxed_ptr */, + &sandbox); + return ret; + } + } + + /** + * @brief Unregister a callback function and disallow the sandbox from + * calling this function henceforth. + */ + template<typename T_Ret, typename... T_Args> + inline void unregister_callback(void* key) + { + // Silently swallowing the failure is better here as RAII types may try to + // cleanup callbacks after sandbox destruction + if (sandbox_created.load() != Sandbox_Status::CREATED) { + return; + } + + this->template impl_unregister_callback< + detail::convert_to_sandbox_equivalent_t<T_Ret, T_Sbx>, + detail::convert_to_sandbox_equivalent_t<T_Args, T_Sbx>...>(key); + + std::lock_guard<std::mutex> lock(callback_lock); + auto el_ref = std::find(callback_keys.begin(), callback_keys.end(), key); + detail::dynamic_check( + el_ref != callback_keys.end(), + "Unexpected state. Unregistering a callback that was never registered."); + callback_keys.erase(el_ref); + } + + static T_Sbx* find_sandbox_from_example(const void* example_sandbox_ptr) + { + detail::dynamic_check( + example_sandbox_ptr != nullptr, + "Internal error: received a null example pointer. Please file a bug."); + + RLBOX_ACQUIRE_SHARED_GUARD(lock, sandbox_list_lock); + for (auto sandbox_v : sandbox_list) { + auto sandbox = reinterpret_cast<rlbox_sandbox<T_Sbx>*>(sandbox_v); + if (sandbox->is_pointer_in_sandbox_memory(example_sandbox_ptr)) { + return sandbox; + } + } + + return nullptr; + } + + template<typename... T_Args> + static auto impl_create_sandbox_helper(rlbox_sandbox<T_Sbx>* this_ptr, + T_Args... args) + { + return this_ptr->impl_create_sandbox(std::forward<T_Args>(args)...); + } + +public: + /** + * @brief Unused member that allows the calling code to save data in a + * "per-sandbox" storage. This can be useful to save context which is used + * in callbacks. + */ + void* sandbox_storage; + + /***** Function to adjust for custom machine models *****/ + + template<typename T> + using convert_to_sandbox_equivalent_nonclass_t = + detail::convert_base_types_t<T, + typename T_Sbx::T_ShortType, + typename T_Sbx::T_IntType, + typename T_Sbx::T_LongType, + typename T_Sbx::T_LongLongType, + typename T_Sbx::T_PointerType>; + + T_Sbx* get_sandbox_impl() { return this; } + + /** + * @brief Create a new sandbox. + * + * @tparam T_Args Arguments passed to the underlying sandbox + * implementation. For the null sandbox, no arguments are necessary. + */ + template<typename... T_Args> + inline bool create_sandbox(T_Args... args) + { +#ifdef RLBOX_MEASURE_TRANSITION_TIMES + // Warm up the timer. The first call is always slow (at least on the test + // platform) + for (int i = 0; i < 10; i++) { + auto val = high_resolution_clock::now(); + RLBOX_UNUSED(val); + } +#endif + auto expected = Sandbox_Status::NOT_CREATED; + bool success = sandbox_created.compare_exchange_strong( + expected, Sandbox_Status::INITIALIZING /* desired */); + detail::dynamic_check( + success, + "create_sandbox called when sandbox already created/is being " + "created concurrently"); + + using T_Result = rlbox::detail::polyfill::invoke_result_t< + decltype(impl_create_sandbox_helper<T_Args...>), + decltype(this), + T_Args...>; + + bool created = true; + if constexpr (std::is_same_v<T_Result, void>) { + this->impl_create_sandbox(std::forward<T_Args>(args)...); + } else if constexpr (std::is_same_v<T_Result, bool>) { + created = this->impl_create_sandbox(std::forward<T_Args>(args)...); + } else { + rlbox_detail_static_fail_because( + (!std::is_same_v<T_Result, void> && !std::is_same_v<T_Result, bool>), + "Expected impl_create_sandbox to return void or a boolean"); + } + + if (created) { + sandbox_created.store(Sandbox_Status::CREATED); + RLBOX_ACQUIRE_UNIQUE_GUARD(lock, sandbox_list_lock); + sandbox_list.push_back(this); + } + + return created; + } + + /** + * @brief Destroy sandbox and reclaim any memory. + */ + inline auto destroy_sandbox() + { + auto expected = Sandbox_Status::CREATED; + bool success = sandbox_created.compare_exchange_strong( + expected, Sandbox_Status::CLEANING_UP /* desired */); + + detail::dynamic_check( + success, + "destroy_sandbox called without sandbox creation/is being " + "destroyed concurrently"); + + { + RLBOX_ACQUIRE_UNIQUE_GUARD(lock, sandbox_list_lock); + auto el_ref = std::find(sandbox_list.begin(), sandbox_list.end(), this); + detail::dynamic_check( + el_ref != sandbox_list.end(), + "Unexpected state. Destroying a sandbox that was never initialized."); + sandbox_list.erase(el_ref); + } + + sandbox_created.store(Sandbox_Status::NOT_CREATED); + return this->impl_destroy_sandbox(); + } + + template<typename T> + inline T get_unsandboxed_pointer( + convert_to_sandbox_equivalent_nonclass_t<T> p) const + { + static_assert(std::is_pointer_v<T>); + if (p == 0) { + return nullptr; + } + auto ret = this->template impl_get_unsandboxed_pointer<T>(p); + return reinterpret_cast<T>(ret); + } + + template<typename T> + inline convert_to_sandbox_equivalent_nonclass_t<T> get_sandboxed_pointer( + const void* p) const + { + static_assert(std::is_pointer_v<T>); + if (p == nullptr) { + return 0; + } + return this->template impl_get_sandboxed_pointer<T>(p); + } + + template<typename T> + static inline T get_unsandboxed_pointer_no_ctx( + convert_to_sandbox_equivalent_nonclass_t<T> p, + const void* example_unsandboxed_ptr) + { + static_assert(std::is_pointer_v<T>); + if (p == 0) { + return nullptr; + } + auto ret = T_Sbx::template impl_get_unsandboxed_pointer_no_ctx<T>( + p, example_unsandboxed_ptr, find_sandbox_from_example); + return reinterpret_cast<T>(ret); + } + + template<typename T> + static inline convert_to_sandbox_equivalent_nonclass_t<T> + get_sandboxed_pointer_no_ctx(const void* p, + const void* example_unsandboxed_ptr) + { + static_assert(std::is_pointer_v<T>); + if (p == nullptr) { + return 0; + } + return T_Sbx::template impl_get_sandboxed_pointer_no_ctx<T>( + p, example_unsandboxed_ptr, find_sandbox_from_example); + } + + /** + * @brief Allocate a new pointer that is accessible to both the application + * and sandbox. The pointer is allocated in sandbox memory. + * + * @tparam T The type of the pointer you want to create. If T=int, this + * would return a pointer to an int. + * + * @return tainted<T*, T_Sbx> Tainted pointer accessible to the application + * and sandbox. + */ + template<typename T> + inline tainted<T*, T_Sbx> malloc_in_sandbox() + { + const uint32_t defaultCount = 1; + return malloc_in_sandbox<T>(defaultCount); + } + + /** + * @brief Allocate an array that is accessible to both the application + * and sandbox. The pointer is allocated in sandbox memory. + * + * @tparam T The type of the array elements you want to create. If T=int, this + * would return a pointer to an array of ints. + * + * @param count The number of array elements to allocate. + * + * @return tainted<T*, T_Sbx> Tainted pointer accessible to the application + * and sandbox. + */ + template<typename T> + inline tainted<T*, T_Sbx> malloc_in_sandbox(uint32_t count) + { + // Silently swallowing the failure is better here as RAII types may try to + // malloc after sandbox destruction + if (sandbox_created.load() != Sandbox_Status::CREATED) { + return tainted<T*, T_Sbx>::internal_factory(nullptr); + } + + detail::dynamic_check(count != 0, "Malloc tried to allocate 0 bytes"); + if constexpr (sizeof(T) >= std::numeric_limits<uint32_t>::max()) { + rlbox_detail_static_fail_because(sizeof(T) >= + std::numeric_limits<uint32_t>::max(), + "Tried to allocate an object over 4GB."); + } + auto total_size = static_cast<uint64_t>(sizeof(T)) * count; + if constexpr (sizeof(size_t) == 4) { + // On a 32-bit platform, we need to make sure that total_size is not >=4GB + detail::dynamic_check(total_size < std::numeric_limits<uint32_t>::max(), + "Tried to allocate memory over 4GB"); + } else if constexpr (sizeof(size_t) != 8) { + // Double check we are on a 64-bit platform + // Note for static checks we need to have some dependence on T, so adding + // a dummy + constexpr bool dummy = sizeof(T) >= 0; + rlbox_detail_static_fail_because(dummy && sizeof(size_t) != 8, + "Expected 32 or 64 bit platform."); + } + auto ptr_in_sandbox = this->impl_malloc_in_sandbox(total_size); + auto ptr = get_unsandboxed_pointer<T*>(ptr_in_sandbox); + if (!ptr) { + return tainted<T*, T_Sbx>(nullptr); + } + detail::dynamic_check(is_pointer_in_sandbox_memory(ptr), + "Malloc returned pointer outside the sandbox memory"); + auto ptr_end = reinterpret_cast<uintptr_t>(ptr + (count - 1)); + detail::dynamic_check( + is_in_same_sandbox(ptr, reinterpret_cast<void*>(ptr_end)), + "Malloc returned a pointer whose range goes beyond sandbox memory"); + auto cast_ptr = reinterpret_cast<T*>(ptr); + return tainted<T*, T_Sbx>::internal_factory(cast_ptr); + } + + /** + * @brief Free the memory referenced by the tainted pointer. + * + * @param ptr Pointer to sandbox memory to free. + */ + template<typename T> + inline void free_in_sandbox(tainted<T*, T_Sbx> ptr) + { + // Silently swallowing the failure is better here as RAII types may try to + // free after sandbox destruction + if (sandbox_created.load() != Sandbox_Status::CREATED) { + return; + } + + this->impl_free_in_sandbox(ptr.get_raw_sandbox_value(*this)); + } + + /** + * @brief Free the memory referenced by a tainted_volatile pointer ref. + * + * @param ptr_ref Pointer reference to sandbox memory to free. + */ + template<typename T> + inline void free_in_sandbox(tainted_volatile<T, T_Sbx>& ptr_ref) + { + tainted<T, T_Sbx> ptr = ptr_ref; + free_in_sandbox(ptr); + } + + /** + * @brief Free the memory referenced by a tainted_opaque pointer. + * + * @param ptr_opaque Opaque pointer to sandbox memory to free. + */ + template<typename T> + inline void free_in_sandbox(tainted_opaque<T, T_Sbx> ptr_opaque) + { + tainted<T, T_Sbx> ptr = from_opaque(ptr_opaque); + free_in_sandbox(ptr); + } + + /** + * @brief Check if two pointers are in the same sandbox. + * For the null-sandbox, this always returns true. + */ + static inline bool is_in_same_sandbox(const void* p1, const void* p2) + { + const size_t num_args = + detail::func_arg_nums_v<decltype(T_Sbx::impl_is_in_same_sandbox)>; + if constexpr (num_args == 2) { + return T_Sbx::impl_is_in_same_sandbox(p1, p2); + } else { + return T_Sbx::impl_is_in_same_sandbox(p1, p2, find_sandbox_from_example); + } + } + + /** + * @brief Check if the pointer points to this sandbox's memory. + * For the null-sandbox, this always returns true. + */ + inline bool is_pointer_in_sandbox_memory(const void* p) + { + return this->impl_is_pointer_in_sandbox_memory(p); + } + + /** + * @brief Check if the pointer points to application memory. + * For the null-sandbox, this always returns true. + */ + inline bool is_pointer_in_app_memory(const void* p) + { + return this->impl_is_pointer_in_app_memory(p); + } + + inline size_t get_total_memory() { return this->impl_get_total_memory(); } + + inline void* get_memory_location() + { + return this->impl_get_memory_location(); + } + + void* get_transition_state() { return transition_state; } + + void set_transition_state(void* new_state) { transition_state = new_state; } + + /** + * @brief For internal use only. + * Grant access of the passed in buffer in to the sandbox instance. Called by + * internal APIs only if the underlying sandbox supports + * can_grant_deny_access by including the line + * ``` + * using can_grant_deny_access = void; + * ``` + */ + template<typename T> + inline tainted<T*, T_Sbx> INTERNAL_grant_access(T* src, + size_t num, + bool& success) + { + auto ret = this->impl_grant_access(src, num, success); + return tainted<T*, T_Sbx>::internal_factory(ret); + } + + /** + * @brief For internal use only. + * Grant access of the passed in buffer in to the sandbox instance. Called by + * internal APIs only if the underlying sandbox supports + * can_grant_deny_access by including the line + * ``` + * using can_grant_deny_access = void; + * ``` + */ + template<typename T> + inline T* INTERNAL_deny_access(tainted<T*, T_Sbx> src, + size_t num, + bool& success) + { + auto ret = + this->impl_deny_access(src.INTERNAL_unverified_safe(), num, success); + return ret; + } + + void* lookup_symbol(const char* func_name) + { + { + RLBOX_ACQUIRE_SHARED_GUARD(lock, func_ptr_cache_lock); + + auto func_ptr_ref = func_ptr_map.find(func_name); + if (func_ptr_ref != func_ptr_map.end()) { + return func_ptr_ref->second; + } + } + + void* func_ptr = this->impl_lookup_symbol(func_name); + RLBOX_ACQUIRE_UNIQUE_GUARD(lock, func_ptr_cache_lock); + func_ptr_map[func_name] = func_ptr; + return func_ptr; + } + + void* internal_lookup_symbol(const char* func_name) + { + { + RLBOX_ACQUIRE_SHARED_GUARD(lock, func_ptr_cache_lock); + + auto func_ptr_ref = func_ptr_map.find(func_name); + if (func_ptr_ref != func_ptr_map.end()) { + return func_ptr_ref->second; + } + } + + void* func_ptr = 0; + if constexpr (rlbox::detail:: + has_member_using_needs_internal_lookup_symbol_v<T_Sbx>) { + func_ptr = this->impl_internal_lookup_symbol(func_name); + } else { + func_ptr = this->impl_lookup_symbol(func_name); + } + RLBOX_ACQUIRE_UNIQUE_GUARD(lock, func_ptr_cache_lock); + func_ptr_map[func_name] = func_ptr; + return func_ptr; + } + + // this is an internal function invoked from macros, so it has be public + template<typename T, typename... T_Args> + inline auto INTERNAL_invoke_with_func_name(const char* func_name, + T_Args&&... params) + { + return INTERNAL_invoke_with_func_ptr<T, T_Args...>( + func_name, lookup_symbol(func_name), std::forward<T_Args>(params)...); + } + + // this is an internal function invoked from macros, so it has be public + // Explicitly don't use inline on this, as this adds a lot of instructions + // prior to function call. What's more, by not inlining, different function + // calls with the same signature can share the same code segments for + // sandboxed function execution in the binary + template<typename T, typename... T_Args> + auto INTERNAL_invoke_with_func_ptr(const char* func_name, + void* func_ptr, + T_Args&&... params) + { + // unused in some paths + RLBOX_UNUSED(func_name); +#ifdef RLBOX_MEASURE_TRANSITION_TIMES + auto enter_time = high_resolution_clock::now(); + auto on_exit = rlbox::detail::make_scope_exit([&] { + auto exit_time = high_resolution_clock::now(); + int64_t ns = duration_cast<nanoseconds>(exit_time - enter_time).count(); + transition_times.push_back(rlbox_transition_timing{ + rlbox_transition::INVOKE, func_name, func_ptr, ns }); + }); +#endif +#ifdef RLBOX_TRANSITION_ACTION_IN + RLBOX_TRANSITION_ACTION_IN( + rlbox_transition::INVOKE, func_name, func_ptr, transition_state); +#endif +#ifdef RLBOX_TRANSITION_ACTION_OUT + auto on_exit_transition = rlbox::detail::make_scope_exit([&] { + RLBOX_TRANSITION_ACTION_OUT( + rlbox_transition::INVOKE, func_name, func_ptr, transition_state); + }); +#endif + (check_invoke_param_type_is_ok<T_Args>(), ...); + + static_assert( + rlbox::detail::polyfill::is_invocable_v< + T, + detail::rlbox_remove_wrapper_t<std::remove_reference_t<T_Args>>...>, + "Mismatched arguments types for function"); + + using T_Result = rlbox::detail::polyfill::invoke_result_t< + T, + detail::rlbox_remove_wrapper_t<std::remove_reference_t<T_Args>>...>; + + using T_Converted = + std::remove_pointer_t<convert_fn_ptr_to_sandbox_equivalent_t<T*>>; + + if constexpr (std::is_void_v<T_Result>) { + this->template impl_invoke_with_func_ptr<T>( + reinterpret_cast<T_Converted*>(func_ptr), + invoke_process_param(params)...); + return; + } else { + auto raw_result = this->template impl_invoke_with_func_ptr<T>( + reinterpret_cast<T_Converted*>(func_ptr), + invoke_process_param(params)...); + tainted<T_Result, T_Sbx> wrapped_result; + using namespace detail; + convert_type<T_Sbx, + adjust_type_direction::TO_APPLICATION, + adjust_type_context::SANDBOX>( + wrapped_result.get_raw_value_ref(), + raw_result, + nullptr /* example_unsandboxed_ptr */, + this /* sandbox_ptr */); + return wrapped_result; + } + } + + // Useful in the porting stage to temporarily allow non tainted pointers to go + // through. This will only ever work in the rlbox_noop_sandbox. Any sandbox + // that actually enforces isolation will crash here. + template<typename T2> + tainted<T2, T_Sbx> UNSAFE_accept_pointer(T2 ptr) + { + static_assert(std::is_pointer_v<T2>, + "UNSAFE_accept_pointer expects a pointer param"); + tainted<T2, T_Sbx> ret; + ret.assign_raw_pointer(*this, ptr); + return ret; + } + + template<typename T_Ret, typename... T_Args> + using T_Cb_no_wrap = detail::rlbox_remove_wrapper_t<T_Ret>( + detail::rlbox_remove_wrapper_t<T_Args>...); + + template<typename T_Ret> + sandbox_callback<T_Cb_no_wrap<T_Ret>*, T_Sbx> register_callback(T_Ret (*)()) + { + rlbox_detail_static_fail_because( + detail::true_v<T_Ret>, + "Modify the callback to change the first parameter to a sandbox. " + "For instance if a callback has type\n\n" + "int foo() {...}\n\n" + "Change this to \n\n" + "tainted<int, T_Sbx> foo(rlbox_sandbox<T_Sbx>& sandbox) {...}\n"); + + // this is never executed, but we need it for the function to type-check + std::abort(); + } + + /** + * @brief Expose a callback function to the sandboxed code. + * + * @param func_ptr The callback to expose. + * + * @tparam T_RL Sandbox reference type (first argument). + * @tparam T_Ret Return type of callback. Must be tainted or void. + * @tparam T_Args Types of remaining callback arguments. Must be tainted. + * + * @return Wrapped callback function pointer that can be passed to the + * sandbox. + */ + template<typename T_RL, typename T_Ret, typename... T_Args> + sandbox_callback<T_Cb_no_wrap<T_Ret, T_Args...>*, T_Sbx> register_callback( + T_Ret (*func_ptr)(T_RL, T_Args...)) + { + // Some branches don't use the param + RLBOX_UNUSED(func_ptr); + + if_constexpr_named(cond1, !std::is_same_v<T_RL, rlbox_sandbox<T_Sbx>&>) + { + rlbox_detail_static_fail_because( + cond1, + "Modify the callback to change the first parameter to a sandbox. " + "For instance if a callback has type\n\n" + "int foo(int a, int b) {...}\n\n" + "Change this to \n\n" + "tainted<int, T_Sbx> foo(rlbox_sandbox<T_Sbx>& sandbox, " + "tainted<int, T_Sbx> a, tainted<int, T_Sbx> b) {...}\n"); + } + else if_constexpr_named( + cond2, !(detail::rlbox_is_tainted_or_opaque_v<T_Args> && ...)) + { + rlbox_detail_static_fail_because( + cond2, + "Change all arguments to the callback have to be tainted or " + "tainted_opaque. " + "For instance if a callback has type\n\n" + "int foo(int a, int b) {...}\n\n" + "Change this to \n\n" + "tainted<int, T_Sbx> foo(rlbox_sandbox<T_Sbx>& sandbox, " + "tainted<int, T_Sbx> a, tainted<int, T_Sbx> b) {...}\n"); + } + else if_constexpr_named( + cond3, (std::is_array_v<detail::rlbox_remove_wrapper_t<T_Args>> || ...)) + { + rlbox_detail_static_fail_because( + cond3, + "Change all static array arguments to the callback to be pointers. " + "For instance if a callback has type\n\n" + "int foo(int a[4]) {...}\n\n" + "Change this to \n\n" + "tainted<int, T_Sbx> foo(rlbox_sandbox<T_Sbx>& sandbox, " + "tainted<int*, T_Sbx> a) {...}\n"); + } + else if_constexpr_named( + cond4, + !(std::is_void_v<T_Ret> || detail::rlbox_is_tainted_or_opaque_v<T_Ret>)) + { + rlbox_detail_static_fail_because( + cond4, + "Change the callback return type to be tainted or tainted_opaque if it " + "is not void. " + "For instance if a callback has type\n\n" + "int foo(int a, int b) {...}\n\n" + "Change this to \n\n" + "tainted<int, T_Sbx> foo(rlbox_sandbox<T_Sbx>& sandbox, " + "tainted<int, T_Sbx> a, tainted<int, T_Sbx> b) {...}\n"); + } + else + { + detail::dynamic_check( + sandbox_created.load() == Sandbox_Status::CREATED, + "register_callback called without sandbox creation"); + + // Need unique key for each callback we register - just use the func addr + void* unique_key = reinterpret_cast<void*>(func_ptr); + + // Make sure that the user hasn't previously registered this function... + // If they have, we would returning 2 owning types (sandbox_callback) to + // the same callback which would be bad + { + std::lock_guard<std::mutex> lock(callback_lock); + bool exists = + std::find(callback_keys.begin(), callback_keys.end(), unique_key) != + callback_keys.end(); + detail::dynamic_check( + !exists, "You have previously already registered this callback."); + callback_keys.push_back(unique_key); + } + + auto callback_interceptor = + sandbox_callback_interceptor<detail::rlbox_remove_wrapper_t<T_Ret>, + detail::rlbox_remove_wrapper_t<T_Args>...>; + + auto callback_trampoline = this->template impl_register_callback< + detail::convert_to_sandbox_equivalent_t< + detail::rlbox_remove_wrapper_t<T_Ret>, + T_Sbx>, + detail::convert_to_sandbox_equivalent_t< + detail::rlbox_remove_wrapper_t<T_Args>, + T_Sbx>...>(unique_key, reinterpret_cast<void*>(callback_interceptor)); + + auto tainted_func_ptr = reinterpret_cast< + detail::rlbox_tainted_opaque_to_tainted_t<T_Ret, T_Sbx> (*)( + T_RL, detail::rlbox_tainted_opaque_to_tainted_t<T_Args, T_Sbx>...)>( + reinterpret_cast<void*>(func_ptr)); + + auto ret = sandbox_callback<T_Cb_no_wrap<T_Ret, T_Args...>*, T_Sbx>( + this, + tainted_func_ptr, + callback_interceptor, + callback_trampoline, + unique_key); + return ret; + } + } + + // this is an internal function invoked from macros, so it has be public + template<typename T> + inline tainted<T*, T_Sbx> INTERNAL_get_sandbox_function_name( + const char* func_name) + { + return INTERNAL_get_sandbox_function_ptr<T>( + internal_lookup_symbol(func_name)); + } + + // this is an internal function invoked from macros, so it has be public + template<typename T> + inline tainted<T*, T_Sbx> INTERNAL_get_sandbox_function_ptr(void* func_ptr) + { + return tainted<T*, T_Sbx>::internal_factory(reinterpret_cast<T*>(func_ptr)); + } + + /** + * @brief Create a "fake" pointer referring to a location in the application + * memory + * + * @param ptr The pointer to refer to + * + * @return The app_pointer object that refers to this location. + */ + template<typename T> + app_pointer<T*, T_Sbx> get_app_pointer(T* ptr) + { + auto max_ptr = (typename T_Sbx::T_PointerType)(get_total_memory() - 1); + auto idx = app_ptr_map.get_app_pointer_idx((void*)ptr, max_ptr); + auto idx_as_ptr = this->template impl_get_unsandboxed_pointer<T>(idx); + // Right now we simply assume that any integer can be converted to a valid + // pointer in the sandbox This may not be true for some sandboxing mechanism + // plugins in the future In this case, we will have to come up with + // something more clever to construct indexes that look like valid pointers + // Add a check for now to make sure things work fine + detail::dynamic_check(is_pointer_in_sandbox_memory(idx_as_ptr), + "App pointers are not currently supported for this " + "rlbox sandbox plugin. Please file a bug."); + auto ret = app_pointer<T*, T_Sbx>( + &app_ptr_map, idx, reinterpret_cast<T*>(idx_as_ptr)); + return ret; + } + + /** + * @brief The mirror of get_app_pointer. Take a tainted pointer which is + * actually an app_pointer, and get the application location being pointed to + * + * @param tainted_ptr The tainted pointer that is actually an app_pointer + * + * @return The original location being referred to by the app_ptr + */ + template<typename T> + T* lookup_app_ptr(tainted<T*, T_Sbx> tainted_ptr) + { + auto idx = tainted_ptr.get_raw_sandbox_value(*this); + void* ret = app_ptr_map.lookup_index(idx); + return reinterpret_cast<T*>(ret); + } + +#ifdef RLBOX_MEASURE_TRANSITION_TIMES + inline std::vector<rlbox_transition_timing>& + process_and_get_transition_times() + { + return transition_times; + } + inline int64_t get_total_ns_time_in_sandbox_and_transitions() + { + int64_t ret = 0; + for (auto& transition_time : transition_times) { + if (transition_time.invoke == rlbox_transition::INVOKE) { + ret += transition_time.time; + } else { + ret -= transition_time.time; + } + } + return ret; + } + inline void clear_transition_times() { transition_times.clear(); } +#endif +}; + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments" +#elif defined(__GNUC__) || defined(__GNUG__) +// Can't turn off the variadic macro warning emitted from -pedantic so use a +// hack to stop GCC emitting warnings for the reminder of this file +# pragma GCC system_header +#elif defined(_MSC_VER) +// Doesn't seem to emit the warning +#else +// Don't know the compiler... just let it go through +#endif + +/** + * @def invoke_sandbox_function + * @brief Call sandbox function. + * + * @param func_name The sandboxed library function to call. + * @param ... Arguments to function should be simple or tainted values. + * @return Tainted value or void. + */ +#ifdef RLBOX_USE_STATIC_CALLS + +# define sandbox_lookup_symbol_helper(prefix, func_name) prefix(func_name) + +# define invoke_sandbox_function(func_name, ...) \ + template INTERNAL_invoke_with_func_ptr<decltype(func_name)>( \ + #func_name, \ + sandbox_lookup_symbol_helper(RLBOX_USE_STATIC_CALLS(), func_name), \ + ##__VA_ARGS__) + +# define get_sandbox_function_address(func_name) \ + template INTERNAL_get_sandbox_function_ptr<decltype(func_name)>( \ + sandbox_lookup_symbol_helper(RLBOX_USE_STATIC_CALLS(), func_name)) + +#else + +# define invoke_sandbox_function(func_name, ...) \ + template INTERNAL_invoke_with_func_name<decltype(func_name)>( \ + #func_name, ##__VA_ARGS__) + +# define get_sandbox_function_address(func_name) \ + template INTERNAL_get_sandbox_function_name<decltype(func_name)>(#func_name) + +#endif + +#define sandbox_invoke(sandbox, func_name, ...) \ + (sandbox).invoke_sandbox_function(func_name, ##__VA_ARGS__) + +#define sandbox_function_address(sandbox, func_name) \ + (sandbox).get_sandbox_function_address(func_name) + +#if defined(__clang__) +# pragma clang diagnostic pop +#else +#endif + +} diff --git a/third_party/rlbox/include/rlbox_stdlib.hpp b/third_party/rlbox/include/rlbox_stdlib.hpp new file mode 100644 index 0000000000..5a6502d0ad --- /dev/null +++ b/third_party/rlbox/include/rlbox_stdlib.hpp @@ -0,0 +1,344 @@ +#pragma once +// IWYU pragma: private, include "rlbox.hpp" +// IWYU pragma: friend "rlbox_.*\.hpp" + +#include <cstring> +#include <type_traits> + +#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_Lhs, \ + typename T_C_Rhs, \ + typename T_C_Sbx, \ + template<typename, typename> \ + typename T_C_Wrap> \ + friend inline tainted<T_C_Lhs, T_C_Sbx> sandbox_reinterpret_cast( \ + const T_C_Wrap<T_C_Rhs, T_C_Sbx>& rhs) noexcept; \ + \ + template<typename T_C_Lhs, \ + typename T_C_Rhs, \ + typename T_C_Sbx, \ + template<typename, typename> \ + typename T_C_Wrap> \ + friend inline tainted<T_C_Lhs, T_C_Sbx> sandbox_const_cast( \ + const T_C_Wrap<T_C_Rhs, T_C_Sbx>& rhs) noexcept; \ + \ + template<typename T_C_Lhs, \ + typename T_C_Rhs, \ + typename T_C_Sbx, \ + template<typename, typename> \ + typename T_C_Wrap> \ + friend inline tainted<T_C_Lhs, T_C_Sbx> sandbox_static_cast( \ + const T_C_Wrap<T_C_Rhs, T_C_Sbx>& rhs) noexcept; + +/** + * @brief The equivalent of a reinterpret_cast but operates on sandboxed values. + */ +template<typename T_Lhs, + typename T_Rhs, + typename T_Sbx, + template<typename, typename> + typename T_Wrap> +inline tainted<T_Lhs, T_Sbx> sandbox_reinterpret_cast( + const T_Wrap<T_Rhs, T_Sbx>& rhs) noexcept +{ + static_assert(detail::rlbox_is_wrapper_v<T_Wrap<T_Rhs, T_Sbx>> && + std::is_pointer_v<T_Lhs> && std::is_pointer_v<T_Rhs>, + "sandbox_reinterpret_cast on incompatible types"); + + tainted<T_Rhs, T_Sbx> taintedVal = rhs; + auto raw = reinterpret_cast<T_Lhs>(taintedVal.INTERNAL_unverified_safe()); + auto ret = tainted<T_Lhs, T_Sbx>::internal_factory(raw); + return ret; +} + +/** + * @brief The equivalent of a const_cast but operates on sandboxed values. + */ +template<typename T_Lhs, + typename T_Rhs, + typename T_Sbx, + template<typename, typename> + typename T_Wrap> +inline tainted<T_Lhs, T_Sbx> sandbox_const_cast( + const T_Wrap<T_Rhs, T_Sbx>& rhs) noexcept +{ + static_assert(detail::rlbox_is_wrapper_v<T_Wrap<T_Rhs, T_Sbx>>, + "sandbox_const_cast on incompatible types"); + + tainted<T_Rhs, T_Sbx> taintedVal = rhs; + auto raw = const_cast<T_Lhs>(taintedVal.INTERNAL_unverified_safe()); + auto ret = tainted<T_Lhs, T_Sbx>::internal_factory(raw); + return ret; +} + +/** + * @brief The equivalent of a static_cast but operates on sandboxed values. + */ +template<typename T_Lhs, + typename T_Rhs, + typename T_Sbx, + template<typename, typename> + typename T_Wrap> +inline tainted<T_Lhs, T_Sbx> sandbox_static_cast( + const T_Wrap<T_Rhs, T_Sbx>& rhs) noexcept +{ + static_assert(detail::rlbox_is_wrapper_v<T_Wrap<T_Rhs, T_Sbx>>, + "sandbox_static_cast on incompatible types"); + + tainted<T_Rhs, T_Sbx> taintedVal = rhs; + auto raw = static_cast<T_Lhs>(taintedVal.INTERNAL_unverified_safe()); + auto ret = tainted<T_Lhs, T_Sbx>::internal_factory(raw); + return ret; +} + +/** + * @brief Fill sandbox memory with a constant byte. + */ +template<typename T_Sbx, + typename T_Rhs, + typename T_Val, + typename T_Num, + template<typename, typename> + typename T_Wrap> +inline T_Wrap<T_Rhs*, T_Sbx> memset(rlbox_sandbox<T_Sbx>& sandbox, + T_Wrap<T_Rhs*, T_Sbx> ptr, + T_Val value, + T_Num num) +{ + + static_assert(detail::rlbox_is_tainted_or_vol_v<T_Wrap<T_Rhs, T_Sbx>>, + "memset called on non wrapped type"); + + static_assert(!std::is_const_v<T_Rhs>, "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<T_Rhs*, T_Sbx> ptr_tainted = ptr; + void* dest_start = ptr_tainted.INTERNAL_unverified_safe(); + detail::check_range_doesnt_cross_app_sbx_boundary<T_Sbx>(dest_start, num_val); + + std::memset(dest_start, detail::unwrap_value(value), num_val); + return ptr; +} + +/** + * @brief types that do not need to be adjusted to fix ABI differences. + * Currently these are only char, wchar, float, and double + */ + +template<typename T> +static constexpr bool can_type_be_memcopied = + std::is_same_v<char, std::remove_cv_t<T>> || std::is_same_v<wchar_t, std::remove_cv_t<T>> || + std::is_same_v<float, std::remove_cv_t<T>> || std::is_same_v<double, std::remove_cv_t<T>> || + std::is_same_v<char16_t, std::remove_cv_t<T>> || std::is_same_v<short, std::remove_cv_t<T>>; + +/** + * @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_Sbx, + typename T_Rhs, + typename T_Lhs, + typename T_Num, + template<typename, typename> + typename T_Wrap> +inline T_Wrap<T_Rhs*, T_Sbx> memcpy(rlbox_sandbox<T_Sbx>& sandbox, + T_Wrap<T_Rhs*, T_Sbx> dest, + T_Lhs src, + T_Num num) +{ + + static_assert(detail::rlbox_is_tainted_or_vol_v<T_Wrap<T_Rhs, T_Sbx>>, + "memcpy called on non wrapped type"); + + static_assert(!std::is_const_v<T_Rhs>, "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<T_Rhs*, T_Sbx> dest_tainted = dest; + void* dest_start = dest_tainted.INTERNAL_unverified_safe(); + detail::check_range_doesnt_cross_app_sbx_boundary<T_Sbx>(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<T_Sbx>(src_start, num_val); + + std::memcpy(dest_start, src_start, num_val); + + return dest; +} + +/** + * @brief Compare data in sandbox memory area. + */ +template<typename T_Sbx, typename T_Rhs, typename T_Lhs, typename T_Num> +inline tainted_int_hint memcmp(rlbox_sandbox<T_Sbx>& sandbox, + T_Rhs&& dest, + T_Lhs&& src, + T_Num&& num) +{ + static_assert( + detail::rlbox_is_tainted_or_vol_v<detail::remove_cv_ref_t<T_Rhs>> || + detail::rlbox_is_tainted_or_vol_v<detail::remove_cv_ref_t<T_Lhs>>, + "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<T_Sbx>(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<T_Sbx>(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<typename T_Sbx, typename T> +tainted<T*, T_Sbx> copy_memory_or_grant_access(rlbox_sandbox<T_Sbx>& sandbox, + T* src, + size_t num, + bool free_source_on_copy, + bool& copied) +{ + copied = false; + + // This function is meant for byte buffers only + static_assert(can_type_be_memcopied<std::remove_pointer_t<T>>, + "copy_memory_or_grant_access not supported on this type as " + "there may be ABI differences"); + + // 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<T_Sbx>) { + detail::check_range_doesnt_cross_app_sbx_boundary<T_Sbx>(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<uint32_t>::max(), + "Granting access too large a region"); + using T_nocv = std::remove_cv_t<T>; + tainted<T_nocv*, T_Sbx> copy = + sandbox.template malloc_in_sandbox<T_nocv>(static_cast<uint32_t>(num)); + if (!copy) { + return nullptr; + } + + rlbox::memcpy(sandbox, copy, src, source_size); + if (free_source_on_copy) { + free(const_cast<void*>(reinterpret_cast<const void*>(src))); + } + + copied = true; + return sandbox_const_cast<T*>(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_Sbx, + typename T, + template<typename, typename> + typename T_Wrap> +T* copy_memory_or_deny_access(rlbox_sandbox<T_Sbx>& sandbox, + T_Wrap<T*, T_Sbx> 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(can_type_be_memcopied<std::remove_pointer_t<T>>, + "copy_memory_or_deny_access not supported on this type as " + "there may be ABI differences"); + + // 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<T_Sbx>) { + detail::check_range_doesnt_cross_app_sbx_boundary<T_Sbx>( + 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<T*>(malloc(source_size)); + if (!copy) { + return nullptr; + } + + tainted<T*, T_Sbx> src_tainted = src; + char* src_raw = src_tainted.copy_and_verify_buffer_address( + [](uintptr_t val) { return reinterpret_cast<char*>(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 <type_traits> +// 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<bool _Val> +using _BoolConstant = std::integral_constant<bool, _Val>; + +template<class _Tp, class _Up> +using _IsNotSame = _BoolConstant<!std::is_same<_Tp, _Up>::value>; + +#define INVOKE_RETURN(...) \ + noexcept(noexcept(__VA_ARGS__))->decltype(__VA_ARGS__) { return __VA_ARGS__; } + +template<class _Fp, class... _Args> +inline auto helper__invoke(_Fp&& __f, _Args&&... __args) + INVOKE_RETURN(std::forward<_Fp>(__f)(std::forward<_Args>(__args)...)) + + template<class _Fp, class... _Args> + 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<class _Ret, class _Fp, class... _Args> + struct __invokable_r +{ + template<class _XFp, class... _XArgs> + static auto __try_call(int) + -> decltype(helper__invoke(std::declval<_XFp>(), + std::declval<_XArgs>()...)); + template<class _XFp, class... _XArgs> + 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<std::is_void<_Ret>::value, + std::true_type, + std::is_convertible<_Result, _Ret>>::type, + std::false_type>::type; + static const bool value = type::value; +}; +template<class _Fp, class... _Args> +using __invokable = __invokable_r<void, _Fp, _Args...>; + +template<bool _IsInvokable, + bool _IsCVVoid, + class _Ret, + class _Fp, + class... _Args> +struct __nothrow_invokable_r_imp +{ + static const bool value = false; +}; + +template<class _Ret, class _Fp, class... _Args> +struct __nothrow_invokable_r_imp<true, false, _Ret, _Fp, _Args...> +{ + typedef __nothrow_invokable_r_imp _ThisT; + + template<class _Tp> + static void __test_noexcept(_Tp) noexcept; + + static const bool value = noexcept(_ThisT::__test_noexcept<_Ret>( + helper__invoke(std::declval<_Fp>(), std::declval<_Args>()...))); +}; + +template<class _Ret, class _Fp, class... _Args> +struct __nothrow_invokable_r_imp<true, true, _Ret, _Fp, _Args...> +{ + static const bool value = + noexcept(helper__invoke(std::declval<_Fp>(), std::declval<_Args>()...)); +}; + +template<class _Ret, class _Fp, class... _Args> +using __nothrow_invokable_r = + __nothrow_invokable_r_imp<__invokable_r<_Ret, _Fp, _Args...>::value, + std::is_void<_Ret>::value, + _Ret, + _Fp, + _Args...>; + +template<class _Fp, class... _Args> +using __nothrow_invokable = + __nothrow_invokable_r_imp<__invokable<_Fp, _Args...>::value, + true, + void, + _Fp, + _Args...>; + +template<class _Fp, class... _Args> +struct helper__invoke_of + : public std::enable_if<__invokable<_Fp, _Args...>::value, + typename __invokable_r<void, _Fp, _Args...>::_Result> +{}; + +// invoke_result + +template<class _Fn, class... _Args> +struct invoke_result : helper__invoke_of<_Fn, _Args...> +{}; + +template<class _Fn, class... _Args> +using invoke_result_t = typename invoke_result<_Fn, _Args...>::type; + +// is_invocable + +template<class _Fn, class... _Args> +struct is_invocable + : std::integral_constant<bool, __invokable<_Fn, _Args...>::value> +{}; + +template<class _Ret, class _Fn, class... _Args> +struct is_invocable_r + : std::integral_constant<bool, __invokable_r<_Ret, _Fn, _Args...>::value> +{}; + +template<class _Fn, class... _Args> +inline constexpr bool is_invocable_v = is_invocable<_Fn, _Args...>::value; + +template<class _Ret, class _Fn, class... _Args> +inline constexpr bool is_invocable_r_v = + is_invocable_r<_Ret, _Fn, _Args...>::value; + +// is_nothrow_invocable + +template<class _Fn, class... _Args> +struct is_nothrow_invocable + : std::integral_constant<bool, __nothrow_invokable<_Fn, _Args...>::value> +{}; + +template<class _Ret, class _Fn, class... _Args> +struct is_nothrow_invocable_r + : std::integral_constant<bool, + __nothrow_invokable_r<_Ret, _Fn, _Args...>::value> +{}; + +template<class _Fn, class... _Args> +inline constexpr bool is_nothrow_invocable_v = + is_nothrow_invocable<_Fn, _Args...>::value; + +template<class _Ret, class _Fn, class... _Args> +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..bdd60275f0 --- /dev/null +++ b/third_party/rlbox/include/rlbox_struct_support.hpp @@ -0,0 +1,354 @@ +#pragma once +// IWYU pragma: private, include "rlbox.hpp" +// IWYU pragma: friend "rlbox_.*\.hpp" + +#include <cstring> +#include <functional> +#include <type_traits> + +#include "rlbox_conversion.hpp" +#include "rlbox_helpers.hpp" +#include "rlbox_types.hpp" +#include "rlbox_wrapper_traits.hpp" + +namespace rlbox::detail { + +template<typename T, typename T_Sbx, typename T_Enable = void> +struct convert_to_sandbox_equivalent_helper; + +template<typename T, typename T_Sbx> +struct convert_to_sandbox_equivalent_helper< + T, + T_Sbx, + std::enable_if_t<!std::is_class_v<T>>> +{ + using type = typename rlbox_sandbox< + T_Sbx>::template convert_to_sandbox_equivalent_nonclass_t<T>; +}; + +template<typename T, typename T_Sbx> +using convert_to_sandbox_equivalent_t = + typename convert_to_sandbox_equivalent_helper<T, T_Sbx>::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<fieldType, T_Sbx> fieldName; + +#define helper_no_op() + +#define sandbox_equivalent_specialization(T, libId) \ + template<typename T_Sbx> \ + 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<typename T_Template, typename T_Sbx> \ + struct convert_to_sandbox_equivalent_helper< \ + T_Template, \ + T_Sbx, \ + std::enable_if_t<std::is_same_v<T_Template, T>>> \ + { \ + using type = Sbx_##libId##_##T<T_Sbx>; \ + }; \ + } + +#define helper_create_tainted_field( \ + fieldType, fieldName, isFrozen, MaybeConst) \ + MaybeConst tainted<fieldType, T_Sbx> fieldName; + +#define helper_create_tainted_vol_field( \ + fieldType, fieldName, isFrozen, MaybeConst) \ + MaybeConst tainted_volatile<fieldType, T_Sbx> fieldName; + +#define helper_convert_type(fieldType, fieldName, isFrozen) \ + ::rlbox::detail::convert_type<T_Sbx, Direction, Context>( \ + 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<typename T_Sbx> \ + class tainted_volatile<MaybeConst T, T_Sbx> \ + { \ + KEEP_CLASSES_FRIENDLY \ + KEEP_CAST_FRIENDLY \ + \ + private: \ + inline MaybeConst Sbx_##libId##_##T<T_Sbx>& \ + get_sandbox_value_ref() noexcept \ + { \ + return *reinterpret_cast<MaybeConst Sbx_##libId##_##T<T_Sbx>*>(this); \ + } \ + \ + inline const Sbx_##libId##_##T<T_Sbx>& get_sandbox_value_ref() \ + const noexcept \ + { \ + return *reinterpret_cast<const Sbx_##libId##_##T<T_Sbx>*>(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<T_Sbx>* 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<T_Sbx> get_raw_sandbox_value() const noexcept \ + { \ + auto ret_ptr = reinterpret_cast<const Sbx_##libId##_##T<T_Sbx>*>(this); \ + return *ret_ptr; \ + } \ + \ + tainted_volatile() = default; \ + tainted_volatile(const tainted_volatile<MaybeConst T, T_Sbx>& p) = \ + default; \ + \ + public: \ + sandbox_fields_reflection_##libId##_class_##T( \ + helper_create_tainted_vol_field, \ + helper_no_op, \ + MaybeConst) \ + \ + inline tainted<MaybeConst T*, T_Sbx> \ + operator&() const noexcept \ + { \ + auto ref_cast = \ + reinterpret_cast<MaybeConst T*>(&get_sandbox_value_ref()); \ + auto ret = tainted<MaybeConst T*, T_Sbx>::internal_factory(ref_cast); \ + return ret; \ + } \ + \ + inline auto UNSAFE_unverified() const { return get_raw_value(); } \ + inline auto UNSAFE_sandboxed(rlbox_sandbox<T_Sbx>& sandbox) const \ + { \ + return get_raw_sandbox_value(sandbox); \ + } \ + \ + template<size_t N> \ + inline auto unverified_safe_because(const char (&reason)[N]) const \ + { \ + RLBOX_UNUSED(reason); \ + return UNSAFE_unverified(); \ + } \ + \ + T copy_and_verify(std::function<T(tainted<T, T_Sbx>)> verifier) \ + { \ + tainted<T, T_Sbx> 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<MaybeConst T, T_Sbx>& operator=( \ + const tainted<T, T_Sbx>& rhs); \ + }; \ + \ + template<typename T_Sbx> \ + class tainted<MaybeConst T, T_Sbx> \ + { \ + KEEP_CLASSES_FRIENDLY \ + KEEP_CAST_FRIENDLY \ + \ + private: \ + inline MaybeConst T& get_raw_value_ref() noexcept \ + { \ + return *reinterpret_cast<MaybeConst T*>(this); \ + } \ + \ + inline const T& get_raw_value_ref() const noexcept \ + { \ + return *reinterpret_cast<const T*>(this); \ + } \ + \ + inline T get_raw_value() const noexcept \ + { \ + auto ret_ptr = reinterpret_cast<const T*>(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<T_Sbx> get_raw_sandbox_value( \ + rlbox_sandbox<T_Sbx>& sandbox) const noexcept \ + { \ + Sbx_##libId##_##T<T_Sbx> 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<T_Sbx>* 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<MaybeConst T, T_Sbx>& p) = default; \ + \ + tainted(const tainted_volatile<T, T_Sbx>& 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<T_Sbx>* sandbox_ptr = nullptr; \ + sandbox_fields_reflection_##libId##_class_##T(helper_convert_type, \ + helper_no_op) \ + } \ + \ + inline tainted_opaque<MaybeConst T, T_Sbx> to_opaque() \ + { \ + return *reinterpret_cast<tainted_opaque<MaybeConst T, T_Sbx>*>(this); \ + } \ + \ + inline auto UNSAFE_unverified() const { return get_raw_value(); } \ + inline auto UNSAFE_sandboxed(rlbox_sandbox<T_Sbx>& sandbox) const \ + { \ + return get_raw_sandbox_value(sandbox); \ + } \ + \ + template<size_t N> \ + inline auto unverified_safe_because(const char (&reason)[N]) const \ + { \ + RLBOX_UNUSED(reason); \ + return UNSAFE_unverified(); \ + } \ + \ + T copy_and_verify(std::function<T(tainted<T, T_Sbx>)> verifier) \ + { \ + return verifier(*this); \ + } \ + }; \ + \ + /* Had to delay the definition due, to mutually dependence between \ + tainted and tainted_volatile for structs */ \ + template<typename T_Sbx> \ + inline tainted_volatile<MaybeConst T, T_Sbx>& \ + tainted_volatile<MaybeConst T, T_Sbx>::operator=( \ + const tainted<T, T_Sbx>& 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<T_Sbx>* 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<typename T_Sbx, \ + detail::adjust_type_direction Direction, \ + adjust_type_context Context, \ + typename T_From> \ + class convert_type_class<T_Sbx, Direction, Context, T, T_From> \ + { \ + public: \ + static inline void run(T& lhs, \ + const T_From& rhs, \ + const void* example_unsandboxed_ptr, \ + rlbox_sandbox<T_Sbx>* sandbox_ptr) \ + { \ + sandbox_fields_reflection_##libId##_class_##T(helper_convert_type, \ + helper_no_op) \ + } \ + }; \ + \ + template<typename T_Sbx, \ + detail::adjust_type_direction Direction, \ + adjust_type_context Context, \ + typename T_From> \ + class convert_type_class<T_Sbx, \ + Direction, \ + Context, \ + Sbx_##libId##_##T<T_Sbx>, \ + T_From> \ + { \ + public: \ + static inline void run(Sbx_##libId##_##T<T_Sbx>& lhs, \ + const T_From& rhs, \ + const void* example_unsandboxed_ptr, \ + rlbox_sandbox<T_Sbx>* 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<detail::markerStruct>, \ + "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 <array> +#include <type_traits> + +namespace rlbox::detail { + +#define RLBOX_ENABLE_IF(...) std::enable_if_t<__VA_ARGS__>* = nullptr + +template<typename T> +constexpr bool true_v = true; + +template<typename T> +constexpr bool is_fundamental_or_enum_v = + std::is_fundamental_v<T> || std::is_enum_v<T>; + +template<typename T> +constexpr bool is_basic_type_v = + std::is_fundamental_v<T> || std::is_enum_v<T> || std::is_pointer_v<T>; + +template<typename T> +using valid_return_t = + std::conditional_t<std::is_function_v<T>, void*, std::decay_t<T>>; + +template<typename T> +using valid_param_t = std::conditional_t<std::is_void_v<T>, void*, T>; + +namespace func_first_arg_detail { + template<typename Ret, typename Arg, typename... Rest> + Arg func_first_arg_t_helper(Ret (*)(Arg, Rest...)); + + template<typename Ret, typename F, typename Arg, typename... Rest> + Arg func_first_arg_t_helper(Ret (F::*)(Arg, Rest...)); + + template<typename Ret, typename F, typename Arg, typename... Rest> + Arg func_first_arg_t_helper(Ret (F::*)(Arg, Rest...) const); + + template<typename F> + decltype(func_first_arg_t_helper(&F::operator())) first_argument_helper(F); +} + +template<typename T> +using func_first_arg_t = + decltype(func_first_arg_detail::first_argument_helper(std::declval<T>())); + +namespace func_arg_nums_v_detail { + template<typename T_Ret, typename... T_Args> + constexpr size_t helper_two(T_Ret (*)(T_Args...)) + { + return sizeof...(T_Args); + } + template<typename T_Func> + constexpr size_t helper() + { + constexpr T_Func* ptr = nullptr; + return helper_two(ptr); + } +} + +template<typename T_Func> +constexpr size_t func_arg_nums_v = func_arg_nums_v_detail::helper<T_Func>(); + +template<typename T> +using valid_array_el_t = + std::conditional_t<std::is_void_v<T> || std::is_function_v<T>, int, T>; + +template<typename T> +constexpr bool is_func_ptr_v = (std::is_pointer_v<T> && + std::is_function_v<std::remove_pointer_t<T>>) || + std::is_member_function_pointer_v<T>; + +template<typename T> +constexpr bool is_func_or_func_ptr = std::is_function_v<T> || is_func_ptr_v<T>; + +template<typename T> +constexpr bool is_one_level_ptr_v = + std::is_pointer_v<T> && !std::is_pointer_v<std::remove_pointer_t<T>>; + +template<typename T_This, typename T_Target> +using add_const_if_this_const_t = + std::conditional_t<std::is_const_v<std::remove_pointer_t<T_This>>, + std::add_const_t<T_Target>, + T_Target>; + +template<typename T> +using remove_const_from_pointer = std::conditional_t< + std::is_pointer_v<T>, + std::add_pointer_t<std::remove_const_t<std::remove_pointer_t<T>>>, + T>; + +template<typename T> +using add_const_from_pointer = std::conditional_t< + std::is_pointer_v<T>, + std::remove_pointer_t<std::add_const_t<std::remove_pointer_t<T>>>, + T>; + +template<typename T> +using remove_cv_ref_t = std::remove_cv_t<std::remove_reference_t<T>>; + +template<typename T> +using c_to_std_array_t = + std::conditional_t<std::is_array_v<T>, + std::array<std::remove_extent_t<T>, std::extent_v<T>>, + T>; + +namespace std_array_to_c_arr_detail { + template<typename T> + struct W + { + using type = T; + }; + + template<typename T, size_t N> + W<T[N]> std_array_to_c_arr_helper(std::array<T, N>); + + template<typename T> + W<T> std_array_to_c_arr_helper(T&&); +} + +template<typename T> +using std_array_to_c_arr_t = + typename decltype(std_array_to_c_arr_detail::std_array_to_c_arr_helper( + std::declval<T>()))::type; + +template<typename T> +using dereference_result_t = + std::conditional_t<std::is_pointer_v<T>, + std::remove_pointer_t<T>, + std::remove_extent_t<std_array_to_c_arr_t<T>> // is_array + >; + +template<typename T> +using value_type_t = + std::conditional_t<std::is_array_v<T>, c_to_std_array_t<T>, T>; + +template<typename T> +using function_ptr_t = + std::conditional_t<std::is_pointer_v<T> && + std::is_function_v<std::remove_pointer_t<T>>, + T, + int (*)(int)>; + +namespace is_c_or_std_array_detail { + template<typename T, typename T_Enable = void> + struct is_c_or_std_array_helper; + + template<typename T> + struct is_c_or_std_array_helper<T, std::enable_if_t<std::is_array_v<T>>> + : std::true_type + {}; + + template<typename T, size_t N> + std::true_type is_std_array_helper(std::array<T, N>*); + + template<typename T> + std::false_type is_std_array_helper(T*); + + template<typename T> + constexpr bool is_std_array_v = + decltype(is_std_array_helper(std::declval<std::add_pointer_t<T>>()))::value; + + template<typename T> + struct is_c_or_std_array_helper<T, std::enable_if_t<is_std_array_v<T>>> + : std::true_type + {}; + + template<typename T> + struct is_c_or_std_array_helper< + T, + std::enable_if_t<!std::is_array_v<T> && !is_std_array_v<T>>> + : std::false_type + {}; +} + +template<typename T> +constexpr bool is_std_array_v = is_c_or_std_array_detail::is_std_array_v<T>; + +template<typename T> +constexpr bool is_c_or_std_array_v = + is_c_or_std_array_detail::is_c_or_std_array_helper<T>::value; + +namespace std_array_el_detail { + template<typename T> + struct W + { + using type = T; + }; + + template<typename T, size_t N> + W<T> is_std_array_helper(std::array<T, N>*); + + template<typename T> + W<void> is_std_array_helper(T*); + + template<typename T> + using std_array_el_t = decltype(std_array_el_detail::is_std_array_helper( + std::declval<std::add_pointer_t<T>>)); +} + +template<typename T> +using std_array_el_t = typename std_array_el_detail::std_array_el_t<T>::type; + +namespace all_extents_same_detail { + + template<typename T1, typename T2, typename T_Enable = void> + struct all_extents_same_helper; + + template<typename T1, typename T2> + struct all_extents_same_helper< + T1, + T2, + std::enable_if_t<std::rank_v<T1> != std::rank_v<T2>>> : std::false_type + {}; + + template<typename T1, typename T2> + struct all_extents_same_helper< + T1, + T2, + std::enable_if_t<std::rank_v<T1> == std::rank_v<T2> && + !std::is_array_v<T1> && !std::is_array_v<T2>>> + : std::true_type + {}; + + template<typename T1, typename T2> + struct all_extents_same_helper< + T1, + T2, + std::enable_if_t<std::rank_v<T1> == std::rank_v<T2> && + std::is_array_v<T1> && std::is_array_v<T2> && + std::extent_v<T1> != std::extent_v<T2>>> : std::false_type + {}; + + template<typename T1, typename T2> + struct all_extents_same_helper< + T1, + T2, + std::enable_if_t<std::rank_v<T1> == std::rank_v<T2> && + std::is_array_v<T1> && std::is_array_v<T2> && + std::extent_v<T1> == std::extent_v<T2>>> + { + static constexpr bool value = + all_extents_same_helper<std::remove_extent_t<T1>, + std::remove_extent_t<T2>>::value; + }; +} + +template<typename T1, typename T2> +constexpr bool all_extents_same = + all_extents_same_detail::all_extents_same_helper<T1, T2>::value; + +// remove all pointers/extent types +namespace remove_all_pointers_detail { + template<typename T> + struct remove_all_pointers + { + typedef T type; + }; + + template<typename T> + struct remove_all_pointers<T*> + { + typedef typename remove_all_pointers<T>::type type; + }; +} + +template<typename T> +using remove_all_pointers_t = + typename remove_all_pointers_detail::remove_all_pointers<T>::type; + +// remove all pointers/extent types +namespace base_type_detail { + template<typename T> + struct base_type + { + typedef T type; + }; + + template<typename T> + struct base_type<T*> + { + typedef typename base_type<T>::type type; + }; + + template<typename T> + struct base_type<T[]> + { + typedef typename base_type<T>::type type; + }; + + template<typename T, std::size_t N> + struct base_type<T[N]> + { + typedef typename base_type<T>::type type; + }; +} + +template<typename T> +using base_type_t = typename base_type_detail::base_type<T>::type; + +// convert types +namespace convert_detail { + template<typename T, + typename T_ShortType, + typename T_IntType, + typename T_LongType, + typename T_LongLongType, + typename T_PointerType, + typename T_Enable = void> + struct convert_base_types_t_helper; + + template<typename T, + typename T_ShortType, + typename T_IntType, + typename T_LongType, + typename T_LongLongType, + typename T_PointerType> + 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<short, T> && !std::is_const_v<T>>> + { + using type = T_ShortType; + }; + + template<typename T, + typename T_ShortType, + typename T_IntType, + typename T_LongType, + typename T_LongLongType, + typename T_PointerType> + 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<int, T> && !std::is_const_v<T>>> + { + using type = T_IntType; + }; + + template<typename T, + typename T_ShortType, + typename T_IntType, + typename T_LongType, + typename T_LongLongType, + typename T_PointerType> + 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<long, T> && !std::is_const_v<T>>> + { + using type = T_LongType; + }; + + template<typename T, + typename T_ShortType, + typename T_IntType, + typename T_LongType, + typename T_LongLongType, + typename T_PointerType> + 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<long long, T> && !std::is_const_v<T>>> + { + using type = T_LongLongType; + }; + + template<typename T, + typename T_ShortType, + typename T_IntType, + typename T_LongType, + typename T_LongLongType, + typename T_PointerType> + struct convert_base_types_t_helper< + T, + T_ShortType, + T_IntType, + T_LongType, + T_LongLongType, + T_PointerType, + std::enable_if_t<std::is_pointer_v<T> && !std::is_const_v<T>>> + { + using type = T_PointerType; + }; + + template<typename T, + typename T_ShortType, + typename T_IntType, + typename T_LongType, + typename T_LongLongType, + typename T_PointerType> + struct convert_base_types_t_helper< + T, + T_ShortType, + T_IntType, + T_LongType, + T_LongLongType, + T_PointerType, + std::enable_if_t<std::is_unsigned_v<T> && !std::is_same_v<T, bool> && + !std::is_same_v<T, char> && !std::is_const_v<T> && + !std::is_enum_v<T>>> + { + using type = std::make_unsigned_t< + typename convert_base_types_t_helper<std::make_signed_t<T>, + T_ShortType, + T_IntType, + T_LongType, + T_LongLongType, + T_PointerType>::type>; + }; + + template<typename T, + typename T_ShortType, + typename T_IntType, + typename T_LongType, + typename T_LongLongType, + typename T_PointerType> + 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<bool, T> || std::is_same_v<void, T> || + std::is_same_v<char, T> || std::is_same_v<signed char, T> || + std::is_floating_point_v<T> || std::is_enum_v<T>)&&!std::is_const_v<T>>> + { + using type = T; + }; + + template<typename T, + typename T_ShortType, + typename T_IntType, + typename T_LongType, + typename T_LongLongType, + typename T_PointerType> + struct convert_base_types_t_helper< + T, + T_ShortType, + T_IntType, + T_LongType, + T_LongLongType, + T_PointerType, + std::enable_if_t<std::is_array_v<T> && !std::is_const_v<T>>> + { + using type = typename convert_base_types_t_helper< + std::remove_extent_t<T>, + T_ShortType, + T_IntType, + T_LongType, + T_LongLongType, + T_PointerType>::type[std::extent_v<T>]; + }; + + template<typename T, + typename T_ShortType, + typename T_IntType, + typename T_LongType, + typename T_LongLongType, + typename T_PointerType> + 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<T>>> + { + using type = std::add_const_t< + typename convert_base_types_t_helper<std::remove_const_t<T>, + T_ShortType, + T_IntType, + T_LongType, + T_LongLongType, + T_PointerType>::type>; + }; +} + +template<typename T, + typename T_ShortType, + typename T_IntType, + typename T_LongType, + typename T_LongLongType, + typename T_PointerType> +using convert_base_types_t = + typename convert_detail::convert_base_types_t_helper<T, + T_ShortType, + T_IntType, + T_LongType, + T_LongLongType, + T_PointerType>::type; + +namespace unsigned_int_of_size_t_detail { + template<typename T, typename T_Enable = void> + struct unsigned_int_of_size_t_helper; + + template<typename T> + struct unsigned_int_of_size_t_helper<T, std::enable_if_t<sizeof(T) == 1>> + { + using type = uint8_t; + }; + + template<typename T> + struct unsigned_int_of_size_t_helper<T, std::enable_if_t<sizeof(T) == 2>> + { + using type = uint16_t; + }; + + template<typename T> + struct unsigned_int_of_size_t_helper<T, std::enable_if_t<sizeof(T) == 4>> + { + using type = uint32_t; + }; + + template<typename T> + struct unsigned_int_of_size_t_helper<T, std::enable_if_t<sizeof(T) == 8>> + { + using type = uint64_t; + }; +} + +template<typename T> +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<typename T, typename T_Sbx> +class tainted_opaque +{ +private: + T data{ 0 }; + +public: + template<typename T2 = T> + void set_zero() + { + data = 0; + } +}; + +template<typename T, typename T_Sbx> +class tainted; + +template<typename T, typename T_Sbx> +class tainted_volatile; + +class tainted_boolean_hint; + +class tainted_int_hint; + +template<typename T_Sbx> +class rlbox_sandbox; + +template<typename T, typename T_Sbx> +class sandbox_callback; + +template<typename T, typename T_Sbx> +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<rlbox_##SBXNAME##_sandbox_type>; \ + template<typename T> \ + using sandbox_callback_##SBXNAME = \ + rlbox::sandbox_callback<T, rlbox_##SBXNAME##_sandbox_type>; \ + template<typename T> \ + using tainted_##SBXNAME = rlbox::tainted<T, rlbox_##SBXNAME##_sandbox_type>; \ + template<typename T> \ + using tainted_opaque_##SBXNAME = \ + rlbox::tainted_opaque<T, rlbox_##SBXNAME##_sandbox_type>; \ + template<typename T> \ + using tainted_volatile_##SBXNAME = \ + rlbox::tainted_volatile<T, rlbox_##SBXNAME##_sandbox_type>; \ + using rlbox::tainted_boolean_hint; \ + template<typename T> \ + using app_pointer_##SBXNAME = \ + rlbox::app_pointer<T, rlbox_##SBXNAME##_sandbox_type>; + +// 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<rlbox_##SBXNAME##_sandbox_type>; \ + template<typename T> \ + using sandbox_callback_##SBXNAME = \ + rlbox::sandbox_callback<T, rlbox_##SBXNAME##_sandbox_type>; \ + template<typename T> \ + using tainted_##SBXNAME = rlbox::tainted<T, rlbox_##SBXNAME##_sandbox_type>; \ + template<typename T> \ + using tainted_opaque_##SBXNAME = \ + rlbox::tainted_opaque<T, rlbox_##SBXNAME##_sandbox_type>; \ + template<typename T> \ + using tainted_volatile_##SBXNAME = \ + rlbox::tainted_volatile<T, rlbox_##SBXNAME##_sandbox_type>; \ + using rlbox::tainted_boolean_hint; \ + template<typename T> \ + using app_pointer_##SBXNAME = \ + rlbox::app_pointer<T, rlbox_##SBXNAME##_sandbox_type>; 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 <type_traits> + +#include "rlbox_type_traits.hpp" +#include "rlbox_types.hpp" + +namespace rlbox::detail { + +template<typename T_Rhs> +inline auto unwrap_value(T_Rhs&& rhs) noexcept +{ + using T_RhsNoQ = detail::remove_cv_ref_t<T_Rhs>; + if constexpr (detail::rlbox_is_wrapper_v<T_RhsNoQ>) { + return rhs.INTERNAL_unverified_safe(); + } else if constexpr (detail::rlbox_is_tainted_boolean_hint_v<T_RhsNoQ>) { + 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 <type_traits> + +#include "rlbox_types.hpp" + +namespace rlbox::detail { + +#define rlbox_generate_wrapper_check(name) \ + namespace detail_rlbox_is_##name \ + { \ + template<typename T> \ + struct unwrapper : std::false_type \ + {}; \ + \ + template<typename T, typename T_Sbx> \ + struct unwrapper<name<T, T_Sbx>> : std::true_type \ + {}; \ + } \ + \ + template<typename T> \ + constexpr bool rlbox_is_##name##_v = \ + detail_rlbox_is_##name::unwrapper<T>::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<typename T> + struct unwrapper : std::false_type + {}; + + template<> + struct unwrapper<tainted_boolean_hint> : std::true_type + {}; +} + +template<typename T> +constexpr bool rlbox_is_tainted_boolean_hint_v = + detail_rlbox_is_tainted_boolean_hint::unwrapper<T>::value; + +template<typename T> +constexpr bool rlbox_is_tainted_or_vol_v = + rlbox_is_tainted_v<T> || rlbox_is_tainted_volatile_v<T>; + +template<typename T> +constexpr bool rlbox_is_tainted_or_opaque_v = + rlbox_is_tainted_v<T> || rlbox_is_tainted_opaque_v<T>; + +// tainted_hint is NOT considered a wrapper type... This carries no particular +// significant and is just a convention choice +template<typename T> +constexpr bool rlbox_is_wrapper_v = + rlbox_is_tainted_v<T> || rlbox_is_tainted_volatile_v<T> || + rlbox_is_tainted_opaque_v<T> || rlbox_is_sandbox_callback_v<T>; + +namespace detail_rlbox_remove_wrapper { + template<typename T> + struct unwrapper + { + using type = T; + using type_sbx = void; + }; + + template<typename T, typename T_Sbx> + struct unwrapper<tainted<T, T_Sbx>> + { + using type = T; + using type_sbx = T_Sbx; + }; + + template<typename T, typename T_Sbx> + struct unwrapper<tainted_volatile<T, T_Sbx>> + { + using type = T; + using type_sbx = T_Sbx; + }; + + template<typename T, typename T_Sbx> + struct unwrapper<tainted_opaque<T, T_Sbx>> + { + using type = T; + using type_sbx = T_Sbx; + }; + + template<typename T, typename T_Sbx> + struct unwrapper<sandbox_callback<T, T_Sbx>> + { + using type = T; + using type_sbx = T_Sbx; + }; +} + +template<typename T> +using rlbox_remove_wrapper_t = + typename detail_rlbox_remove_wrapper::unwrapper<T>::type; + +template<typename T> +using rlbox_get_wrapper_sandbox_t = + typename detail_rlbox_remove_wrapper::unwrapper<T>::type_sbx; + +template<typename T, typename T_Sbx> +using rlbox_tainted_opaque_to_tainted_t = + std::conditional_t<rlbox_is_tainted_opaque_v<T>, + tainted<rlbox_remove_wrapper_t<T>, 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<typename T, typename = void> + struct is_member_of_rlbox_detail_helper : std::false_type + {}; + + template<typename T> + struct is_member_of_rlbox_detail_helper< + T, + decltype(struct_is_member_of_rlbox_detail(std::declval<T>()))> + : std::true_type + {}; +} + +template<typename T> +void struct_is_member_of_rlbox_detail(T&&); + +template<typename T> +constexpr auto is_member_of_rlbox_detail = + detail_is_member_of_rlbox_detail::is_member_of_rlbox_detail_helper<T>::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<class T, class Enable = void> + struct has_member_using_can_grant_deny_access : std::false_type + {}; + + template<class T> + struct has_member_using_can_grant_deny_access< + T, + std::void_t<typename T::can_grant_deny_access>> : std::true_type + {}; +} + +template<class T> +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<T>::value; + +namespace detail_has_member_using_needs_internal_lookup_symbol { + template<class T, class Enable = void> + struct has_member_using_needs_internal_lookup_symbol : std::false_type + {}; + + template<class T> + struct has_member_using_needs_internal_lookup_symbol< + T, + std::void_t<typename T::needs_internal_lookup_symbol>> : std::true_type + {}; +} + +template<class T> +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<T>::value; + +}
\ No newline at end of file |