зеркало из https://github.com/mozilla/gecko-dev.git
1343 строки
56 KiB
C++
1343 строки
56 KiB
C++
#pragma once
|
|
|
|
#include <array>
|
|
#include <cstring>
|
|
#include <memory>
|
|
#include <type_traits>
|
|
#include <utility>
|
|
|
|
#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() { return impl().get_raw_value(); }
|
|
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)
|
|
{
|
|
return impl().get_raw_sandbox_value(sandbox);
|
|
}
|
|
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.
|
|
*/
|
|
rlbox_detail_member_and_const(
|
|
template<size_t N>
|
|
inline auto unverified_safe_because(const char (&reason)[N]),
|
|
{
|
|
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();
|
|
});
|
|
|
|
rlbox_detail_member_and_const(
|
|
template<size_t N>
|
|
inline auto unverified_safe_pointer_because(size_t count,
|
|
const char (&reason)[N]),
|
|
{
|
|
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() { return UNSAFE_unverified(); }
|
|
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)>, \
|
|
"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)
|
|
{
|
|
rlbox_detail_forward_to_const_a(operator[], T_OpSubscriptArrRet&, 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;
|
|
}
|
|
|
|
inline T_OpDerefRet& operator*()
|
|
{
|
|
rlbox_detail_forward_to_const(operator*, T_OpDerefRet&);
|
|
}
|
|
|
|
// 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 auto operator-> () const
|
|
{
|
|
static_assert(std::is_pointer_v<T>,
|
|
"Operator -> only supported for pointer types");
|
|
auto ret = impl().get_raw_value();
|
|
using T_Ret = std::remove_pointer_t<T>;
|
|
using T_RetWrap = const tainted_volatile<T_Ret, T_Sbx>;
|
|
return reinterpret_cast<T_RetWrap*>(ret);
|
|
}
|
|
|
|
inline auto operator-> ()
|
|
{
|
|
using T_Ret = tainted_volatile<std::remove_pointer_t<T>, T_Sbx>*;
|
|
rlbox_detail_forward_to_const(operator->, T_Ret);
|
|
}
|
|
|
|
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 verifer 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 verifer 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 verifer Function used to verify the copied value.
|
|
* @tparam T_Func the type of the verifier ``T_Ret(*)(unique_ptr<char[]>)``
|
|
* @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*");
|
|
|
|
auto start = impl().get_raw_value();
|
|
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));
|
|
}
|
|
|
|
/**
|
|
* @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)
|
|
{
|
|
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)
|
|
{
|
|
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 specialization 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.\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 std::remove_cv_t<T_AppType> get_raw_value() noexcept
|
|
{
|
|
rlbox_detail_forward_to_const(get_raw_value, std::remove_cv_t<T_AppType>);
|
|
}
|
|
|
|
inline std::remove_cv_t<T_SandboxedType> get_raw_sandbox_value(
|
|
rlbox_sandbox<T_Sbx>& sandbox)
|
|
{
|
|
rlbox_detail_forward_to_const_a(
|
|
get_raw_sandbox_value, std::remove_cv_t<T_SandboxedType>, sandbox);
|
|
};
|
|
|
|
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 specialization 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.\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_AppType> get_raw_value()
|
|
{
|
|
rlbox_detail_forward_to_const(get_raw_value, std::remove_cv_t<T_AppType>);
|
|
}
|
|
|
|
inline std::remove_cv_t<T_SandboxedType> get_raw_sandbox_value() noexcept
|
|
{
|
|
rlbox_detail_forward_to_const(get_raw_sandbox_value,
|
|
std::remove_cv_t<T_SandboxedType>);
|
|
};
|
|
|
|
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);
|
|
auto ret = tainted<const T*, T_Sbx>::internal_factory(ref_cast);
|
|
return ret;
|
|
}
|
|
|
|
inline tainted<T*, T_Sbx> operator&() noexcept
|
|
{
|
|
using T_Ret = tainted<T*, T_Sbx>;
|
|
rlbox_detail_forward_to_const(operator&, T_Ret);
|
|
}
|
|
|
|
// 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() = reinterpret_cast<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;
|
|
}
|
|
};
|
|
|
|
}
|