#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; } }; }