From 36d22d82aa202bb199967e9512281e9a53db42c9 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 21:33:14 +0200 Subject: Adding upstream version 115.7.0esr. Signed-off-by: Daniel Baumann --- third_party/rlbox/include/rlbox.hpp | 1349 +++++++++++++++++++++++++++++++++++ 1 file changed, 1349 insertions(+) create mode 100644 third_party/rlbox/include/rlbox.hpp (limited to 'third_party/rlbox/include/rlbox.hpp') diff --git a/third_party/rlbox/include/rlbox.hpp b/third_party/rlbox/include/rlbox.hpp new file mode 100644 index 0000000000..96daaac233 --- /dev/null +++ b/third_party/rlbox/include/rlbox.hpp @@ -0,0 +1,1349 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "rlbox_app_pointer.hpp" +#include "rlbox_conversion.hpp" +#include "rlbox_helpers.hpp" +#include "rlbox_policy_types.hpp" +#include "rlbox_range.hpp" +#include "rlbox_sandbox.hpp" +#include "rlbox_stdlib.hpp" +#include "rlbox_struct_support.hpp" +#include "rlbox_type_traits.hpp" +#include "rlbox_types.hpp" +#include "rlbox_unwrap.hpp" +#include "rlbox_wrapper_traits.hpp" + +namespace rlbox { + +template typename T_Wrap, + typename T, + typename T_Sbx> +class tainted_base_impl +{ + KEEP_CLASSES_FRIENDLY + KEEP_CAST_FRIENDLY + +public: + inline auto& impl() { return *static_cast*>(this); } + inline auto& impl() const + { + return *static_cast*>(this); + } + + /** + * @brief Unwrap a tainted value without verification. This is an unsafe + * operation and should be used with care. + */ + inline auto UNSAFE_unverified() const { return impl().get_raw_value(); } + /** + * @brief Like UNSAFE_unverified, but get the underlying sandbox + * representation. + * + * @param sandbox Reference to sandbox. + * + * For the Wasm-based sandbox, this function additionally validates the + * unwrapped value against the machine model of the sandbox (LP32). + */ + inline auto UNSAFE_sandboxed(rlbox_sandbox& sandbox) const + { + return impl().get_raw_sandbox_value(sandbox); + } + + /** + * @brief Unwrap a tainted value without verification. This function should + * be used when unwrapping is safe. + * + * @param reason An explanation why the unverified unwrapping is safe. + */ + template + inline auto unverified_safe_because(const char (&reason)[N]) const + { + RLBOX_UNUSED(reason); + static_assert(!std::is_pointer_v, + "unverified_safe_because does not support pointers. Use " + "unverified_safe_pointer_because."); + return UNSAFE_unverified(); + } + + template + inline auto unverified_safe_pointer_because(size_t count, + const char (&reason)[N]) const + { + RLBOX_UNUSED(reason); + + static_assert(std::is_pointer_v, "Expected pointer type"); + using T_Pointed = std::remove_pointer_t; + if_constexpr_named(cond1, std::is_pointer_v) + { + rlbox_detail_static_fail_because( + cond1, + "There is no way to use unverified_safe_pointer_because for " + "'pointers to pointers' safely. Use copy_and_verify instead."); + return nullptr; + } + + auto ret = UNSAFE_unverified(); + if (ret != nullptr) { + size_t bytes = sizeof(T) * count; + detail::check_range_doesnt_cross_app_sbx_boundary(ret, bytes); + } + return ret; + } + + inline auto INTERNAL_unverified_safe() const { return UNSAFE_unverified(); } + +#define BinaryOpValAndPtr(opSymbol) \ + template \ + inline constexpr auto operator opSymbol(const T_Rhs& rhs) \ + const->tainted() opSymbol std::declval< \ + detail::rlbox_remove_wrapper_t>()), \ + T_Sbx> \ + { \ + static_assert(detail::is_basic_type_v, \ + "Operator " #opSymbol \ + " only supported for primitive and pointer types"); \ + \ + auto raw_rhs = detail::unwrap_value(rhs); \ + \ + if constexpr (std::is_pointer_v) { \ + static_assert(std::is_integral_v, \ + "Can only operate on numeric types"); \ + auto ptr = impl().get_raw_value(); \ + detail::dynamic_check(ptr != nullptr, \ + "Pointer arithmetic on a null pointer"); \ + /* increment the target by size of the data structure */ \ + auto target = \ + reinterpret_cast(ptr) opSymbol raw_rhs * sizeof(*impl()); \ + auto no_overflow = rlbox_sandbox::is_in_same_sandbox( \ + reinterpret_cast(ptr), \ + reinterpret_cast(target)); \ + detail::dynamic_check( \ + no_overflow, \ + "Pointer arithmetic overflowed a pointer beyond sandbox memory"); \ + \ + return tainted::internal_factory(reinterpret_cast(target)); \ + } else { \ + auto raw = impl().get_raw_value(); \ + auto ret = raw opSymbol raw_rhs; \ + using T_Ret = decltype(ret); \ + return tainted::internal_factory(ret); \ + } \ + } \ + RLBOX_REQUIRE_SEMI_COLON + + BinaryOpValAndPtr(+); + BinaryOpValAndPtr(-); + +#undef BinaryOpValAndPtr + +#define BinaryOp(opSymbol) \ + template \ + inline constexpr auto operator opSymbol(const T_Rhs& rhs) \ + const->tainted() opSymbol std::declval< \ + detail::rlbox_remove_wrapper_t>()), \ + T_Sbx> \ + { \ + static_assert(detail::is_fundamental_or_enum_v, \ + "Operator " #opSymbol \ + " only supported for primitive types"); \ + \ + auto raw = impl().get_raw_value(); \ + auto raw_rhs = detail::unwrap_value(rhs); \ + static_assert(std::is_integral_v \ + || std::is_floating_point_v, \ + "Can only operate on numeric types"); \ + \ + auto ret = raw opSymbol raw_rhs; \ + using T_Ret = decltype(ret); \ + return tainted::internal_factory(ret); \ + } \ + RLBOX_REQUIRE_SEMI_COLON + + BinaryOp(*); + BinaryOp(/); + BinaryOp(%); + BinaryOp(^); + BinaryOp(&); + BinaryOp(|); + BinaryOp(<<); + BinaryOp(>>); + +#undef BinaryOp + +#define CompoundAssignmentOp(opSymbol) \ + template \ + inline constexpr T_Wrap& operator opSymbol##=(const T_Rhs& rhs) \ + { \ + auto& this_ref = impl(); \ + this_ref = this_ref opSymbol rhs; \ + return this_ref; \ + } \ + RLBOX_REQUIRE_SEMI_COLON + + CompoundAssignmentOp(+); + CompoundAssignmentOp(-); + CompoundAssignmentOp(*); + CompoundAssignmentOp(/); + CompoundAssignmentOp(%); + CompoundAssignmentOp(^); + CompoundAssignmentOp(&); + CompoundAssignmentOp(|); + CompoundAssignmentOp(<<); + CompoundAssignmentOp(>>); + +#undef CompoundAssignmentOp + +#define PreIncDecOps(opSymbol) \ + inline constexpr T_Wrap& operator opSymbol##opSymbol() \ + { \ + auto& this_ref = impl(); \ + this_ref = this_ref opSymbol 1; \ + return this_ref; \ + } \ + RLBOX_REQUIRE_SEMI_COLON + + PreIncDecOps(+); + PreIncDecOps(-); + +#undef PreIncDecOps + +#define PostIncDecOps(opSymbol) \ + inline constexpr T_Wrap operator opSymbol##opSymbol(int) \ + { \ + tainted ret = impl(); \ + operator++(); \ + return ret; \ + } \ + RLBOX_REQUIRE_SEMI_COLON + + PostIncDecOps(+); + PostIncDecOps(-); + +#undef PostIncDecOps + +#define BooleanBinaryOp(opSymbol) \ + template \ + inline constexpr auto operator opSymbol(const T_Rhs& rhs) \ + const->tainted() opSymbol std::declval< \ + detail::rlbox_remove_wrapper_t>()), \ + T_Sbx> \ + { \ + static_assert(detail::is_fundamental_or_enum_v, \ + "Operator " #opSymbol \ + " only supported for primitive types"); \ + \ + auto raw = impl().get_raw_value(); \ + auto raw_rhs = detail::unwrap_value(rhs); \ + static_assert(std::is_integral_v, \ + "Can only operate on numeric types"); \ + \ + auto ret = raw opSymbol raw_rhs; \ + using T_Ret = decltype(ret); \ + return tainted::internal_factory(ret); \ + } \ + \ + template \ + inline constexpr auto operator opSymbol(const T_Rhs&&) \ + const->tainted() opSymbol std::declval< \ + detail::rlbox_remove_wrapper_t>()), \ + T_Sbx> \ + { \ + rlbox_detail_static_fail_because( \ + detail::true_v, \ + "C++ does not permit safe overloading of && and || operations as this " \ + "affects the short circuiting behaviour of these operations. RLBox " \ + "does let you use && and || with tainted in limited situations - when " \ + "all arguments starting from the second are local variables. It does " \ + "not allow it if arguments starting from the second are expressions.\n" \ + "For example the following is not allowed\n" \ + "\n" \ + "tainted a = true;\n" \ + "auto r = a && true && sandbox.invoke_sandbox_function(getBool);\n" \ + "\n" \ + "However the following would be allowed\n" \ + "tainted a = true;\n" \ + "auto b = true\n" \ + "auto c = sandbox.invoke_sandbox_function(getBool);\n" \ + "auto r = a && b && c;\n" \ + "\n" \ + "Note that these 2 programs are not identical. The first program may " \ + "or may not call getBool, while second program always calls getBool"); \ + return tainted(false); \ + } \ + RLBOX_REQUIRE_SEMI_COLON + + BooleanBinaryOp(&&); + BooleanBinaryOp(||); + +#undef BooleanBinaryOp + +#define UnaryOp(opSymbol) \ + inline auto operator opSymbol() \ + { \ + static_assert(detail::is_fundamental_or_enum_v, \ + "Operator " #opSymbol " only supported for primitive"); \ + \ + auto raw = impl().get_raw_value(); \ + auto ret = opSymbol raw; \ + using T_Ret = decltype(ret); \ + return tainted::internal_factory(ret); \ + } \ + RLBOX_REQUIRE_SEMI_COLON + + UnaryOp(-); + UnaryOp(~); + +#undef UnaryOp + +/** + * @brief Comparison operators. Comparisons to values in sandbox memory can + * only return a "tainted_boolean_hint" as the values in memory can be + * incorrect or malicously change in the future. + * + * @tparam T_Rhs + * @param rhs + * @return One of either a bool, tainted, or a tainted_boolean_hint + * depending on the arguments to the binary expression. + */ +#define CompareOp(opSymbol, permit_pointers) \ + template \ + inline constexpr auto operator opSymbol(const T_Rhs& rhs) const \ + { \ + using T_RhsNoQ = detail::remove_cv_ref_t; \ + constexpr bool check_rhs_hint = \ + detail::rlbox_is_tainted_volatile_v || \ + detail::rlbox_is_tainted_boolean_hint_v; \ + constexpr bool check_lhs_hint = \ + detail::rlbox_is_tainted_volatile_v>; \ + constexpr bool is_hint = check_lhs_hint || check_rhs_hint; \ + \ + constexpr bool is_unwrapped = \ + detail::rlbox_is_tainted_v> && \ + std::is_null_pointer_v; \ + \ + /* Sanity check - can't be a hint and unwrapped */ \ + static_assert(is_hint ? !is_unwrapped : true, \ + "Internal error: Could not deduce type for comparison. " \ + "Please file a bug."); \ + \ + if constexpr (!permit_pointers && std::is_pointer_v) { \ + rlbox_detail_static_fail_because( \ + std::is_pointer_v, \ + "Only == and != comparisons are allowed for pointers"); \ + } \ + \ + bool ret = (impl().get_raw_value() opSymbol detail::unwrap_value(rhs)); \ + \ + if constexpr (is_hint) { \ + return tainted_boolean_hint(ret); \ + } else if constexpr (is_unwrapped) { \ + return ret; \ + } else { \ + return tainted(ret); \ + } \ + } \ + RLBOX_REQUIRE_SEMI_COLON + + CompareOp(==, true /* permit_pointers */); + CompareOp(!=, true /* permit_pointers */); + CompareOp(<, false /* permit_pointers */); + CompareOp(<=, false /* permit_pointers */); + CompareOp(>, false /* permit_pointers */); + CompareOp(>=, false /* permit_pointers */); + +#undef CompareOp + +private: + using T_OpSubscriptArrRet = std::conditional_t< + std::is_pointer_v, + tainted_volatile, T_Sbx>, // is_pointer + T_Wrap, T_Sbx> // is_array + >; + +public: + template + inline const T_OpSubscriptArrRet& operator[](T_Rhs&& rhs) const + { + static_assert(std::is_pointer_v || detail::is_c_or_std_array_v, + "Operator [] supports pointers and arrays only"); + + auto raw_rhs = detail::unwrap_value(rhs); + static_assert(std::is_integral_v, + "Can only index with numeric types"); + + if constexpr (std::is_pointer_v) { + auto ptr = this->impl().get_raw_value(); + + // increment the target by size of the data structure + auto target = + reinterpret_cast(ptr) + raw_rhs * sizeof(*this->impl()); + auto no_overflow = rlbox_sandbox::is_in_same_sandbox( + ptr, reinterpret_cast(target)); + detail::dynamic_check( + no_overflow, + "Pointer arithmetic overflowed a pointer beyond sandbox memory"); + + auto target_wrap = tainted::internal_factory( + reinterpret_cast(target)); + return *target_wrap; + } else { + using T_Rhs_Unsigned = std::make_unsigned_t; + detail::dynamic_check( + raw_rhs >= 0 && static_cast(raw_rhs) < + std::extent_v, 0>, + "Static array indexing overflow"); + + const void* target_ptr; + if constexpr (detail::rlbox_is_tainted_v>) { + auto& data_ref = impl().get_raw_value_ref(); + target_ptr = &(data_ref[raw_rhs]); + } else { + auto& data_ref = impl().get_sandbox_value_ref(); + auto target_ptr_vol = &(data_ref[raw_rhs]); + // target_ptr is a volatile... remove this. + // Safe as we will return a tainted_volatile if this is the case + target_ptr = detail::remove_volatile_from_ptr_cast(target_ptr_vol); + } + + using T_Target = const T_Wrap, T_Sbx>; + auto wrapped_target_ptr = reinterpret_cast(target_ptr); + return *wrapped_target_ptr; + } + } + + template + inline T_OpSubscriptArrRet& operator[](T_Rhs&& rhs) + { + return const_cast(std::as_const(*this)[rhs]); + } + +private: + using T_OpDerefRet = tainted_volatile, T_Sbx>; + +public: + inline T_OpDerefRet& operator*() const + { + static_assert(std::is_pointer_v, "Operator * only allowed on pointers"); + auto ret_ptr_const = + reinterpret_cast(impl().get_raw_value()); + // Safe - If T_OpDerefRet is not a const ptr, this is trivially safe + // If T_OpDerefRet is a const ptr, then the const is captured + // inside the wrapper + auto ret_ptr = const_cast(ret_ptr_const); + return *ret_ptr; + } + + // We need to implement the -> operator even if T is not a struct + // So that we can support code patterns such as the below + // tainted a; + // a->UNSAFE_unverified(); + inline const T_OpDerefRet* operator->() const + { + static_assert(std::is_pointer_v, + "Operator -> only supported for pointer types"); + return reinterpret_cast(impl().get_raw_value()); + } + + inline T_OpDerefRet* operator->() + { + return const_cast(std::as_const(*this).operator->()); + } + + inline auto operator!() + { + if_constexpr_named(cond1, std::is_pointer_v) + { + return impl() == nullptr; + } + else if_constexpr_named(cond2, std::is_same_v, bool>) + { + return impl() == false; + } + else + { + auto unknownCase = !(cond1 || cond2); + rlbox_detail_static_fail_because( + unknownCase, + "Operator ! only permitted for pointer or boolean types. For other" + "types, unwrap the tainted value with the copy_and_verify API and then" + "use operator !"); + } + } + + /** + * @brief Copy tainted value from sandbox and verify it. + * + * @param verifier Function used to verify the copied value. + * @tparam T_Func the type of the verifier. + * @return Whatever the verifier function returns. + */ + template + inline auto copy_and_verify(T_Func verifier) const + { + using T_Deref = std::remove_cv_t>; + + if_constexpr_named(cond1, detail::is_fundamental_or_enum_v) + { + auto val = impl().get_raw_value(); + return verifier(val); + } + else if_constexpr_named( + cond2, detail::is_one_level_ptr_v && !std::is_class_v) + { + // Some paths don't use the verifier + RLBOX_UNUSED(verifier); + + if_constexpr_named(subcond1, std::is_void_v) + { + rlbox_detail_static_fail_because( + subcond1, + "copy_and_verify not recommended for void* as it could lead to some " + "anti-patterns in verifiers. Cast it to a different tainted pointer " + "with sandbox_reinterpret_cast and then call copy_and_verify. " + "Alternately, you can use the UNSAFE_unverified API to do this " + "without casting."); + return nullptr; + } + // Test with detail::is_func_ptr_v to check for member funcs also + else if_constexpr_named(subcond2, detail::is_func_ptr_v) + { + rlbox_detail_static_fail_because( + subcond2, + "copy_and_verify cannot be applied to function pointers as this " + "makes a deep copy. This is not possible for function pointers. " + "Consider copy_and_verify_address instead."); + return nullptr; + } + else + { + auto val = impl().get_raw_value(); + if (val == nullptr) { + return verifier(nullptr); + } else { + // Important to assign to a local variable (i.e. make a copy) + // Else, for tainted_volatile, this will allow a + // time-of-check-time-of-use attack + auto val_copy = std::make_unique(); + *val_copy = *val; + return verifier(std::move(val_copy)); + } + } + } + else if_constexpr_named( + cond3, detail::is_one_level_ptr_v && std::is_class_v) + { + auto val_copy = std::make_unique>(*impl()); + return verifier(std::move(val_copy)); + } + else if_constexpr_named(cond4, std::is_array_v) + { + static_assert( + detail::is_fundamental_or_enum_v>, + "copy_and_verify on arrays is only safe for fundamental or enum types. " + "For arrays of other types, apply copy_and_verify on each element " + "individually --- a[i].copy_and_verify(...)"); + + auto copy = impl().get_raw_value(); + return verifier(copy); + } + else + { + auto unknownCase = !(cond1 || cond2 || cond3 || cond4); + rlbox_detail_static_fail_because( + unknownCase, + "copy_and_verify not supported for this type as it may be unsafe"); + } + } + +private: + using T_CopyAndVerifyRangeEl = + detail::valid_array_el_t>>; + + // Template needed to ensure that function isn't instantiated for unsupported + // types like function pointers which causes compile errors... + template + inline const void* verify_range_helper(std::size_t count) const + { + static_assert(std::is_pointer_v); + static_assert(detail::is_fundamental_or_enum_v); + + detail::dynamic_check( + count != 0, + "Called copy_and_verify_range/copy_and_verify_string with count 0"); + + auto start = reinterpret_cast(impl().get_raw_value()); + if (start == nullptr) { + return nullptr; + } + + detail::check_range_doesnt_cross_app_sbx_boundary( + start, count * sizeof(T_CopyAndVerifyRangeEl)); + + return start; + } + + template + inline std::unique_ptr copy_and_verify_range_helper( + std::size_t count) const + { + const void* start = verify_range_helper(count); + if (start == nullptr) { + return nullptr; + } + + auto target = std::make_unique(count); + + for (size_t i = 0; i < count; i++) { + auto p_src_i_tainted = &(impl()[i]); + auto p_src_i = p_src_i_tainted.get_raw_value(); + detail::convert_type_fundamental_or_array(target[i], *p_src_i); + } + + return target; + } + +public: + /** + * @brief Copy a range of tainted values from sandbox and verify them. + * + * @param verifier Function used to verify the copied value. + * @param count Number of elements to copy. + * @tparam T_Func the type of the verifier. If the tainted type is ``int*`` + * then ``T_Func = T_Ret(*)(unique_ptr)``. + * @return Whatever the verifier function returns. + */ + template + inline auto copy_and_verify_range(T_Func verifier, std::size_t count) const + { + static_assert(std::is_pointer_v, + "Can only call copy_and_verify_range on pointers"); + + static_assert( + detail::is_fundamental_or_enum_v, + "copy_and_verify_range is only safe for ranges of " + "fundamental or enum types. For other types, call " + "copy_and_verify on each element --- a[i].copy_and_verify(...)"); + + std::unique_ptr target = + copy_and_verify_range_helper(count); + return verifier(std::move(target)); + } + + /** + * @brief Copy a tainted string from sandbox and verify it. + * + * @param verifier Function used to verify the copied value. + * @tparam T_Func the type of the verifier either + * ``T_Ret(*)(unique_ptr)`` or ``T_Ret(*)(std::string)`` + * @return Whatever the verifier function returns. + */ + template + inline auto copy_and_verify_string(T_Func verifier) const + { + static_assert(std::is_pointer_v, + "Can only call copy_and_verify_string on pointers"); + + static_assert(std::is_same_v, + "copy_and_verify_string only allows char*"); + + using T_VerifParam = detail::func_first_arg_t; + + auto start = impl().get_raw_value(); + if_constexpr_named( + cond1, + std::is_same_v> || + std::is_same_v>) + { + if (start == nullptr) { + return verifier(nullptr); + } + + // it is safe to run strlen on a tainted as worst case, the string + // does not have a null and we try to copy all the memory out of the + // sandbox however, copy_and_verify_range ensures that we never copy + // memory outsider the range + auto str_len = std::strlen(start) + 1; + std::unique_ptr target = + copy_and_verify_range_helper(str_len); + + // ensure the string has a trailing null + target[str_len - 1] = '\0'; + + return verifier(std::move(target)); + } + else if_constexpr_named(cond2, std::is_same_v) + { + if (start == nullptr) { + std::string param = ""; + return verifier(param); + } + + // it is safe to run strlen on a tainted as worst case, the string + // does not have a null and we try to copy all the memory out of the + // sandbox however, copy_and_verify_range ensures that we never copy + // memory outsider the range + auto str_len = std::strlen(start) + 1; + + const char* checked_start = (const char*)verify_range_helper(str_len); + if (checked_start == nullptr) { + std::string param = ""; + return verifier(param); + } + + std::string copy(checked_start, str_len - 1); + return verifier(std::move(copy)); + } + else + { + constexpr bool unknownCase = !(cond1 || cond2); + rlbox_detail_static_fail_because( + unknownCase, + "copy_and_verify_string verifier parameter should either be " + "unique_ptr, unique_ptr or std::string"); + } + } + + /** + * @brief Copy a tainted pointer from sandbox and verify the address. + * + * This function is useful if you need to verify physical bits representing + * the address of a pointer. Other APIs such as copy_and_verify performs a + * deep copy and changes the address bits. + * + * @param verifier Function used to verify the copied value. + * @tparam T_Func the type of the verifier ``T_Ret(*)(uintptr_t)`` + * @return Whatever the verifier function returns. + */ + template + inline auto copy_and_verify_address(T_Func verifier) const + { + static_assert(std::is_pointer_v, + "copy_and_verify_address must be used on pointers"); + auto val = reinterpret_cast(impl().get_raw_value()); + return verifier(val); + } + + /** + * @brief Copy a tainted pointer to a buffer from sandbox and verify the + * address. + * + * This function is useful if you need to verify physical bits representing + * the address of a buffer. Other APIs such as copy_and_verify performs a + * deep copy and changes the address bits. + * + * @param verifier Function used to verify the copied value. + * @param size Size of the buffer. Buffer with length size is expected to fit + * inside sandbox memory. + * @tparam T_Func the type of the verifier ``T_Ret(*)(uintptr_t)`` + * @return Whatever the verifier function returns. + */ + template + inline auto copy_and_verify_buffer_address(T_Func verifier, + std::size_t size) const + { + static_assert(std::is_pointer_v, + "copy_and_verify_address must be used on pointers"); + auto val = reinterpret_cast(verify_range_helper(size)); + return verifier(val); + } +}; + +#define BinaryOpWrappedRhs(opSymbol) \ + template typename T_Wrap, \ + typename T, \ + typename T_Sbx, \ + typename T_Lhs, \ + RLBOX_ENABLE_IF(!detail::rlbox_is_wrapper_v && \ + !detail::rlbox_is_tainted_boolean_hint_v)> \ + inline constexpr auto operator opSymbol( \ + const T_Lhs& lhs, const tainted_base_impl& rhs) \ + { \ + /* Handles the case for "3 + tainted", where + is a binary op */ \ + /* Technically pointer arithmetic can be performed as 3 + tainted_ptr */ \ + /* as well. However, this is unusual and to keep the code simple we do */ \ + /* not support this. */ \ + static_assert( \ + std::is_arithmetic_v, \ + "Binary expressions between an non tainted type and tainted" \ + "type is only permitted if the first value is the tainted type. Try " \ + "changing the order of the binary expression accordingly"); \ + auto ret = tainted(lhs) opSymbol rhs.impl(); \ + return ret; \ + } \ + RLBOX_REQUIRE_SEMI_COLON + +BinaryOpWrappedRhs(+); +BinaryOpWrappedRhs(-); +BinaryOpWrappedRhs(*); +BinaryOpWrappedRhs(/); +BinaryOpWrappedRhs(%); +BinaryOpWrappedRhs(^); +BinaryOpWrappedRhs(&); +BinaryOpWrappedRhs(|); +BinaryOpWrappedRhs(<<); +BinaryOpWrappedRhs(>>); +BinaryOpWrappedRhs(==); +BinaryOpWrappedRhs(!=); +BinaryOpWrappedRhs(<); +BinaryOpWrappedRhs(<=); +BinaryOpWrappedRhs(>); +BinaryOpWrappedRhs(>=); +#undef BinaryOpWrappedRhs + +#define BooleanBinaryOpWrappedRhs(opSymbol) \ + template typename T_Wrap, \ + typename T, \ + typename T_Sbx, \ + typename T_Lhs, \ + RLBOX_ENABLE_IF(!detail::rlbox_is_wrapper_v && \ + !detail::rlbox_is_tainted_boolean_hint_v)> \ + inline constexpr auto operator opSymbol( \ + const T_Lhs& lhs, const tainted_base_impl& rhs) \ + { \ + static_assert( \ + std::is_arithmetic_v, \ + "Binary expressions between an non tainted type and tainted" \ + "type is only permitted if the first value is the tainted type. Try " \ + "changing the order of the binary expression accordingly"); \ + auto ret = tainted(lhs) opSymbol rhs.impl(); \ + return ret; \ + } \ + \ + template typename T_Wrap, \ + typename T, \ + typename T_Sbx, \ + typename T_Lhs, \ + RLBOX_ENABLE_IF(!detail::rlbox_is_wrapper_v && \ + !detail::rlbox_is_tainted_boolean_hint_v)> \ + inline constexpr auto operator opSymbol( \ + const T_Lhs&, const tainted_base_impl&&) \ + { \ + rlbox_detail_static_fail_because( \ + detail::true_v, \ + "C++ does not permit safe overloading of && and || operations as this " \ + "affects the short circuiting behaviour of these operations. RLBox " \ + "does let you use && and || with tainted in limited situations - when " \ + "all arguments starting from the second are local variables. It does " \ + "not allow it if arguments starting from the second are expressions.\n" \ + "For example the following is not allowed\n" \ + "\n" \ + "tainted a = true;\n" \ + "auto r = a && true && sandbox.invoke_sandbox_function(getBool);\n" \ + "\n" \ + "However the following would be allowed\n" \ + "tainted a = true;\n" \ + "auto b = true\n" \ + "auto c = sandbox.invoke_sandbox_function(getBool);\n" \ + "auto r = a && b && c;\n" \ + "\n" \ + "Note that these 2 programs are not identical. The first program may " \ + "or may not call getBool, while second program always calls getBool"); \ + return tainted(false); \ + } \ + RLBOX_REQUIRE_SEMI_COLON + +BooleanBinaryOpWrappedRhs(&&); +BooleanBinaryOpWrappedRhs(||); +#undef BooleanBinaryOpWrappedRhs + +namespace tainted_detail { + template + using tainted_repr_t = detail::c_to_std_array_t; + + template + using tainted_vol_repr_t = + detail::c_to_std_array_t::template convert_to_sandbox_equivalent_nonclass_t>>; +} + +/** + * @brief Tainted values represent untrusted values that originate from the + * sandbox. + */ +template +class tainted : public tainted_base_impl +{ + KEEP_CLASSES_FRIENDLY + KEEP_CAST_FRIENDLY + + // Classes recieve their own specialization + static_assert( + !std::is_class_v, + "Missing definition for class T. This error occurs for one " + "of 2 reasons.\n" + " 1) Make sure you have include a call rlbox_load_structs_from_library " + "for this library with this class included.\n" + " 2) Make sure you run (re-run) the struct-dump tool to list " + "all structs in use by your program.\n"); + + static_assert( + detail::is_basic_type_v || std::is_array_v, + "Tainted types only support fundamental, enum, pointer, array and struct " + "types. Please file a bug if more support is needed."); + +private: + using T_ClassBase = tainted_base_impl; + using T_AppType = tainted_detail::tainted_repr_t; + using T_SandboxedType = tainted_detail::tainted_vol_repr_t; + T_AppType data; + + inline auto& get_raw_value_ref() noexcept { return data; } + inline auto& get_raw_value_ref() const noexcept { return data; } + + inline std::remove_cv_t get_raw_value() const noexcept + { + return data; + } + + inline std::remove_cv_t get_raw_sandbox_value( + rlbox_sandbox& sandbox) const + { + std::remove_cv_t ret; + + using namespace detail; + convert_type_non_class( + ret, data, nullptr /* example_unsandboxed_ptr */, &sandbox); + return ret; + }; + + inline const void* find_example_pointer_or_null() const noexcept + { + if constexpr (std::is_array_v) { + auto& data_ref = get_raw_value_ref(); + + for (size_t i = 0; i < std::extent_v; i++) { + const void* ret = data[i].find_example_pointer_or_null(); + if (ret != nullptr) { + return ret; + } + } + } else if constexpr (std::is_pointer_v && !detail::is_func_ptr_v) { + auto data = get_raw_value(); + return data; + } + return nullptr; + } + + // Initializing with a pointer is dangerous and permitted only internally + template)> + tainted(T2 val, const void* /* internal_tag */) + : data(val) + { + // Sanity check + static_assert(std::is_pointer_v); + } + + template + static inline tainted internal_factory(T_Rhs&& rhs) + { + if constexpr (std::is_pointer_v>) { + const void* internal_tag = nullptr; + return tainted(std::forward(rhs), internal_tag); + } else { + return tainted(std::forward(rhs)); + } + } + +public: + tainted() = default; + tainted(const tainted& p) = default; + + tainted(const tainted_volatile& p) + { + // Need to construct an example_unsandboxed_ptr for pointers or arrays of + // pointers. Since tainted_volatile is the type of data in sandbox memory, + // the address of data (&data) refers to a location in sandbox memory and + // can thus be the example_unsandboxed_ptr + const volatile void* p_data_ref = &p.get_sandbox_value_ref(); + const void* example_unsandboxed_ptr = const_cast(p_data_ref); + using namespace detail; + convert_type_non_class( + get_raw_value_ref(), + p.get_sandbox_value_ref(), + example_unsandboxed_ptr, + nullptr /* sandbox_ptr */); + } + + // Initializing with a pointer is dangerous and permitted only internally + template)> + tainted(T2 val) + : data(val) + { + rlbox_detail_static_fail_because( + std::is_pointer_v, + "Assignment of pointers is not safe as it could\n " + "1) Leak pointers from the appliction to the sandbox which may break " + "ASLR\n " + "2) Pass inaccessible pointers to the sandbox leading to crash\n " + "3) Break sandboxes that require pointers to be swizzled first\n " + "\n " + "Instead, if you want to pass in a pointer, do one of the following\n " + "1) Allocate with malloc_in_sandbox, and pass in a tainted pointer\n " + "2) For pointers that point to functions in the application, register " + "with sandbox.register_callback(\"foo\"), and pass in the registered " + "value\n " + "3) For pointers that point to functions in the sandbox, get the " + "address with get_sandbox_function_address(sandbox, foo), and pass in " + "the " + "address\n " + "4) For raw pointers, use assign_raw_pointer which performs required " + "safety checks\n "); + } + + tainted( + const sandbox_callback< + detail::function_ptr_t // Need to ensure we never generate code that + // creates a sandbox_callback of a non function + , + T_Sbx>&) + { + rlbox_detail_static_fail_because( + detail::true_v, + "RLBox does not support assigning sandbox_callback values to tainted " + "types (i.e. types that live in application memory).\n" + "If you still want to do this, consider changing your code to store the " + "value in sandbox memory as follows. Convert\n\n" + "sandbox_callback cb = ...;\n" + "tainted foo = cb;\n\n" + "to\n\n" + "tainted foo_ptr = sandbox.malloc_in_sandbox();\n" + "*foo_ptr = cb;\n\n" + "This would keep the assignment in sandbox memory"); + } + + tainted(const std::nullptr_t& arg) + : data(arg) + { + static_assert(std::is_pointer_v); + } + + // We explicitly disable this constructor if it has one of the signatures + // above, so that we give the above constructors a higher priority. We only + // allow this for fundamental types as this is potentially unsafe for pointers + // and structs + template> && + detail::is_fundamental_or_enum_v && + detail::is_fundamental_or_enum_v>)> + tainted(T_Arg&& arg) + : data(std::forward(arg)) + {} + + template + void assign_raw_pointer(rlbox_sandbox& sandbox, T_Rhs val) + { + static_assert(std::is_pointer_v, "Must be a pointer"); + static_assert(std::is_assignable_v, + "Should assign pointers of compatible types."); + // Maybe a function pointer, so we need to cast + const void* cast_val = reinterpret_cast(val); + bool safe = sandbox.is_pointer_in_sandbox_memory(cast_val); + detail::dynamic_check( + safe, + "Tried to assign a pointer that is not in the sandbox.\n " + "This is not safe as it could\n " + "1) Leak pointers from the appliction to the sandbox which may break " + "ASLR\n " + "2) Pass inaccessible pointers to the sandbox leading to crash\n " + "3) Break sandboxes that require pointers to be swizzled first\n " + "\n " + "Instead, if you want to pass in a pointer, do one of the following\n " + "1) Allocate with malloc_in_sandbox, and pass in a tainted pointer\n " + "2) For pointers that point to functions in the application, register " + "with sandbox.register_callback(\"foo\"), and pass in the registered " + "value\n " + "3) For pointers that point to functions in the sandbox, get the " + "address with get_sandbox_function_address(sandbox, foo), and pass in " + "the " + "address\n "); + data = val; + } + + inline tainted_opaque to_opaque() + { + return *reinterpret_cast*>(this); + } + + template + operator bool() const + { + if_constexpr_named(cond1, std::is_pointer_v) + { + // We return this without the tainted wrapper as the checking for null + // doesn't really "induce" tainting in the application If the + // application is checking this pointer for null, then it is robust to + // this pointer being null or not null + return get_raw_value() != nullptr; + } + else + { + auto unknownCase = !(cond1); + rlbox_detail_static_fail_because( + unknownCase, + "Implicit conversion to bool is only permitted for pointer types. For " + "other types, unwrap the tainted value with the copy_and_verify API " + "and then perform the required checks"); + } + } +}; + +template +inline tainted from_opaque(tainted_opaque val) +{ + return *reinterpret_cast*>(&val); +} + +/** + * @brief Tainted volatile values are like tainted values but still point to + * sandbox memory. Dereferencing a tainted pointer produces a tainted_volatile. + */ +template +class tainted_volatile : public tainted_base_impl +{ + KEEP_CLASSES_FRIENDLY + KEEP_CAST_FRIENDLY + + // Classes recieve their own specialization + static_assert( + !std::is_class_v, + "Missing definition for class T. This error occurs for one " + "of 2 reasons.\n" + " 1) Make sure you have include a call rlbox_load_structs_from_library " + "for this library with this class included.\n" + " 2) Make sure you run (re-run) the struct-dump tool to list " + "all structs in use by your program.\n"); + + static_assert( + detail::is_basic_type_v || std::is_array_v, + "Tainted types only support fundamental, enum, pointer, array and struct " + "types. Please file a bug if more support is needed."); + +private: + using T_ClassBase = tainted_base_impl; + using T_AppType = tainted_detail::tainted_repr_t; + using T_SandboxedType = tainted_detail::tainted_vol_repr_t; + T_SandboxedType data; + + inline auto& get_sandbox_value_ref() noexcept { return data; } + inline auto& get_sandbox_value_ref() const noexcept { return data; } + + inline std::remove_cv_t get_raw_value() const + { + std::remove_cv_t ret; + // Need to construct an example_unsandboxed_ptr for pointers or arrays of + // pointers. Since tainted_volatile is the type of data in sandbox memory, + // the address of data (&data) refers to a location in sandbox memory and + // can thus be the example_unsandboxed_ptr + const volatile void* data_ref = &data; + const void* example_unsandboxed_ptr = const_cast(data_ref); + using namespace detail; + convert_type_non_class( + ret, data, example_unsandboxed_ptr, nullptr /* sandbox_ptr */); + return ret; + } + + inline std::remove_cv_t get_raw_sandbox_value() + const noexcept + { + return data; + }; + + inline std::remove_cv_t get_raw_sandbox_value( + rlbox_sandbox& sandbox) const noexcept + { + RLBOX_UNUSED(sandbox); + return data; + }; + + tainted_volatile() = default; + tainted_volatile(const tainted_volatile& p) = default; + +public: + inline tainted operator&() const noexcept + { + auto ref = + detail::remove_volatile_from_ptr_cast(&this->get_sandbox_value_ref()); + auto ref_cast = reinterpret_cast(ref); + return tainted::internal_factory(ref_cast); + } + + inline tainted operator&() noexcept + { + return sandbox_const_cast(&std::as_const(*this)); + } + + // Needed as the definition of unary & above shadows the base's binary & + rlbox_detail_forward_binop_to_base(&, T_ClassBase); + + template + inline tainted_volatile& operator=(T_RhsRef&& val) + { + using T_Rhs = std::remove_reference_t; + using T_Rhs_El = std::remove_all_extents_t; + + // Need to construct an example_unsandboxed_ptr for pointers or arrays of + // pointers. Since tainted_volatile is the type of data in sandbox memory, + // the address of data (&data) refers to a location in sandbox memory and + // can thus be the example_unsandboxed_ptr + const volatile void* data_ref = &get_sandbox_value_ref(); + const void* example_unsandboxed_ptr = const_cast(data_ref); + // Some branches don't use this + RLBOX_UNUSED(example_unsandboxed_ptr); + + if_constexpr_named( + cond1, std::is_same_v, std::nullptr_t>) + { + static_assert(std::is_pointer_v, + "Null pointer can only be assigned to pointers"); + // assign using an integer instead of nullptr, as the pointer field may be + // represented as integer + data = 0; + } + else if_constexpr_named(cond2, detail::rlbox_is_tainted_v) + { + using namespace detail; + convert_type_non_class( + get_sandbox_value_ref(), + val.get_raw_value_ref(), + example_unsandboxed_ptr, + nullptr /* sandbox_ptr */); + } + else if_constexpr_named(cond3, detail::rlbox_is_tainted_volatile_v) + { + using namespace detail; + convert_type_non_class( + get_sandbox_value_ref(), + val.get_sandbox_value_ref(), + example_unsandboxed_ptr, + nullptr /* sandbox_ptr */); + } + else if_constexpr_named(cond4, detail::rlbox_is_sandbox_callback_v) + { + using T_RhsFunc = detail::rlbox_remove_wrapper_t; + + // need to perform some typechecking to ensure we are assigning compatible + // function pointer types only + if_constexpr_named(subcond1, !std::is_assignable_v) + { + rlbox_detail_static_fail_because( + subcond1, + "Trying to assign function pointer to field of incompatible types"); + } + else + { + // Need to reinterpret_cast as the representation of the signature of a + // callback uses the machine model of the sandbox, while the field uses + // that of the application. But we have already checked above that this + // is safe. + auto func = val.get_raw_sandbox_value(); + using T_Cast = std::remove_volatile_t; + get_sandbox_value_ref() = (T_Cast)func; + } + } + else if_constexpr_named( + cond5, + detail::is_fundamental_or_enum_v || + (std::is_array_v && !std::is_pointer_v)) + { + detail::convert_type_fundamental_or_array(get_sandbox_value_ref(), val); + } + else if_constexpr_named( + cond6, std::is_pointer_v || std::is_pointer_v) + { + rlbox_detail_static_fail_because( + cond6, + "Assignment of pointers is not safe as it could\n " + "1) Leak pointers from the appliction to the sandbox which may break " + "ASLR\n " + "2) Pass inaccessible pointers to the sandbox leading to crash\n " + "3) Break sandboxes that require pointers to be swizzled first\n " + "\n " + "Instead, if you want to pass in a pointer, do one of the following\n " + "1) Allocate with malloc_in_sandbox, and pass in a tainted pointer\n " + "2) For pointers that point to functions in the application, register " + "with sandbox.register_callback(\"foo\"), and pass in the registered " + "value\n " + "3) For pointers that point to functions in the sandbox, get the " + "address with get_sandbox_function_address(sandbox, foo), and pass in " + "the " + "address\n " + "4) For raw pointers, use assign_raw_pointer which performs required " + "safety checks\n "); + } + else + { + auto unknownCase = + !(cond1 || cond2 || cond3 || cond4 || cond5 /* || cond6 */); + rlbox_detail_static_fail_because( + unknownCase, "Assignment of the given type of value is not supported"); + } + + return *this; + } + + template + void assign_raw_pointer(rlbox_sandbox& sandbox, T_Rhs val) + { + static_assert(std::is_pointer_v, "Must be a pointer"); + static_assert(std::is_assignable_v, + "Should assign pointers of compatible types."); + // Maybe a function pointer, so we need to cast + const void* cast_val = reinterpret_cast(val); + bool safe = sandbox.is_pointer_in_sandbox_memory(cast_val); + detail::dynamic_check( + safe, + "Tried to assign a pointer that is not in the sandbox.\n " + "This is not safe as it could\n " + "1) Leak pointers from the appliction to the sandbox which may break " + "ASLR\n " + "2) Pass inaccessible pointers to the sandbox leading to crash\n " + "3) Break sandboxes that require pointers to be swizzled first\n " + "\n " + "Instead, if you want to pass in a pointer, do one of the following\n " + "1) Allocate with malloc_in_sandbox, and pass in a tainted pointer\n " + "2) For pointers that point to functions in the application, register " + "with sandbox.register_callback(\"foo\"), and pass in the registered " + "value\n " + "3) For pointers that point to functions in the sandbox, get the " + "address with get_sandbox_function_address(sandbox, foo), and pass in " + "the " + "address\n "); + get_sandbox_value_ref() = + sandbox.template get_sandboxed_pointer(cast_val); + } + + template + operator bool() const + { + rlbox_detail_static_fail_because( + detail::true_v, + "Cannot apply implicit conversion to bool on values that are located in " + "sandbox memory. This error occurs if you compare a dereferenced value " + "such as the code shown below\n\n" + "tainted a = ...;\n" + "assert(*a);\n\n" + "Instead you can write this code as \n" + "tainted temp = *a;\n" + "assert(temp);\n"); + return false; + } +}; + +} -- cgit v1.2.3