std::string to_string()
{
std::ostringstream ret; if (invoke == rlbox_transition::INVOKE) {
ret << name;
} else {
ret << "Callback " << ptr;
}
ret << " : " << time << "\n";
return ret.str();
}
}; #endif
#ifndef RLBOX_SINGLE_THREADED_INVOCATIONS # error \ "RLBox does not yet support threading. Please define RLBOX_SINGLE_THREADED_INVOCATIONS prior to including RLBox and ensure you are only using it from a single thread. If threading is required, please file a bug." #endif
/** * @brief Encapsulation for sandboxes. * * @tparam T_Sbx Type of sandbox. For the null sandbox this is * `rlbox_noop_sandbox`
*/ template<typename T_Sbx> class rlbox_sandbox : protected T_Sbx
{
KEEP_CLASSES_FRIENDLY
staticinline RLBOX_SHARED_LOCK(sandbox_list_lock); // The actual type of the vector is std::vector<rlbox_sandbox<T_Sbx>*> // However clang 5, 6 have bugs where compilation seg-faults on this type // So we just use this std::vector<void*> staticinline MOZ_RUNINIT std::vector<void*> sandbox_list;
// This variable tracks of the sandbox has already been created/destroyed. // APIs in this class should be called only when the sandbox is created. // However, it is expensive to check in APIs such as invoke or in the callback // interceptor. What's more, there could be time of check time of use issues // in the checks as well. // In general, we leave it up to the user to ensure these APIs are never // called prior to sandbox construction or after destruction. We perform some // conservative sanity checks, where they would not add too much overhead. enumclass Sandbox_Status
{
NOT_CREATED,
INITIALIZING,
CREATED,
CLEANING_UP
};
std::atomic<Sandbox_Status> sandbox_created = Sandbox_Status::NOT_CREATED;
if_constexpr_named(cond1, detail::rlbox_is_wrapper_v<T_NoRef>)
{
if_constexpr_named(
subcond1,
!std::is_same_v<T_Sbx, detail::rlbox_get_wrapper_sandbox_t<T_NoRef>>)
{
rlbox_detail_static_fail_because(
cond1 && subcond1, "Mixing tainted data from a different sandbox types. This could " "happen due to couple of different reasons.\n" "1. You are using 2 sandbox types for example'rlbox_noop_sandbox' " "and 'rlbox_lucet_sandbox', and are passing tainted data from one " "sandbox as parameters into a function call to the other sandbox. " "This is not allowed, unwrap the tainted data with copy_and_verify " "or other unwrapping APIs first.\n" "2. You have inadvertantly forgotten to set/remove " "RLBOX_USE_STATIC_CALLS depending on the sandbox type. Some sandbox " "types like rlbox_noop_sandbox require this to be set to a given " "value, while other types like rlbox_lucet_sandbox, require this not " "to be set.");
}
} else if_constexpr_named(cond2,
std::is_null_pointer_v<T_NoRef> ||
detail::is_fundamental_or_enum_v<T_NoRef>)
{} else
{
constexpr auto unknownCase = !(cond1 || cond2);
rlbox_detail_static_fail_because(
unknownCase, "Arguments to a sandbox function call should be primitives or wrapped " "types like tainted, callbacks etc.");
}
}
if constexpr (detail::rlbox_is_tainted_opaque_v<T_NoRef>) { auto ret = from_opaque(param); return ret.UNSAFE_sandboxed(*this);
} elseif constexpr (detail::rlbox_is_wrapper_v<T_NoRef>) { return param.UNSAFE_sandboxed(*this);
} elseif constexpr (std::is_null_pointer_v<T_NoRef>) {
tainted<void*, T_Sbx> ret = nullptr; return ret.UNSAFE_sandboxed(*this);
} elseif constexpr (detail::is_fundamental_or_enum_v<T_NoRef>) { // For unwrapped primitives, assign to a tainted var and then unwrap so // that we adjust for machine model
tainted<T_NoRef, T_Sbx> ret = param; return ret.UNSAFE_sandboxed(*this);
} else {
rlbox_detail_static_fail_because(
detail::true_v<T_NoRef>, "Only tainted types, callbacks or primitive values such as ints can be " "passed as parameters.\n" "To make a parameter tainted, try moving the allocation into the " "sandbox.\n" "If the parameter is a callback, try registering the callback via the " "register_callback API.");
}
}
/** * @brief Unregister a callback function and disallow the sandbox from * calling this function henceforth.
*/ template<typename T_Ret, typename... T_Args> inlinevoid unregister_callback(void* key)
{ // Silently swallowing the failure is better here as RAII types may try to // cleanup callbacks after sandbox destruction if (sandbox_created.load() != Sandbox_Status::CREATED) { return;
}
std::lock_guard<std::mutex> lock(callback_lock); auto el_ref = std::find(callback_keys.begin(), callback_keys.end(), key);
detail::dynamic_check(
el_ref != callback_keys.end(), "Unexpected state. Unregistering a callback that was never registered.");
callback_keys.erase(el_ref);
}
static T_Sbx* find_sandbox_from_example(constvoid* example_sandbox_ptr)
{
detail::dynamic_check(
example_sandbox_ptr != nullptr, "Internal error: received a null example pointer. Please file a bug.");
RLBOX_ACQUIRE_SHARED_GUARD(lock, sandbox_list_lock); for (auto sandbox_v : sandbox_list) { auto sandbox = reinterpret_cast<rlbox_sandbox<T_Sbx>*>(sandbox_v); if (sandbox->is_pointer_in_sandbox_memory(example_sandbox_ptr)) { return sandbox;
}
}
public: /** * @brief Unused member that allows the calling code to save data in a * "per-sandbox" storage. This can be useful to save context which is used * in callbacks.
*/ void* sandbox_storage;
/***** Function to adjust for custom machine models *****/
/** * @brief Create a new sandbox. * * @tparam T_Args Arguments passed to the underlying sandbox * implementation. For the null sandbox, no arguments are necessary.
*/ template<typename... T_Args> inlinebool create_sandbox(T_Args... args)
{ #ifdef RLBOX_MEASURE_TRANSITION_TIMES // Warm up the timer. The first call is always slow (at least on the test // platform) for (int i = 0; i < 10; i++) { auto val = high_resolution_clock::now();
RLBOX_UNUSED(val);
} #endif auto expected = Sandbox_Status::NOT_CREATED; bool success = sandbox_created.compare_exchange_strong(
expected, Sandbox_Status::INITIALIZING /* desired */);
detail::dynamic_check(
success, "create_sandbox called when sandbox already created/is being " "created concurrently");
using T_Result = rlbox::detail::polyfill::invoke_result_t<
decltype(impl_create_sandbox_helper<T_Args...>),
decltype(this),
T_Args...>;
bool created = true; if constexpr (std::is_same_v<T_Result, void>) {
this->impl_create_sandbox(std::forward<T_Args>(args)...);
} elseif constexpr (std::is_same_v<T_Result, bool>) {
created = this->impl_create_sandbox(std::forward<T_Args>(args)...);
} else {
rlbox_detail_static_fail_because(
(!std::is_same_v<T_Result, void> && !std::is_same_v<T_Result, bool>), "Expected impl_create_sandbox to return void or a boolean");
}
if (created) {
sandbox_created.store(Sandbox_Status::CREATED);
RLBOX_ACQUIRE_UNIQUE_GUARD(lock, sandbox_list_lock);
sandbox_list.push_back(this);
}
return created;
}
/** * @brief Destroy sandbox and reclaim any memory.
*/ inlineauto destroy_sandbox()
{ auto expected = Sandbox_Status::CREATED; bool success = sandbox_created.compare_exchange_strong(
expected, Sandbox_Status::CLEANING_UP /* desired */);
detail::dynamic_check(
success, "destroy_sandbox called without sandbox creation/is being " "destroyed concurrently");
{
RLBOX_ACQUIRE_UNIQUE_GUARD(lock, sandbox_list_lock); auto el_ref = std::find(sandbox_list.begin(), sandbox_list.end(), this);
detail::dynamic_check(
el_ref != sandbox_list.end(), "Unexpected state. Destroying a sandbox that was never initialized.");
sandbox_list.erase(el_ref);
}
/** * @brief Allocate a new pointer that is accessible to both the application * and sandbox. The pointer is allocated in sandbox memory. * * @tparam T The type of the pointer you want to create. If T=int, this * would return a pointer to an int. * * @return tainted<T*, T_Sbx> Tainted pointer accessible to the application * and sandbox.
*/ template<typename T> inline tainted<T*, T_Sbx> malloc_in_sandbox()
{ const uint32_t defaultCount = 1; return malloc_in_sandbox<T>(defaultCount);
}
/** * @brief Allocate an array that is accessible to both the application * and sandbox. The pointer is allocated in sandbox memory. * * @tparam T The type of the array elements you want to create. If T=int, this * would return a pointer to an array of ints. * * @param count The number of array elements to allocate. * * @return tainted<T*, T_Sbx> Tainted pointer accessible to the application * and sandbox.
*/ template<typename T> inline tainted<T*, T_Sbx> malloc_in_sandbox(uint32_t count)
{ // Silently swallowing the failure is better here as RAII types may try to // malloc after sandbox destruction if (sandbox_created.load() != Sandbox_Status::CREATED) { return tainted<T*, T_Sbx>::internal_factory(nullptr);
}
detail::dynamic_check(count != 0, "Malloc tried to allocate 0 bytes"); if constexpr (sizeof(T) >= std::numeric_limits<uint32_t>::max()) {
rlbox_detail_static_fail_because(sizeof(T) >=
std::numeric_limits<uint32_t>::max(), "Tried to allocate an object over 4GB.");
} auto total_size = static_cast<uint64_t>(sizeof(T)) * count; if constexpr (sizeof(size_t) == 4) { // On a 32-bit platform, we need to make sure that total_size is not >=4GB
detail::dynamic_check(total_size < std::numeric_limits<uint32_t>::max(), "Tried to allocate memory over 4GB");
} elseif constexpr (sizeof(size_t) != 8) { // Double check we are on a 64-bit platform // Note for static checks we need to have some dependence on T, so adding // a dummy
constexpr bool dummy = sizeof(T) >= 0;
rlbox_detail_static_fail_because(dummy && sizeof(size_t) != 8, "Expected 32 or 64 bit platform.");
} auto ptr_in_sandbox = this->impl_malloc_in_sandbox(total_size); auto ptr = get_unsandboxed_pointer<T*>(ptr_in_sandbox); if (!ptr) { return tainted<T*, T_Sbx>(nullptr);
}
detail::dynamic_check(is_pointer_in_sandbox_memory(ptr), "Malloc returned pointer outside the sandbox memory"); auto ptr_end = reinterpret_cast<uintptr_t>(ptr + (count - 1));
detail::dynamic_check(
is_in_same_sandbox(ptr, reinterpret_cast<void*>(ptr_end)), "Malloc returned a pointer whose range goes beyond sandbox memory"); auto cast_ptr = reinterpret_cast<T*>(ptr); return tainted<T*, T_Sbx>::internal_factory(cast_ptr);
}
/** * @brief Free the memory referenced by the tainted pointer. * * @param ptr Pointer to sandbox memory to free.
*/ template<typename T> inlinevoid free_in_sandbox(tainted<T*, T_Sbx> ptr)
{ // Silently swallowing the failure is better here as RAII types may try to // free after sandbox destruction if (sandbox_created.load() != Sandbox_Status::CREATED) { return;
}
/** * @brief Free the memory referenced by a tainted_volatile pointer ref. * * @param ptr_ref Pointer reference to sandbox memory to free.
*/ template<typename T> inlinevoid free_in_sandbox(tainted_volatile<T, T_Sbx>& ptr_ref)
{
tainted<T, T_Sbx> ptr = ptr_ref;
free_in_sandbox(ptr);
}
/** * @brief Free the memory referenced by a tainted_opaque pointer. * * @param ptr_opaque Opaque pointer to sandbox memory to free.
*/ template<typename T> inlinevoid free_in_sandbox(tainted_opaque<T, T_Sbx> ptr_opaque)
{
tainted<T, T_Sbx> ptr = from_opaque(ptr_opaque);
free_in_sandbox(ptr);
}
/** * @brief Check if two pointers are in the same sandbox. * For the null-sandbox, this always returns true.
*/ staticinlinebool is_in_same_sandbox(constvoid* p1, constvoid* p2)
{ const size_t num_args =
detail::func_arg_nums_v<decltype(T_Sbx::impl_is_in_same_sandbox)>; if constexpr (num_args == 2) { return T_Sbx::impl_is_in_same_sandbox(p1, p2);
} else { return T_Sbx::impl_is_in_same_sandbox(p1, p2, find_sandbox_from_example);
}
}
/** * @brief Check if the pointer points to this sandbox's memory. * For the null-sandbox, this always returns true.
*/ inlinebool is_pointer_in_sandbox_memory(constvoid* p)
{ return this->impl_is_pointer_in_sandbox_memory(p);
}
/** * @brief Check if the pointer points to application memory. * For the null-sandbox, this always returns true.
*/ inlinebool is_pointer_in_app_memory(constvoid* p)
{ return this->impl_is_pointer_in_app_memory(p);
}
/** * @brief For internal use only. * Grant access of the passed in buffer in to the sandbox instance. Called by * internal APIs only if the underlying sandbox supports * can_grant_deny_access by including the line * ``` * using can_grant_deny_access = void; * ```
*/ template<typename T> inline tainted<T*, T_Sbx> INTERNAL_grant_access(T* src,
size_t num, bool& success)
{ auto ret = this->impl_grant_access(src, num, success); return tainted<T*, T_Sbx>::internal_factory(ret);
}
/** * @brief For internal use only. * Grant access of the passed in buffer in to the sandbox instance. Called by * internal APIs only if the underlying sandbox supports * can_grant_deny_access by including the line * ``` * using can_grant_deny_access = void; * ```
*/ template<typename T> inline T* INTERNAL_deny_access(tainted<T*, T_Sbx> src,
size_t num, bool& success)
{ auto ret =
this->impl_deny_access(src.INTERNAL_unverified_safe(), num, success); return ret;
}
// this is an internal function invoked from macros, so it has be public template<typename T, typename... T_Args> inlineauto INTERNAL_invoke_with_func_name(constchar* func_name,
T_Args&&... params)
{ return INTERNAL_invoke_with_func_ptr<T, T_Args...>(
func_name, lookup_symbol(func_name), std::forward<T_Args>(params)...);
}
// this is an internal function invoked from macros, so it has be public // Explicitly don't use inline on this, as this adds a lot of instructions // prior to function call. What's more, by not inlining, different function // calls with the same signature can share the same code segments for // sandboxed function execution in the binary template<typename T, typename... T_Args> auto INTERNAL_invoke_with_func_ptr(constchar* func_name, void* func_ptr,
T_Args&&... params)
{ // unused in some paths
RLBOX_UNUSED(func_name); #ifdef RLBOX_MEASURE_TRANSITION_TIMES auto enter_time = high_resolution_clock::now(); auto on_exit = rlbox::detail::make_scope_exit([&] { auto exit_time = high_resolution_clock::now();
int64_t ns = duration_cast<nanoseconds>(exit_time - enter_time).count();
transition_times.push_back(rlbox_transition_timing{
rlbox_transition::INVOKE, func_name, func_ptr, ns });
}); #endif #ifdef RLBOX_TRANSITION_ACTION_IN
RLBOX_TRANSITION_ACTION_IN(
rlbox_transition::INVOKE, func_name, func_ptr, transition_state); #endif #ifdef RLBOX_TRANSITION_ACTION_OUT auto on_exit_transition = rlbox::detail::make_scope_exit([&] {
RLBOX_TRANSITION_ACTION_OUT(
rlbox_transition::INVOKE, func_name, func_ptr, transition_state);
}); #endif
(check_invoke_param_type_is_ok<T_Args>(), ...);
static_assert(
rlbox::detail::polyfill::is_invocable_v<
T,
detail::rlbox_remove_wrapper_t<std::remove_reference_t<T_Args>>...>, "Mismatched arguments types for function");
using T_Result = rlbox::detail::polyfill::invoke_result_t<
T,
detail::rlbox_remove_wrapper_t<std::remove_reference_t<T_Args>>...>;
using T_Converted =
std::remove_pointer_t<convert_fn_ptr_to_sandbox_equivalent_t<T*>>;
// Useful in the porting stage to temporarily allow non tainted pointers to go // through. This will only ever work in the rlbox_noop_sandbox. Any sandbox // that actually enforces isolation will crash here. template<typename T2>
tainted<T2, T_Sbx> UNSAFE_accept_pointer(T2 ptr)
{
static_assert(std::is_pointer_v<T2>, "UNSAFE_accept_pointer expects a pointer param");
tainted<T2, T_Sbx> ret;
ret.assign_raw_pointer(*this, ptr); return ret;
}
template<typename T_Ret, typename... T_Args> using T_Cb_no_wrap = detail::rlbox_remove_wrapper_t<T_Ret>(
detail::rlbox_remove_wrapper_t<T_Args>...);
template<typename T_Ret>
sandbox_callback<T_Cb_no_wrap<T_Ret>*, T_Sbx> register_callback(T_Ret (*)())
{
rlbox_detail_static_fail_because(
detail::true_v<T_Ret>, "Modify the callback to change the first parameter to a sandbox. " "For instance if a callback has type\n\n" "int foo() {...}\n\n" "Change this to \n\n" "tainted foo(rlbox_sandbox& sandbox) {...}\n");
// this is never executed, but we need it for the function to type-check
std::abort();
}
/** * @brief Expose a callback function to the sandboxed code. * * @param func_ptr The callback to expose. * * @tparam T_RL Sandbox reference type (first argument). * @tparam T_Ret Return type of callback. Must be tainted or void. * @tparam T_Args Types of remaining callback arguments. Must be tainted. * * @return Wrapped callback function pointer that can be passed to the * sandbox.
*/ template<typename T_RL, typename T_Ret, typename... T_Args>
sandbox_callback<T_Cb_no_wrap<T_Ret, T_Args...>*, T_Sbx> register_callback(
T_Ret (*func_ptr)(T_RL, T_Args...))
{ // Some branches don't use the param
RLBOX_UNUSED(func_ptr);
if_constexpr_named(cond1, !std::is_same_v<T_RL, rlbox_sandbox<T_Sbx>&>)
{
rlbox_detail_static_fail_because(
cond1, "Modify the callback to change the first parameter to a sandbox. " "For instance if a callback has type\n\n" "int foo(int a, int b) {...}\n\n" "Change this to \n\n" "tainted foo(rlbox_sandbox& sandbox, " "tainted a, tainted b) {...}\n");
} else if_constexpr_named(
cond2, !(detail::rlbox_is_tainted_or_opaque_v<T_Args> && ...))
{
rlbox_detail_static_fail_because(
cond2, "Change all arguments to the callback have to be tainted or " "tainted_opaque. " "For instance if a callback has type\n\n" "int foo(int a, int b) {...}\n\n" "Change this to \n\n" "tainted foo(rlbox_sandbox& sandbox, " "tainted a, tainted b) {...}\n");
} else if_constexpr_named(
cond3, (std::is_array_v<detail::rlbox_remove_wrapper_t<T_Args>> || ...))
{
rlbox_detail_static_fail_because(
cond3, "Change all static array arguments to the callback to be pointers. " "For instance if a callback has type\n\n" "int foo(int a[4]) {...}\n\n" "Change this to \n\n" "tainted foo(rlbox_sandbox& sandbox, " "tainted a) {...}\n");
} else if_constexpr_named(
cond4,
!(std::is_void_v<T_Ret> || detail::rlbox_is_tainted_or_opaque_v<T_Ret>))
{
rlbox_detail_static_fail_because(
cond4, "Change the callback return type to be tainted or tainted_opaque if it " "is not void. " "For instance if a callback has type\n\n" "int foo(int a, int b) {...}\n\n" "Change this to \n\n" "tainted foo(rlbox_sandbox& sandbox, " "tainted a, tainted b) {...}\n");
} else
{
detail::dynamic_check(
sandbox_created.load() == Sandbox_Status::CREATED, "register_callback called without sandbox creation");
// Need unique key for each callback we register - just use the func addr void* unique_key = reinterpret_cast<void*>(func_ptr);
// Make sure that the user hasn't previously registered this function... // If they have, we would returning 2 owning types (sandbox_callback) to // the same callback which would be bad
{
std::lock_guard<std::mutex> lock(callback_lock); bool exists =
std::find(callback_keys.begin(), callback_keys.end(), unique_key) !=
callback_keys.end();
detail::dynamic_check(
!exists, "You have previously already registered this callback.");
callback_keys.push_back(unique_key);
}
auto callback_interceptor =
sandbox_callback_interceptor<detail::rlbox_remove_wrapper_t<T_Ret>,
detail::rlbox_remove_wrapper_t<T_Args>...>;
auto ret = sandbox_callback<T_Cb_no_wrap<T_Ret, T_Args...>*, T_Sbx>( this,
tainted_func_ptr,
callback_interceptor,
callback_trampoline,
unique_key); return ret;
}
}
// this is an internal function invoked from macros, so it has be public template<typename T> inline tainted<T*, T_Sbx> INTERNAL_get_sandbox_function_name( constchar* func_name)
{ return INTERNAL_get_sandbox_function_ptr<T>(
internal_lookup_symbol(func_name));
}
// this is an internal function invoked from macros, so it has be public template<typename T> inline tainted<T*, T_Sbx> INTERNAL_get_sandbox_function_ptr(void* func_ptr)
{ return tainted<T*, T_Sbx>::internal_factory(reinterpret_cast<T*>(func_ptr));
}
/** * @brief Create a "fake" pointer referring to a location in the application * memory * * @param ptr The pointer to refer to * * @return The app_pointer object that refers to this location.
*/ template<typename T>
app_pointer<T*, T_Sbx> get_app_pointer(T* ptr)
{ auto max_ptr = (typename T_Sbx::T_PointerType)(get_total_memory() - 1); auto idx = app_ptr_map.get_app_pointer_idx((void*)ptr, max_ptr); auto idx_as_ptr = this->template impl_get_unsandboxed_pointer<T>(idx); // Right now we simply assume that any integer can be converted to a valid // pointer in the sandbox This may not be true for some sandboxing mechanism // plugins in the future In this case, we will have to come up with // something more clever to construct indexes that look like valid pointers // Add a check for now to make sure things work fine
detail::dynamic_check(is_pointer_in_sandbox_memory(idx_as_ptr), "App pointers are not currently supported for this " "rlbox sandbox plugin. Please file a bug."); auto ret = app_pointer<T*, T_Sbx>(
&app_ptr_map, idx, reinterpret_cast<T*>(idx_as_ptr)); return ret;
}
/** * @brief The mirror of get_app_pointer. Take a tainted pointer which is * actually an app_pointer, and get the application location being pointed to * * @param tainted_ptr The tainted pointer that is actually an app_pointer * * @return The original location being referred to by the app_ptr
*/ template<typename T>
T* lookup_app_ptr(tainted<T*, T_Sbx> tainted_ptr)
{ auto idx = tainted_ptr.get_raw_sandbox_value(*this); void* ret = app_ptr_map.lookup_index(idx); returnreinterpret_cast<T*>(ret);
}
#ifdef RLBOX_MEASURE_TRANSITION_TIMES inline std::vector<rlbox_transition_timing>&
process_and_get_transition_times()
{ return transition_times;
} inline int64_t get_total_ns_time_in_sandbox_and_transitions()
{
int64_t ret = 0; for (auto& transition_time : transition_times) { if (transition_time.invoke == rlbox_transition::INVOKE) {
ret += transition_time.time;
} else {
ret -= transition_time.time;
}
} return ret;
} inlinevoid clear_transition_times() { transition_times.clear(); } #endif
};
#ifdefined(__clang__) # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments" #elifdefined(__GNUC__) || defined(__GNUG__) // Can't turn off the variadic macro warning emitted from -pedantic so use a // hack to stop GCC emitting warnings for the reminder of this file # pragma GCC system_header #elifdefined(_MSC_VER) // Doesn't seem to emit the warning #else // Don't know the compiler... just let it go through #endif
/** * @def invoke_sandbox_function * @brief Call sandbox function. * * @param func_name The sandboxed library function to call. * @param ... Arguments to function should be simple or tainted values. * @return Tainted value or void.
*/ #ifdef RLBOX_USE_STATIC_CALLS
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.