Bug 1737701 - Update rlbox for fallible sandbox, static linking support r=glandium

Differential Revision: https://phabricator.services.mozilla.com/D129455
This commit is contained in:
shravanrn@gmail.com 2021-10-26 02:55:02 +00:00
parent e58ea79f59
commit 321dd6bde0
13 changed files with 641 additions and 139 deletions

View File

@ -1,7 +1,7 @@
This directory contains the rlbox source from the upstream repo:
https://github.com/PLSysSec/rlbox_sandboxing_api/
Current version: [commit fc796e549b3a48e89b9a8db28011dcad06494ba3]
Current version: [commit 71222a234ceae75ff70e09d0855ebeb3b4c961a7]
UPDATING:

View File

@ -655,7 +655,7 @@ public:
* @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[]>)``
* @tparam T_Func the type of the verifier either ``T_Ret(*)(unique_ptr<char[]>)`` or ``T_Ret(*)(std::string)``
* @return Whatever the verifier function returns.
*/
template<typename T_Func>
@ -667,23 +667,53 @@ public:
static_assert(std::is_same_v<char, T_CopyAndVerifyRangeEl>,
"copy_and_verify_string only allows char*");
using T_VerifParam = detail::func_first_arg_t<T_Func>;
auto start = impl().get_raw_value();
if (start == nullptr) {
return verifier(nullptr);
if_constexpr_named(cond1, std::is_same_v<T_VerifParam, std::unique_ptr<char[]>> || std::is_same_v<T_VerifParam, std::unique_ptr<const char[]>>) {
if (start == nullptr) {
return verifier(nullptr);
}
// it is safe to run strlen on a tainted<string> as worst case, the string
// does not have a null and we try to copy all the memory out of the sandbox
// however, copy_and_verify_range ensures that we never copy memory outsider
// the range
auto str_len = std::strlen(start) + 1;
std::unique_ptr<T_CopyAndVerifyRangeEl[]> target =
copy_and_verify_range_helper(str_len);
// ensure the string has a trailing null
target[str_len - 1] = '\0';
return verifier(std::move(target));
} else if_constexpr_named (cond2, std::is_same_v<T_VerifParam, std::string>) {
if (start == nullptr) {
std::string param = "";
return verifier(param);
}
// it is safe to run strlen on a tainted<string> as worst case, the string
// does not have a null and we try to copy all the memory out of the sandbox
// however, copy_and_verify_range ensures that we never copy memory outsider
// the range
auto str_len = std::strlen(start) + 1;
const char* checked_start = (const char*) verify_range_helper(str_len);
if (checked_start == nullptr) {
std::string param = "";
return verifier(param);
}
std::string copy(checked_start, str_len - 1);
return verifier(std::move(copy));
} else {
constexpr bool unknownCase = !(cond1 || cond2);
rlbox_detail_static_fail_because(
unknownCase,
"copy_and_verify_string verifier parameter should either be unique_ptr<char[]>, unique_ptr<const char[]> or std::string"
);
}
// 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));
}
/**
@ -851,10 +881,10 @@ class tainted : public tainted_base_impl<tainted, T, T_Sbx>
// Classes recieve their own specialization
static_assert(
!std::is_class_v<T>,
"Missing specialization for class T. This error occurs for one "
"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.\n"
"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");
@ -1105,10 +1135,10 @@ class tainted_volatile : public tainted_base_impl<tainted_volatile, T, T_Sbx>
// Classes recieve their own specialization
static_assert(
!std::is_class_v<T>,
"Missing specialization for class T. This error occurs for one "
"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.\n"
"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");
@ -1152,6 +1182,7 @@ private:
inline std::remove_cv_t<T_SandboxedType> get_raw_sandbox_value(
rlbox_sandbox<T_Sbx>& sandbox) const noexcept
{
RLBOX_UNUSED(sandbox);
return data;
};
@ -1169,6 +1200,7 @@ private:
inline std::remove_cv_t<T_SandboxedType> get_raw_sandbox_value(
rlbox_sandbox<T_Sbx>& sandbox) noexcept
{
RLBOX_UNUSED(sandbox);
rlbox_detail_forward_to_const(get_raw_sandbox_value,
std::remove_cv_t<T_SandboxedType>);
};
@ -1261,7 +1293,7 @@ public:
// 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);
get_sandbox_value_ref() = (T_Cast)func;
}
}
else if_constexpr_named(

View File

@ -32,13 +32,13 @@ private:
for (T_PointerTypeUnsigned i = counter; i < max_val; i++) {
if (pointer_map.find(i) == pointer_map.end()) {
counter = i + 1;
return reinterpret_cast<T_PointerType>(i);
return (T_PointerType)i;
}
}
for (T_PointerTypeUnsigned i = min_val; i < counter; i++) {
if (pointer_map.find(i) == pointer_map.end()) {
counter = i + 1;
return reinterpret_cast<T_PointerType>(i);
return (T_PointerType)i;
}
}
detail::dynamic_check(false, "Could not find free app pointer slot");
@ -57,8 +57,7 @@ public:
{
RLBOX_ACQUIRE_UNIQUE_GUARD(lock, map_mutex);
T_PointerType idx = get_unused_index();
T_PointerTypeUnsigned idx_int =
reinterpret_cast<T_PointerTypeUnsigned>(idx);
T_PointerTypeUnsigned idx_int = (T_PointerTypeUnsigned)idx;
pointer_map[idx_int] = ptr;
return idx;
}
@ -66,8 +65,7 @@ public:
void remove_app_ptr(T_PointerType idx)
{
RLBOX_ACQUIRE_UNIQUE_GUARD(lock, map_mutex);
T_PointerTypeUnsigned idx_int =
reinterpret_cast<T_PointerTypeUnsigned>(idx);
T_PointerTypeUnsigned idx_int = (T_PointerTypeUnsigned)idx;
auto it = pointer_map.find(idx_int);
detail::dynamic_check(it != pointer_map.end(),
"Error: removing a non-existing app pointer");
@ -77,8 +75,7 @@ public:
void* lookup_index(T_PointerType idx)
{
RLBOX_ACQUIRE_SHARED_GUARD(lock, map_mutex);
T_PointerTypeUnsigned idx_int =
reinterpret_cast<T_PointerTypeUnsigned>(idx);
T_PointerTypeUnsigned idx_int = (T_PointerTypeUnsigned)idx;
auto it = pointer_map.find(idx_int);
detail::dynamic_check(it != pointer_map.end(),
"Error: looking up a non-existing app pointer");

View File

@ -131,7 +131,7 @@ inline constexpr void convert_type_fundamental_or_array(T_To& to,
is_signed_v<T_To_El> == is_signed_v<T_From_El>) {
// Sanity check - this should definitely be true
static_assert(sizeof(T_From_C) == sizeof(T_To_C));
memcpy(&to, &from, sizeof(T_To_C));
std::memcpy(&to, &from, sizeof(T_To_C));
} else {
for (size_t i = 0; i < std::extent_v<T_To_C>; i++) {
convert_type_fundamental_or_array(to[i], from[i]);

View File

@ -0,0 +1,309 @@
#pragma once
#include <cstdint>
#include <cstdlib>
#include <mutex>
#ifndef RLBOX_USE_CUSTOM_SHARED_LOCK
# include <shared_mutex>
#endif
#include <utility>
#if defined(_WIN32)
// Ensure the min/max macro in the header doesn't collide with functions in
// std::
# ifndef NOMINMAX
# define NOMINMAX
# endif
# include <windows.h>
#else
# include <dlfcn.h>
#endif
#include "rlbox_helpers.hpp"
namespace rlbox {
class rlbox_dylib_sandbox;
struct rlbox_dylib_sandbox_thread_data
{
rlbox_dylib_sandbox* sandbox;
uint32_t last_callback_invoked;
};
#ifdef RLBOX_EMBEDDER_PROVIDES_TLS_STATIC_VARIABLES
rlbox_dylib_sandbox_thread_data* get_rlbox_dylib_sandbox_thread_data();
# define RLBOX_DYLIB_SANDBOX_STATIC_VARIABLES() \
thread_local rlbox::rlbox_dylib_sandbox_thread_data \
rlbox_dylib_sandbox_thread_info{ 0, 0 }; \
namespace rlbox { \
rlbox_dylib_sandbox_thread_data* get_rlbox_dylib_sandbox_thread_data() \
{ \
return &rlbox_dylib_sandbox_thread_info; \
} \
} \
static_assert(true, "Enforce semi-colon")
#endif
/**
* @brief Class that implements the null sandbox. This sandbox doesn't actually
* provide any isolation and only serves as a stepping stone towards migrating
* an application to use the RLBox API.
*/
class rlbox_dylib_sandbox
{
public:
// Stick with the system defaults
using T_LongLongType = long long;
using T_LongType = long;
using T_IntType = int;
using T_PointerType = void*;
using T_ShortType = short;
// no-op sandbox can transfer buffers as there is no sandboxings
// Thus transfer is a noop
using can_grant_deny_access = void;
// if this plugin uses a separate function to lookup internal callbacks
using needs_internal_lookup_symbol = void;
private:
void* sandbox = nullptr;
RLBOX_SHARED_LOCK(callback_mutex);
static inline const uint32_t MAX_CALLBACKS = 64;
void* callback_unique_keys[MAX_CALLBACKS]{ 0 };
void* callbacks[MAX_CALLBACKS]{ 0 };
#ifndef RLBOX_EMBEDDER_PROVIDES_TLS_STATIC_VARIABLES
thread_local static inline rlbox_dylib_sandbox_thread_data thread_data{ 0,
0 };
#endif
template<uint32_t N, typename T_Ret, typename... T_Args>
static T_Ret callback_trampoline(T_Args... params)
{
#ifdef RLBOX_EMBEDDER_PROVIDES_TLS_STATIC_VARIABLES
auto& thread_data = *get_rlbox_dylib_sandbox_thread_data();
#endif
thread_data.last_callback_invoked = N;
using T_Func = T_Ret (*)(T_Args...);
T_Func func;
{
RLBOX_ACQUIRE_SHARED_GUARD(lock, thread_data.sandbox->callback_mutex);
func = reinterpret_cast<T_Func>(thread_data.sandbox->callbacks[N]);
}
// Callbacks are invoked through function pointers, cannot use std::forward
// as we don't have caller context for T_Args, which means they are all
// effectively passed by value
return func(params...);
}
protected:
#if defined(_WIN32)
using path_buf = const LPCWSTR;
#else
using path_buf = const char*;
#endif
inline void impl_create_sandbox(path_buf path)
{
#if defined(_WIN32)
sandbox = (void*)LoadLibraryW(path);
#else
sandbox = dlopen(path, RTLD_LAZY | RTLD_LOCAL);
#endif
if (!sandbox) {
std::string error_msg = "Could not load dynamic library: ";
#if defined(_WIN32)
DWORD errorMessageID = GetLastError();
if (errorMessageID != 0) {
LPSTR messageBuffer = nullptr;
// The api creates the buffer that holds the message
size_t size = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
errorMessageID,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPSTR)&messageBuffer,
0,
NULL);
// Copy the error message into a std::string.
std::string message(messageBuffer, size);
error_msg += message;
LocalFree(messageBuffer);
}
#else
error_msg += dlerror();
#endif
detail::dynamic_check(false, error_msg.c_str());
}
}
inline void impl_destroy_sandbox()
{
#if defined(_WIN32)
FreeLibrary((HMODULE)sandbox);
#else
dlclose(sandbox);
#endif
sandbox = nullptr;
}
template<typename T>
inline void* impl_get_unsandboxed_pointer(T_PointerType p) const
{
return p;
}
template<typename T>
inline T_PointerType impl_get_sandboxed_pointer(const void* p) const
{
return const_cast<T_PointerType>(p);
}
template<typename T>
static inline void* impl_get_unsandboxed_pointer_no_ctx(
T_PointerType p,
const void* /* example_unsandboxed_ptr */,
rlbox_dylib_sandbox* (* // Func ptr
/* param: expensive_sandbox_finder */)(
const void* example_unsandboxed_ptr))
{
return p;
}
template<typename T>
static inline T_PointerType impl_get_sandboxed_pointer_no_ctx(
const void* p,
const void* /* example_unsandboxed_ptr */,
rlbox_dylib_sandbox* (* // Func ptr
/* param: expensive_sandbox_finder */)(
const void* example_unsandboxed_ptr))
{
return const_cast<T_PointerType>(p);
}
inline T_PointerType impl_malloc_in_sandbox(size_t size)
{
void* p = malloc(size);
return p;
}
inline void impl_free_in_sandbox(T_PointerType p) { free(p); }
static inline bool impl_is_in_same_sandbox(const void*, const void*)
{
return true;
}
inline bool impl_is_pointer_in_sandbox_memory(const void*) { return true; }
inline bool impl_is_pointer_in_app_memory(const void*) { return true; }
inline size_t impl_get_total_memory()
{
return std::numeric_limits<size_t>::max();
}
inline void* impl_get_memory_location()
{
// There isn't any sandbox memory for the dylib_sandbox as we just redirect
// to the app. Also, this is mostly used for pointer swizzling or sandbox
// bounds checks which is also not present/not required. So we can just
// return null
return nullptr;
}
void* impl_lookup_symbol(const char* func_name)
{
#if defined(_WIN32)
void* ret = GetProcAddress((HMODULE)sandbox, func_name);
#else
void* ret = dlsym(sandbox, func_name);
#endif
detail::dynamic_check(ret != nullptr, "Symbol not found");
return ret;
}
void* impl_internal_lookup_symbol(const char* func_name)
{
return impl_lookup_symbol(func_name);
}
template<typename T, typename T_Converted, typename... T_Args>
auto impl_invoke_with_func_ptr(T_Converted* func_ptr, T_Args&&... params)
{
#ifdef RLBOX_EMBEDDER_PROVIDES_TLS_STATIC_VARIABLES
auto& thread_data = *get_rlbox_dylib_sandbox_thread_data();
#endif
thread_data.sandbox = this;
return (*func_ptr)(params...);
}
template<typename T_Ret, typename... T_Args>
inline T_PointerType impl_register_callback(void* key, void* callback)
{
RLBOX_ACQUIRE_UNIQUE_GUARD(lock, callback_mutex);
void* chosen_trampoline = nullptr;
// need a compile time for loop as we we need I to be a compile time value
// this is because we are returning the I'th callback trampoline
detail::compile_time_for<MAX_CALLBACKS>([&](auto I) {
if (!chosen_trampoline && callback_unique_keys[I.value] == nullptr) {
callback_unique_keys[I.value] = key;
callbacks[I.value] = callback;
chosen_trampoline = reinterpret_cast<void*>(
callback_trampoline<I.value, T_Ret, T_Args...>);
}
});
return reinterpret_cast<T_PointerType>(chosen_trampoline);
}
static inline std::pair<rlbox_dylib_sandbox*, void*>
impl_get_executed_callback_sandbox_and_key()
{
#ifdef RLBOX_EMBEDDER_PROVIDES_TLS_STATIC_VARIABLES
auto& thread_data = *get_rlbox_dylib_sandbox_thread_data();
#endif
auto sandbox = thread_data.sandbox;
auto callback_num = thread_data.last_callback_invoked;
void* key = sandbox->callback_unique_keys[callback_num];
return std::make_pair(sandbox, key);
}
template<typename T_Ret, typename... T_Args>
inline void impl_unregister_callback(void* key)
{
RLBOX_ACQUIRE_UNIQUE_GUARD(lock, callback_mutex);
for (uint32_t i = 0; i < MAX_CALLBACKS; i++) {
if (callback_unique_keys[i] == key) {
callback_unique_keys[i] = nullptr;
callbacks[i] = nullptr;
break;
}
}
}
template<typename T>
inline T* impl_grant_access(T* src, size_t num, bool& success)
{
RLBOX_UNUSED(num);
success = true;
return src;
}
template<typename T>
inline T* impl_deny_access(T* src, size_t num, bool& success)
{
RLBOX_UNUSED(num);
success = true;
return src;
}
};
}

View File

@ -7,6 +7,9 @@
#include <stdexcept>
#include <type_traits>
#include <utility>
#ifndef RLBOX_USE_CUSTOM_SHARED_LOCK
#include <mutex>
#endif
#include "rlbox_stdlib_polyfill.hpp"
@ -21,8 +24,12 @@ namespace detail {
#if __cpp_exceptions && defined(RLBOX_USE_EXCEPTIONS)
throw std::runtime_error(msg);
#else
std::cerr << msg << std::endl;
std::abort();
#ifdef RLBOX_CUSTOM_ABORT
RLBOX_CUSTOM_ABORT(msg);
#else
std::cerr << msg << std::endl;
std::abort();
#endif
#endif
}
// clang-format on

View File

@ -104,10 +104,9 @@ protected:
static inline void* impl_get_unsandboxed_pointer_no_ctx(
T_PointerType p,
const void* /* example_unsandboxed_ptr */,
rlbox_noop_sandbox* (
* // Func ptr
/* param: expensive_sandbox_finder */)(const void*
example_unsandboxed_ptr))
rlbox_noop_sandbox* (* // Func ptr
/* param: expensive_sandbox_finder */)(
const void* example_unsandboxed_ptr))
{
return p;
}
@ -116,10 +115,9 @@ protected:
static inline T_PointerType impl_get_sandboxed_pointer_no_ctx(
const void* p,
const void* /* example_unsandboxed_ptr */,
rlbox_noop_sandbox* (
* // Func ptr
/* param: expensive_sandbox_finder */)(const void*
example_unsandboxed_ptr))
rlbox_noop_sandbox* (* // Func ptr
/* param: expensive_sandbox_finder */)(
const void* example_unsandboxed_ptr))
{
return const_cast<T_PointerType>(p);
}

View File

@ -281,4 +281,120 @@ public:
}
};
/**
* @brief Tainted boolean value that serves as a "hint" and not a definite
* answer. Comparisons with a tainted_volatile return such hints. They are
* not `tainted<bool>` values because a compromised sandbox can modify
* tainted_volatile data at any time.
*/
class tainted_boolean_hint
{
private:
bool val;
public:
tainted_boolean_hint(bool init)
: val(init)
{}
tainted_boolean_hint(const tainted_boolean_hint&) = default;
inline tainted_boolean_hint& operator=(bool rhs)
{
val = rhs;
return *this;
}
inline tainted_boolean_hint operator!() { return tainted_boolean_hint(!val); }
template<size_t N>
inline bool unverified_safe_because(const char (&reason)[N]) const
{
(void)reason; /* unused */
return val;
}
inline bool UNSAFE_unverified() const { return val; }
inline bool UNSAFE_unverified() { return val; }
inline auto INTERNAL_unverified_safe() { return UNSAFE_unverified(); }
inline auto INTERNAL_unverified_safe() const { return UNSAFE_unverified(); }
// Add a template parameter to make sure the assert only fires when called
template<typename T=void>
inline bool copy_and_verify(...) const
{
rlbox_detail_static_fail_because(
detail::true_v<T>,
"You can't call copy_and_verify on this value, as this is a result of a "
"comparison with memory accessible by the sandbox. \n"
"The sandbox could unexpectedly change the value leading to "
"time-of-check-time-of-use attacks. \n"
"You can avoid this by making a local copy of the data."
"For example, if your original code, looked like \n"
"if ((tainted_ptr->member == 5).copy_and_verify(...)) { ... } \n\n"
"Change this to \n\n"
"tainted<int> val = tainted_ptr->member\n"
"if ((val == 5).copy_and_verify(...)) { ... } \n\n"
"tainted<int, T_Sbx> foo(rlbox_sandbox<T_Sbx>& sandbox) {...} \n\n"
"Alternately, if you are sure your code is safe you can use the "
"unverified_safe_because API to remove tainting\n");
// this is never executed, but we need it for the function to type-check
return false;
}
};
/**
* @brief Tainted integer value that serves as a "hint" and not a definite
* answer. Comparisons with a tainted_volatile return such hints. They are
* not `tainted<int>` values because a compromised sandbox can modify
* tainted_volatile data at any time.
*/
class tainted_int_hint
{
private:
int val;
public:
tainted_int_hint(int init)
: val(init)
{}
tainted_int_hint(const tainted_int_hint&) = default;
inline tainted_int_hint& operator=(int rhs)
{
val = rhs;
return *this;
}
inline tainted_boolean_hint operator!() { return tainted_boolean_hint(!val); }
template<size_t N>
inline int unverified_safe_because(const char (&reason)[N]) const
{
(void)reason; /* unused */
return val;
}
inline int UNSAFE_unverified() const { return val; }
inline int UNSAFE_unverified() { return val; }
inline auto INTERNAL_unverified_safe() { return UNSAFE_unverified(); }
inline auto INTERNAL_unverified_safe() const { return UNSAFE_unverified(); }
// Add a template parameter to make sure the assert only fires when called
template<typename T=void>
inline int copy_and_verify(...) const
{
rlbox_detail_static_fail_because(
detail::true_v<T>,
"You can't call copy_and_verify on this value, as this is a result of a "
"comparison with memory accessible by the sandbox. \n"
"The sandbox could unexpectedly change the value leading to "
"time-of-check-time-of-use attacks. \n"
"You can avoid this by making a local copy of the data."
"For example, if your original code, looked like \n"
"if ((tainted_ptr->member == 5).copy_and_verify(...)) { ... } \n\n"
"Change this to \n\n"
"tainted<int> val = tainted_ptr->member\n"
"if ((val == 5).copy_and_verify(...)) { ... } \n\n"
"tainted<int, T_Sbx> foo(rlbox_sandbox<T_Sbx>& sandbox) {...} \n\n"
"Alternately, if you are sure your code is safe you can use the "
"unverified_safe_because API to remove tainting\n");
// this is never executed, but we need it for the function to type-check
return 0;
}
};
}

View File

@ -174,7 +174,7 @@ private:
constexpr auto unknownCase = !(cond1 || cond2);
rlbox_detail_static_fail_because(
unknownCase,
"Arguments to a sandbox function call should be primitives or wrapped "
"Arguments to a sandbox function call should be primitives or wrapped "
"types like tainted, callbacks etc.");
}
}
@ -200,7 +200,10 @@ private:
tainted<T_NoRef, T_Sbx> ret = param;
return ret.UNSAFE_sandboxed(*this);
} else {
rlbox_detail_static_fail_because(detail::true_v<T_NoRef>, "Unknown case");
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.");
}
}
@ -327,13 +330,14 @@ private:
}
}
detail::dynamic_check(
false,
"Internal error: Could not find the sandbox associated with example "
"pointer. Please file a bug.");
return nullptr;
}
template<typename... T_Args>
static auto impl_create_sandbox_helper(rlbox_sandbox<T_Sbx>* this_ptr, T_Args... args) {
return this_ptr->impl_create_sandbox(std::forward<T_Args>(args)...);
}
public:
/**
* @brief Unused member that allows the calling code to save data in a
@ -362,7 +366,7 @@ public:
* implementation. For the null sandbox, no arguments are necessary.
*/
template<typename... T_Args>
inline auto create_sandbox(T_Args... args)
inline bool create_sandbox(T_Args... args)
{
#ifdef RLBOX_MEASURE_TRANSITION_TIMES
// Warm up the timer. The first call is always slow (at least on the test
@ -380,15 +384,27 @@ public:
"create_sandbox called when sandbox already created/is being "
"created concurrently");
return detail::return_first_result(
[&]() {
return this->impl_create_sandbox(std::forward<T_Args>(args)...);
},
[&]() {
sandbox_created.store(Sandbox_Status::CREATED);
RLBOX_ACQUIRE_UNIQUE_GUARD(lock, sandbox_list_lock);
sandbox_list.push_back(this);
});
using T_Result = rlbox::detail::polyfill::invoke_result_t<decltype(impl_create_sandbox_helper<T_Args...>), decltype(this), T_Args...>;
bool created = true;
if constexpr (std::is_same_v<T_Result, void>) {
this->impl_create_sandbox(std::forward<T_Args>(args)...);
} else if constexpr (std::is_same_v<T_Result, bool>) {
created = this->impl_create_sandbox(std::forward<T_Args>(args)...);
} else {
rlbox_detail_static_fail_because(
(!std::is_same_v<T_Result, void> && !std::is_same_v<T_Result, bool>),
"Expected impl_create_sandbox to return void or a boolean"
);
}
if (created) {
sandbox_created.store(Sandbox_Status::CREATED);
RLBOX_ACQUIRE_UNIQUE_GUARD(lock, sandbox_list_lock);
sandbox_list.push_back(this);
}
return created;
}
/**
@ -587,7 +603,13 @@ public:
*/
static inline bool is_in_same_sandbox(const void* p1, const void* p2)
{
return T_Sbx::impl_is_in_same_sandbox(p1, 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);
}
}
/**
@ -673,6 +695,28 @@ public:
return func_ptr;
}
void* internal_lookup_symbol(const char* func_name)
{
{
RLBOX_ACQUIRE_SHARED_GUARD(lock, func_ptr_cache_lock);
auto func_ptr_ref = func_ptr_map.find(func_name);
if (func_ptr_ref != func_ptr_map.end()) {
return func_ptr_ref->second;
}
}
void* func_ptr = 0;
if constexpr(rlbox::detail::has_member_using_needs_internal_lookup_symbol_v<T_Sbx>) {
func_ptr = this->impl_internal_lookup_symbol(func_name);
} else {
func_ptr = this->impl_lookup_symbol(func_name);
}
RLBOX_ACQUIRE_UNIQUE_GUARD(lock, func_ptr_cache_lock);
func_ptr_map[func_name] = func_ptr;
return func_ptr;
}
// this is an internal function invoked from macros, so it has be public
template<typename T, typename... T_Args>
inline auto INTERNAL_invoke_with_func_name(const char* func_name,
@ -772,7 +816,7 @@ public:
{
rlbox_detail_static_fail_because(
detail::true_v<T_Ret>,
"Modify the callback to change the first parameter to a sandbox."
"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"
@ -805,11 +849,11 @@ public:
{
rlbox_detail_static_fail_because(
cond1,
"Modify the callback to change the first parameter to a sandbox."
"Modify the callback to change the first parameter to a sandbox. "
"For instance if a callback has type\n\n"
"int foo(int a, int b) {...}\n\n"
"Change this to \n\n"
"tainted<int, T_Sbx> foo(rlbox_sandbox<T_Sbx>& sandbox,"
"tainted<int, T_Sbx> foo(rlbox_sandbox<T_Sbx>& sandbox, "
"tainted<int, T_Sbx> a, tainted<int, T_Sbx> b) {...}\n");
}
else if_constexpr_named(
@ -818,11 +862,11 @@ public:
rlbox_detail_static_fail_because(
cond2,
"Change all arguments to the callback have to be tainted or "
"tainted_opaque."
"tainted_opaque. "
"For instance if a callback has type\n\n"
"int foo(int a, int b) {...}\n\n"
"Change this to \n\n"
"tainted<int, T_Sbx> foo(rlbox_sandbox<T_Sbx>& sandbox,"
"tainted<int, T_Sbx> foo(rlbox_sandbox<T_Sbx>& sandbox, "
"tainted<int, T_Sbx> a, tainted<int, T_Sbx> b) {...}\n");
}
else if_constexpr_named(
@ -830,11 +874,11 @@ public:
{
rlbox_detail_static_fail_because(
cond3,
"Change all static array arguments to the callback to be pointers."
"Change all static array arguments to the callback to be pointers. "
"For instance if a callback has type\n\n"
"int foo(int a[4]) {...}\n\n"
"Change this to \n\n"
"tainted<int, T_Sbx> foo(rlbox_sandbox<T_Sbx>& sandbox,"
"tainted<int, T_Sbx> foo(rlbox_sandbox<T_Sbx>& sandbox, "
"tainted<int*, T_Sbx> a) {...}\n");
}
else if_constexpr_named(
@ -844,11 +888,11 @@ public:
rlbox_detail_static_fail_because(
cond4,
"Change the callback return type to be tainted or tainted_opaque if it "
"is not void."
"is not void. "
"For instance if a callback has type\n\n"
"int foo(int a, int b) {...}\n\n"
"Change this to \n\n"
"tainted<int, T_Sbx> foo(rlbox_sandbox<T_Sbx>& sandbox,"
"tainted<int, T_Sbx> foo(rlbox_sandbox<T_Sbx>& sandbox, "
"tainted<int, T_Sbx> a, tainted<int, T_Sbx> b) {...}\n");
}
else
@ -905,7 +949,7 @@ public:
inline tainted<T*, T_Sbx> INTERNAL_get_sandbox_function_name(
const char* func_name)
{
return INTERNAL_get_sandbox_function_ptr<T>(lookup_symbol(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

View File

@ -130,7 +130,12 @@ inline T_Wrap<T_Rhs*, T_Sbx> memset(rlbox_sandbox<T_Sbx>& sandbox,
}
/**
* @brief Copy to sandbox memory area.
* @brief Copy to sandbox memory area. Note that memcpy is meant to be called on
* byte arrays does not adjust data according to ABI differences. If the
* programmer does accidentally call memcpy on buffers that needs ABI
* adjustment, this may cause compatibility issues, but will not cause a
* security issue as the destination is always a tainted or tainted_volatile
* pointer
*/
template<typename T_Sbx,
typename T_Rhs,
@ -220,13 +225,18 @@ tainted<T*, T_Sbx> copy_memory_or_grant_access(rlbox_sandbox<T_Sbx>& sandbox,
bool free_source_on_copy,
bool& copied)
{
// Malloc in sandbox takes a uint32_t as the parameter, need a bounds check
detail::dynamic_check(num <= std::numeric_limits<uint32_t>::max(),
"Granting access too large a region");
uint32_t num_trunc = num;
// sandbox can grant access if it includes the following line
// using can_grant_deny_access = void;
if constexpr (detail::has_member_using_can_grant_deny_access_v<T_Sbx>) {
detail::check_range_doesnt_cross_app_sbx_boundary<T_Sbx>(src, num);
detail::check_range_doesnt_cross_app_sbx_boundary<T_Sbx>(src, num_trunc);
bool success;
auto ret = sandbox.INTERNAL_grant_access(src, num, success);
auto ret = sandbox.INTERNAL_grant_access(src, num_trunc, success);
if (success) {
copied = false;
return ret;
@ -235,8 +245,8 @@ tainted<T*, T_Sbx> copy_memory_or_grant_access(rlbox_sandbox<T_Sbx>& sandbox,
using T_nocv = std::remove_cv_t<T>;
tainted<T_nocv*, T_Sbx> copy =
sandbox.template malloc_in_sandbox<T_nocv>(num);
rlbox::memcpy(sandbox, copy, src, num);
sandbox.template malloc_in_sandbox<T_nocv>(num_trunc);
rlbox::memcpy(sandbox, copy, src, num_trunc);
if (free_source_on_copy) {
free(const_cast<void*>(reinterpret_cast<const void*>(src)));
}

View File

@ -27,6 +27,40 @@ using valid_return_t =
template<typename T>
using valid_param_t = std::conditional_t<std::is_void_v<T>, void*, T>;
namespace func_first_arg_detail {
template<typename Ret, typename Arg, typename... Rest>
Arg func_first_arg_t_helper(Ret(*) (Arg, Rest...));
template<typename Ret, typename F, typename Arg, typename... Rest>
Arg func_first_arg_t_helper(Ret(F::*) (Arg, Rest...));
template<typename Ret, typename F, typename Arg, typename... Rest>
Arg func_first_arg_t_helper(Ret(F::*) (Arg, Rest...) const);
template <typename F>
decltype(func_first_arg_t_helper(&F::operator())) first_argument_helper(F);
}
template <typename T>
using func_first_arg_t = decltype(func_first_arg_detail::first_argument_helper(std::declval<T>()));
namespace func_arg_nums_v_detail {
template<typename T_Ret, typename... T_Args>
constexpr size_t helper_two(T_Ret (*)(T_Args...))
{
return sizeof...(T_Args);
}
template<typename T_Func>
constexpr size_t helper()
{
constexpr T_Func* ptr = nullptr;
return helper_two(ptr);
}
}
template<typename T_Func>
constexpr size_t func_arg_nums_v = func_arg_nums_v_detail::helper<T_Func>();
template<typename T>
using valid_array_el_t =
std::conditional_t<std::is_void_v<T> || std::is_function_v<T>, int, T>;

View File

@ -24,73 +24,9 @@ class tainted;
template<typename T, typename T_Sbx>
class tainted_volatile;
/**
* @brief Tainted boolean value that serves as a "hint" and not a definite
* answer. Comparisons with a tainted_volatile return such hints. They are
* not `tainted<bool>` values because a compromised sandbox can modify
* tainted_volatile data at any time.
*/
class tainted_boolean_hint
{
private:
bool val;
class tainted_boolean_hint;
public:
tainted_boolean_hint(bool init)
: val(init)
{}
tainted_boolean_hint(const tainted_boolean_hint&) = default;
inline tainted_boolean_hint& operator=(bool rhs)
{
val = rhs;
return *this;
}
inline tainted_boolean_hint operator!() { return tainted_boolean_hint(!val); }
template<size_t N>
inline bool unverified_safe_because(const char (&reason)[N]) const
{
(void)reason; /* unused */
return val;
}
inline bool UNSAFE_unverified() const { return val; }
inline bool UNSAFE_unverified() { return val; }
inline auto INTERNAL_unverified_safe() { return UNSAFE_unverified(); }
inline auto INTERNAL_unverified_safe() const { return UNSAFE_unverified(); }
};
/**
* @brief Tainted integer value that serves as a "hint" and not a definite
* answer. Comparisons with a tainted_volatile return such hints. They are
* not `tainted<int>` values because a compromised sandbox can modify
* tainted_volatile data at any time.
*/
class tainted_int_hint
{
private:
int val;
public:
tainted_int_hint(int init)
: val(init)
{}
tainted_int_hint(const tainted_int_hint&) = default;
inline tainted_int_hint& operator=(int rhs)
{
val = rhs;
return *this;
}
inline tainted_boolean_hint operator!() { return tainted_boolean_hint(!val); }
template<size_t N>
inline int unverified_safe_because(const char (&reason)[N]) const
{
(void)reason; /* unused */
return val;
}
inline int UNSAFE_unverified() const { return val; }
inline int UNSAFE_unverified() { return val; }
inline auto INTERNAL_unverified_safe() { return UNSAFE_unverified(); }
inline auto INTERNAL_unverified_safe() const { return UNSAFE_unverified(); }
};
class tainted_int_hint;
template<typename T_Sbx>
class rlbox_sandbox;
@ -102,4 +38,6 @@ template<typename T, typename T_Sbx>
class app_pointer;
class rlbox_noop_sandbox;
class rlbox_dylib_sandbox;
}

View File

@ -151,4 +151,21 @@ constexpr bool has_member_using_can_grant_deny_access_v =
detail_has_member_using_can_grant_deny_access::
has_member_using_can_grant_deny_access<T>::value;
namespace detail_has_member_using_needs_internal_lookup_symbol {
template<class T, class Enable = void>
struct has_member_using_needs_internal_lookup_symbol : std::false_type
{};
template<class T>
struct has_member_using_needs_internal_lookup_symbol<
T,
std::void_t<typename T::needs_internal_lookup_symbol>> : std::true_type
{};
}
template<class T>
constexpr bool has_member_using_needs_internal_lookup_symbol_v =
detail_has_member_using_needs_internal_lookup_symbol::
has_member_using_needs_internal_lookup_symbol<T>::value;
}