summaryrefslogtreecommitdiffstats
path: root/third_party/rust/rlbox_lucet_sandbox/include
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
commit2aa4a82499d4becd2284cdb482213d541b8804dd (patch)
treeb80bf8bf13c3766139fbacc530efd0dd9d54394c /third_party/rust/rlbox_lucet_sandbox/include
parentInitial commit. (diff)
downloadfirefox-upstream.tar.xz
firefox-upstream.zip
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/rlbox_lucet_sandbox/include')
-rw-r--r--third_party/rust/rlbox_lucet_sandbox/include/lucet_sandbox.h71
-rw-r--r--third_party/rust/rlbox_lucet_sandbox/include/rlbox_lucet_sandbox.hpp881
2 files changed, 952 insertions, 0 deletions
diff --git a/third_party/rust/rlbox_lucet_sandbox/include/lucet_sandbox.h b/third_party/rust/rlbox_lucet_sandbox/include/lucet_sandbox.h
new file mode 100644
index 0000000000..822a862d0f
--- /dev/null
+++ b/third_party/rust/rlbox_lucet_sandbox/include/lucet_sandbox.h
@@ -0,0 +1,71 @@
+#ifndef LUCET_SANDBOX_H
+#define LUCET_SANDBOX_H
+
+#include <stdint.h>
+#include <stdlib.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct LucetSandboxInstance;
+typedef struct LucetSandboxInstance LucetSandboxInstance;
+
+enum LucetValueType {
+ LucetValueType_I32,
+ LucetValueType_I64,
+ LucetValueType_F32,
+ LucetValueType_F64,
+ LucetValueType_Void
+};
+
+typedef struct {
+ enum LucetValueType val_type;
+ union {
+ uint32_t u32;
+ uint64_t u64;
+ float f32;
+ double f64;
+ };
+} LucetValue;
+
+typedef struct {
+ enum LucetValueType ret;
+ uint32_t parameter_cnt;
+ LucetValueType *parameters;
+} LucetFunctionSignature;
+
+typedef struct {
+ uint64_t ty;
+ uint64_t rf;
+} LucetFunctionTableElement;
+
+typedef struct {
+ LucetFunctionTableElement *data;
+ size_t length;
+} LucetFunctionTable;
+
+void lucet_ensure_linked();
+LucetSandboxInstance *lucet_load_module(const char *lucet_module_path,
+ bool allow_stdio);
+void lucet_drop_module(LucetSandboxInstance *inst);
+
+void* lucet_lookup_function(LucetSandboxInstance *inst,
+ const char *fn_name);
+void lucet_set_curr_instance(LucetSandboxInstance *inst);
+void lucet_clear_curr_instance(LucetSandboxInstance *inst);
+
+uintptr_t lucet_get_reserved_callback_slot_val(void *inst,
+ uint32_t slot_number);
+LucetFunctionTable lucet_get_function_pointer_table(void *inst);
+int32_t lucet_get_function_type_index(void *inst, LucetFunctionSignature csig);
+
+void *lucet_get_heap_base(LucetSandboxInstance *inst);
+size_t lucet_get_heap_size(LucetSandboxInstance *inst);
+uint32_t lucet_get_export_function_id(void *inst, void *unsandboxed_ptr);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif \ No newline at end of file
diff --git a/third_party/rust/rlbox_lucet_sandbox/include/rlbox_lucet_sandbox.hpp b/third_party/rust/rlbox_lucet_sandbox/include/rlbox_lucet_sandbox.hpp
new file mode 100644
index 0000000000..43d6e3715e
--- /dev/null
+++ b/third_party/rust/rlbox_lucet_sandbox/include/rlbox_lucet_sandbox.hpp
@@ -0,0 +1,881 @@
+#pragma once
+
+#include "lucet_sandbox.h"
+
+#include <cstdint>
+#include <iostream>
+#include <limits>
+#include <map>
+#include <memory>
+#include <mutex>
+// RLBox allows applications to provide a custom shared lock implementation
+#ifndef RLBOX_USE_CUSTOM_SHARED_LOCK
+# include <shared_mutex>
+#endif
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+#define RLBOX_LUCET_UNUSED(...) (void)__VA_ARGS__
+
+// Use the same convention as rlbox to allow applications to customize the
+// shared lock
+#ifndef RLBOX_USE_CUSTOM_SHARED_LOCK
+# define RLBOX_SHARED_LOCK(name) std::shared_timed_mutex name
+# define RLBOX_ACQUIRE_SHARED_GUARD(name, ...) \
+ std::shared_lock<std::shared_timed_mutex> name(__VA_ARGS__)
+# define RLBOX_ACQUIRE_UNIQUE_GUARD(name, ...) \
+ std::unique_lock<std::shared_timed_mutex> name(__VA_ARGS__)
+#else
+# if !defined(RLBOX_SHARED_LOCK) || !defined(RLBOX_ACQUIRE_SHARED_GUARD) || \
+ !defined(RLBOX_ACQUIRE_UNIQUE_GUARD)
+# error \
+ "RLBOX_USE_CUSTOM_SHARED_LOCK defined but missing definitions for RLBOX_SHARED_LOCK, RLBOX_ACQUIRE_SHARED_GUARD, RLBOX_ACQUIRE_UNIQUE_GUARD"
+# endif
+#endif
+
+namespace rlbox {
+
+namespace detail {
+ // relying on the dynamic check settings (exception vs abort) in the rlbox lib
+ inline void dynamic_check(bool check, const char* const msg);
+}
+
+namespace lucet_detail {
+
+ template<typename T>
+ constexpr bool false_v = false;
+
+ // https://stackoverflow.com/questions/6512019/can-we-get-the-type-of-a-lambda-argument
+ namespace return_argument_detail {
+ template<typename Ret, typename... Rest>
+ Ret helper(Ret (*)(Rest...));
+
+ template<typename Ret, typename F, typename... Rest>
+ Ret helper(Ret (F::*)(Rest...));
+
+ template<typename Ret, typename F, typename... Rest>
+ Ret helper(Ret (F::*)(Rest...) const);
+
+ template<typename F>
+ decltype(helper(&F::operator())) helper(F);
+ } // namespace return_argument_detail
+
+ template<typename T>
+ using return_argument =
+ decltype(return_argument_detail::helper(std::declval<T>()));
+
+ ///////////////////////////////////////////////////////////////
+
+ // https://stackoverflow.com/questions/37602057/why-isnt-a-for-loop-a-compile-time-expression
+ namespace compile_time_for_detail {
+ template<std::size_t N>
+ struct num
+ {
+ static const constexpr auto value = N;
+ };
+
+ template<class F, std::size_t... Is>
+ inline void compile_time_for_helper(F func, std::index_sequence<Is...>)
+ {
+ (func(num<Is>{}), ...);
+ }
+ } // namespace compile_time_for_detail
+
+ template<std::size_t N, typename F>
+ inline void compile_time_for(F func)
+ {
+ compile_time_for_detail::compile_time_for_helper(
+ func, std::make_index_sequence<N>());
+ }
+
+ ///////////////////////////////////////////////////////////////
+
+ template<typename T, typename = void>
+ struct convert_type_to_wasm_type
+ {
+ static_assert(std::is_void_v<T>, "Missing specialization");
+ using type = void;
+ static constexpr enum LucetValueType lucet_type = LucetValueType_Void;
+ };
+
+ template<typename T>
+ struct convert_type_to_wasm_type<
+ T,
+ std::enable_if_t<(std::is_integral_v<T> || std::is_enum_v<T>)&&sizeof(T) <=
+ sizeof(uint32_t)>>
+ {
+ using type = uint32_t;
+ static constexpr enum LucetValueType lucet_type = LucetValueType_I32;
+ };
+
+ template<typename T>
+ struct convert_type_to_wasm_type<
+ T,
+ std::enable_if_t<(std::is_integral_v<T> ||
+ std::is_enum_v<T>)&&sizeof(uint32_t) < sizeof(T) &&
+ sizeof(T) <= sizeof(uint64_t)>>
+ {
+ using type = uint64_t;
+ static constexpr enum LucetValueType lucet_type = LucetValueType_I64;
+ };
+
+ template<typename T>
+ struct convert_type_to_wasm_type<T,
+ std::enable_if_t<std::is_same_v<T, float>>>
+ {
+ using type = T;
+ static constexpr enum LucetValueType lucet_type = LucetValueType_F32;
+ };
+
+ template<typename T>
+ struct convert_type_to_wasm_type<T,
+ std::enable_if_t<std::is_same_v<T, double>>>
+ {
+ using type = T;
+ static constexpr enum LucetValueType lucet_type = LucetValueType_F64;
+ };
+
+ template<typename T>
+ struct convert_type_to_wasm_type<
+ T,
+ std::enable_if_t<std::is_pointer_v<T> || std::is_class_v<T>>>
+ {
+ // pointers are 32 bit indexes in wasm
+ // class paramters are passed as a pointer to an object in the stack or heap
+ using type = uint32_t;
+ static constexpr enum LucetValueType lucet_type = LucetValueType_I32;
+ };
+
+ ///////////////////////////////////////////////////////////////
+
+ namespace prepend_arg_type_detail {
+ template<typename T, typename T_ArgNew>
+ struct helper;
+
+ template<typename T_ArgNew, typename T_Ret, typename... T_Args>
+ struct helper<T_Ret(T_Args...), T_ArgNew>
+ {
+ using type = T_Ret(T_ArgNew, T_Args...);
+ };
+ }
+
+ template<typename T_Func, typename T_ArgNew>
+ using prepend_arg_type =
+ typename prepend_arg_type_detail::helper<T_Func, T_ArgNew>::type;
+
+ ///////////////////////////////////////////////////////////////
+
+ namespace change_return_type_detail {
+ template<typename T, typename T_RetNew>
+ struct helper;
+
+ template<typename T_RetNew, typename T_Ret, typename... T_Args>
+ struct helper<T_Ret(T_Args...), T_RetNew>
+ {
+ using type = T_RetNew(T_Args...);
+ };
+ }
+
+ template<typename T_Func, typename T_RetNew>
+ using change_return_type =
+ typename change_return_type_detail::helper<T_Func, T_RetNew>::type;
+
+ ///////////////////////////////////////////////////////////////
+
+ namespace change_class_arg_types_detail {
+ template<typename T, typename T_ArgNew>
+ struct helper;
+
+ template<typename T_ArgNew, typename T_Ret, typename... T_Args>
+ struct helper<T_Ret(T_Args...), T_ArgNew>
+ {
+ using type =
+ T_Ret(std::conditional_t<std::is_class_v<T_Args>, T_ArgNew, T_Args>...);
+ };
+ }
+
+ template<typename T_Func, typename T_ArgNew>
+ using change_class_arg_types =
+ typename change_class_arg_types_detail::helper<T_Func, T_ArgNew>::type;
+
+} // namespace lucet_detail
+
+class rlbox_lucet_sandbox;
+
+struct rlbox_lucet_sandbox_thread_data
+{
+ rlbox_lucet_sandbox* sandbox;
+ uint32_t last_callback_invoked;
+};
+
+#ifdef RLBOX_EMBEDDER_PROVIDES_TLS_STATIC_VARIABLES
+
+rlbox_lucet_sandbox_thread_data* get_rlbox_lucet_sandbox_thread_data();
+# define RLBOX_LUCET_SANDBOX_STATIC_VARIABLES() \
+ thread_local rlbox::rlbox_lucet_sandbox_thread_data \
+ rlbox_lucet_sandbox_thread_info{ 0, 0 }; \
+ namespace rlbox { \
+ rlbox_lucet_sandbox_thread_data* get_rlbox_lucet_sandbox_thread_data() \
+ { \
+ return &rlbox_lucet_sandbox_thread_info; \
+ } \
+ } \
+ static_assert(true, "Enforce semi-colon")
+
+#endif
+
+class rlbox_lucet_sandbox
+{
+public:
+ using T_LongLongType = int32_t;
+ using T_LongType = int32_t;
+ using T_IntType = int32_t;
+ using T_PointerType = uint32_t;
+ using T_ShortType = int16_t;
+
+private:
+ LucetSandboxInstance* sandbox = nullptr;
+ uintptr_t heap_base;
+ void* malloc_index = 0;
+ void* free_index = 0;
+ size_t return_slot_size = 0;
+ T_PointerType return_slot = 0;
+
+ static const size_t MAX_CALLBACKS = 128;
+ RLBOX_SHARED_LOCK(callback_mutex);
+ void* callback_unique_keys[MAX_CALLBACKS]{ 0 };
+ void* callbacks[MAX_CALLBACKS]{ 0 };
+ uint32_t callback_slot_assignment[MAX_CALLBACKS]{ 0 };
+
+ using TableElementRef = LucetFunctionTableElement*;
+ struct FunctionTable
+ {
+ TableElementRef elements[MAX_CALLBACKS];
+ uint32_t slot_number[MAX_CALLBACKS];
+ };
+ inline static std::mutex callback_table_mutex;
+ // We need to share the callback slot info across multiple sandbox instances
+ // that may load the same sandboxed library. Thus if the sandboxed library is
+ // already in the memory space, we should just use the previously saved info
+ // as the load is destroys the callback info. Once all instances of the
+ // library is unloaded, the sandboxed library is removed from the address
+ // space and thus we can "reset" our state. The semantics of shared and weak
+ // pointers ensure this and will automatically release the memory after all
+ // instances are released.
+ inline static std::map<void*, std::weak_ptr<FunctionTable>>
+ shared_callback_slots;
+ std::shared_ptr<FunctionTable> callback_slots = nullptr;
+ // However, if the library is also loaded externally in the application, then
+ // we don't know when we can ever "reset". In such scenarios, we are better of
+ // never throwing away the callback info, rather than figuring out
+ // what/why/when the application is loading or unloading the sandboxed
+ // library. An extra reference to the shared_ptr will ensure this.
+ inline static std::vector<std::shared_ptr<FunctionTable>>
+ saved_callback_slot_info;
+
+#ifndef RLBOX_EMBEDDER_PROVIDES_TLS_STATIC_VARIABLES
+ thread_local static inline rlbox_lucet_sandbox_thread_data thread_data{ 0,
+ 0 };
+#endif
+
+ template<typename T_FormalRet, typename T_ActualRet>
+ inline auto serialize_to_sandbox(T_ActualRet arg)
+ {
+ if constexpr (std::is_class_v<T_FormalRet>) {
+ // structs returned as pointers into wasm memory/wasm stack
+ auto ptr = reinterpret_cast<T_FormalRet*>(
+ impl_get_unsandboxed_pointer<T_FormalRet*>(arg));
+ T_FormalRet ret = *ptr;
+ return ret;
+ } else {
+ return arg;
+ }
+ }
+
+ inline std::shared_ptr<FunctionTable> get_callback_ref_data(
+ LucetFunctionTable& functionPointerTable)
+ {
+ auto callback_slots = std::make_shared<FunctionTable>();
+
+ for (size_t i = 0; i < MAX_CALLBACKS; i++) {
+ uintptr_t reservedVal =
+ lucet_get_reserved_callback_slot_val(sandbox, i + 1);
+
+ bool found = false;
+ for (size_t j = 0; j < functionPointerTable.length; j++) {
+ if (functionPointerTable.data[j].rf == reservedVal) {
+ functionPointerTable.data[j].rf = 0;
+ callback_slots->elements[i] = &(functionPointerTable.data[j]);
+ callback_slots->slot_number[i] = static_cast<uint32_t>(j);
+ found = true;
+ break;
+ }
+ }
+
+ detail::dynamic_check(found, "Unable to intialize callback tables");
+ }
+
+ return callback_slots;
+ }
+
+ inline void reinit_callback_ref_data(
+ LucetFunctionTable& functionPointerTable,
+ std::shared_ptr<FunctionTable>& callback_slots)
+ {
+ for (size_t i = 0; i < MAX_CALLBACKS; i++) {
+ uintptr_t reservedVal =
+ lucet_get_reserved_callback_slot_val(sandbox, i + 1);
+
+ for (size_t j = 0; j < functionPointerTable.length; j++) {
+ if (functionPointerTable.data[j].rf == reservedVal) {
+ functionPointerTable.data[j].rf = 0;
+
+ detail::dynamic_check(
+ callback_slots->elements[i] == &(functionPointerTable.data[j]) &&
+ callback_slots->slot_number[i] == static_cast<uint32_t>(j),
+ "Sandbox creation error: Error when checking the values of "
+ "callback slot data");
+
+ break;
+ }
+ }
+ }
+ }
+
+ inline void set_callbacks_slots_ref(bool external_loads_exist)
+ {
+ LucetFunctionTable functionPointerTable =
+ lucet_get_function_pointer_table(sandbox);
+ void* key = functionPointerTable.data;
+
+ std::lock_guard<std::mutex> lock(callback_table_mutex);
+ std::weak_ptr<FunctionTable> slots = shared_callback_slots[key];
+
+ if (auto shared_slots = slots.lock()) {
+ // pointer exists
+ callback_slots = shared_slots;
+ // Sometimes, dlopen and process forking seem to act a little weird.
+ // Writes to the writable page of the dynamic lib section seem to not
+ // always be propagated (possibly when the dynamic library is opened
+ // externally - "external_loads_exist")). This occurred in when RLBox was
+ // used in ASAN builds of Firefox. In general, we take the precaution of
+ // rechecking this on each sandbox creation.
+ reinit_callback_ref_data(functionPointerTable, callback_slots);
+ return;
+ }
+
+ callback_slots = get_callback_ref_data(functionPointerTable);
+ shared_callback_slots[key] = callback_slots;
+ if (external_loads_exist) {
+ saved_callback_slot_info.push_back(callback_slots);
+ }
+ }
+
+ template<uint32_t N, typename T_Ret, typename... T_Args>
+ static typename lucet_detail::convert_type_to_wasm_type<T_Ret>::type
+ callback_interceptor(
+ void* /* vmContext */,
+ typename lucet_detail::convert_type_to_wasm_type<T_Args>::type... params)
+ {
+#ifdef RLBOX_EMBEDDER_PROVIDES_TLS_STATIC_VARIABLES
+ auto& thread_data = *get_rlbox_lucet_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(thread_data.sandbox->serialize_to_sandbox<T_Args>(params)...);
+ }
+
+ template<uint32_t N, typename T_Ret, typename... T_Args>
+ static void callback_interceptor_promoted(
+ void* /* vmContext */,
+ typename lucet_detail::convert_type_to_wasm_type<T_Ret>::type ret,
+ typename lucet_detail::convert_type_to_wasm_type<T_Args>::type... params)
+ {
+#ifdef RLBOX_EMBEDDER_PROVIDES_TLS_STATIC_VARIABLES
+ auto& thread_data = *get_rlbox_lucet_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
+ auto ret_val =
+ func(thread_data.sandbox->serialize_to_sandbox<T_Args>(params)...);
+ // Copy the return value back
+ auto ret_ptr = reinterpret_cast<T_Ret*>(
+ thread_data.sandbox->template impl_get_unsandboxed_pointer<T_Ret*>(ret));
+ *ret_ptr = ret_val;
+ }
+
+ template<typename T_Ret, typename... T_Args>
+ inline T_PointerType get_lucet_type_index(
+ T_Ret (*/* dummy for template inference */)(T_Args...) = nullptr) const
+ {
+ // Class return types as promoted to args
+ constexpr bool promoted = std::is_class_v<T_Ret>;
+ int32_t type_index;
+
+ if constexpr (promoted) {
+ LucetValueType ret_type = LucetValueType::LucetValueType_Void;
+ LucetValueType param_types[] = {
+ lucet_detail::convert_type_to_wasm_type<T_Ret>::lucet_type,
+ lucet_detail::convert_type_to_wasm_type<T_Args>::lucet_type...
+ };
+ LucetFunctionSignature signature{ ret_type,
+ sizeof(param_types) /
+ sizeof(LucetValueType),
+ &(param_types[0]) };
+ type_index = lucet_get_function_type_index(sandbox, signature);
+ } else {
+ LucetValueType ret_type =
+ lucet_detail::convert_type_to_wasm_type<T_Ret>::lucet_type;
+ LucetValueType param_types[] = {
+ lucet_detail::convert_type_to_wasm_type<T_Args>::lucet_type...
+ };
+ LucetFunctionSignature signature{ ret_type,
+ sizeof(param_types) /
+ sizeof(LucetValueType),
+ &(param_types[0]) };
+ type_index = lucet_get_function_type_index(sandbox, signature);
+ }
+
+ return type_index;
+ }
+
+ void ensure_return_slot_size(size_t size)
+ {
+ if (size > return_slot_size) {
+ if (return_slot_size) {
+ impl_free_in_sandbox(return_slot);
+ }
+ return_slot = impl_malloc_in_sandbox(size);
+ detail::dynamic_check(
+ return_slot != 0,
+ "Error initializing return slot. Sandbox may be out of memory!");
+ return_slot_size = size;
+ }
+ }
+
+protected:
+ // Set external_loads_exist to true, if the host application loads the
+ // library lucet_module_path outside of rlbox_lucet_sandbox such as via dlopen
+ // or the Windows equivalent
+ inline void impl_create_sandbox(const char* lucet_module_path,
+ bool external_loads_exist,
+ bool allow_stdio)
+ {
+ detail::dynamic_check(sandbox == nullptr, "Sandbox already initialized");
+ sandbox = lucet_load_module(lucet_module_path, allow_stdio);
+ detail::dynamic_check(sandbox != nullptr, "Sandbox could not be created");
+
+ heap_base = reinterpret_cast<uintptr_t>(impl_get_memory_location());
+ // Check that the address space is larger than the sandbox heap i.e. 4GB
+ // sandbox heap, host has to have more than 4GB
+ static_assert(sizeof(uintptr_t) > sizeof(T_PointerType));
+ // Check that the heap is aligned to the pointer size i.e. 32-bit pointer =>
+ // aligned to 4GB. The implementations of
+ // impl_get_unsandboxed_pointer_no_ctx and impl_get_sandboxed_pointer_no_ctx
+ // below rely on this.
+ uintptr_t heap_offset_mask = std::numeric_limits<T_PointerType>::max();
+ detail::dynamic_check((heap_base & heap_offset_mask) == 0,
+ "Sandbox heap not aligned to 4GB");
+
+ // cache these for performance
+ malloc_index = impl_lookup_symbol("malloc");
+ free_index = impl_lookup_symbol("free");
+
+ set_callbacks_slots_ref(external_loads_exist);
+ }
+
+ inline void impl_create_sandbox(const char* lucet_module_path)
+ {
+ // Default is to assume that no external code will load the wasm library as
+ // this is usually the case
+ const bool external_loads_exist = false;
+ const bool allow_stdio = true;
+ impl_create_sandbox(lucet_module_path, external_loads_exist, allow_stdio);
+ }
+
+ inline void impl_destroy_sandbox() {
+ if (return_slot_size) {
+ impl_free_in_sandbox(return_slot);
+ }
+ lucet_drop_module(sandbox);
+ }
+
+ template<typename T>
+ inline void* impl_get_unsandboxed_pointer(T_PointerType p) const
+ {
+ if constexpr (std::is_function_v<std::remove_pointer_t<T>>) {
+ LucetFunctionTable functionPointerTable =
+ lucet_get_function_pointer_table(sandbox);
+ if (p >= functionPointerTable.length) {
+ // Received out of range function pointer
+ return nullptr;
+ }
+ auto ret = functionPointerTable.data[p].rf;
+ return reinterpret_cast<void*>(static_cast<uintptr_t>(ret));
+ } else {
+ return reinterpret_cast<void*>(heap_base + p);
+ }
+ }
+
+ template<typename T>
+ inline T_PointerType impl_get_sandboxed_pointer(const void* p) const
+ {
+ if constexpr (std::is_function_v<std::remove_pointer_t<T>>) {
+ // p is a pointer to a function internal to the lucet module
+ // we need to either
+ // 1) find the indirect function slot this is registered and return the
+ // slot number. For this we need to scan the full indirect function table,
+ // not just the portion we have reserved for callbacks.
+ // 2) in the scenario this function has not ever been listed as an
+ // indirect function, we need to register this like a normal callback.
+ // However, unlike callbacks, we will not require the user to unregister
+ // this. Instead, this permenantly takes up a callback slot.
+ LucetFunctionTable functionPointerTable =
+ lucet_get_function_pointer_table(sandbox);
+ std::lock_guard<std::mutex> lock(callback_table_mutex);
+
+ // Scenario 1 described above
+ ssize_t empty_slot = -1;
+ for (size_t i = 0; i < functionPointerTable.length; i++) {
+ if (functionPointerTable.data[i].rf == reinterpret_cast<uintptr_t>(p)) {
+ return static_cast<T_PointerType>(i);
+ } else if (functionPointerTable.data[i].rf == 0 && empty_slot == -1) {
+ // found an empty slot. Save it, as we may use it later.
+ empty_slot = i;
+ }
+ }
+
+ // Scenario 2 described above
+ detail::dynamic_check(
+ empty_slot != -1,
+ "Could not find an empty slot in sandbox function table. This would "
+ "happen if you have registered too many callbacks, or unsandboxed "
+ "too many function pointers. You can file a bug if you want to "
+ "increase the maximum allowed callbacks or unsadnboxed functions "
+ "pointers");
+ T dummy = nullptr;
+ int32_t type_index = get_lucet_type_index(dummy);
+ functionPointerTable.data[empty_slot].ty = type_index;
+ functionPointerTable.data[empty_slot].rf = reinterpret_cast<uintptr_t>(p);
+ return empty_slot;
+
+ } else {
+ return static_cast<T_PointerType>(reinterpret_cast<uintptr_t>(p));
+ }
+ }
+
+ template<typename T>
+ static inline void* impl_get_unsandboxed_pointer_no_ctx(
+ T_PointerType p,
+ const void* example_unsandboxed_ptr,
+ rlbox_lucet_sandbox* (*expensive_sandbox_finder)(
+ const void* example_unsandboxed_ptr))
+ {
+ if constexpr (std::is_function_v<std::remove_pointer_t<T>>) {
+ // swizzling function pointers needs access to the function pointer tables
+ // and thus cannot be done without context
+ auto sandbox = expensive_sandbox_finder(example_unsandboxed_ptr);
+ return sandbox->impl_get_unsandboxed_pointer<T>(p);
+ } else {
+ // grab the memory base from the example_unsandboxed_ptr
+ uintptr_t heap_base_mask =
+ std::numeric_limits<uintptr_t>::max() &
+ ~(static_cast<uintptr_t>(std::numeric_limits<T_PointerType>::max()));
+ uintptr_t computed_heap_base =
+ reinterpret_cast<uintptr_t>(example_unsandboxed_ptr) & heap_base_mask;
+ uintptr_t ret = computed_heap_base | p;
+ return reinterpret_cast<void*>(ret);
+ }
+ }
+
+ template<typename T>
+ static inline T_PointerType impl_get_sandboxed_pointer_no_ctx(
+ const void* p,
+ const void* example_unsandboxed_ptr,
+ rlbox_lucet_sandbox* (*expensive_sandbox_finder)(
+ const void* example_unsandboxed_ptr))
+ {
+ if constexpr (std::is_function_v<std::remove_pointer_t<T>>) {
+ // swizzling function pointers needs access to the function pointer tables
+ // and thus cannot be done without context
+ auto sandbox = expensive_sandbox_finder(example_unsandboxed_ptr);
+ return sandbox->impl_get_sandboxed_pointer<T>(p);
+ } else {
+ // Just clear the memory base to leave the offset
+ RLBOX_LUCET_UNUSED(example_unsandboxed_ptr);
+ uintptr_t ret = reinterpret_cast<uintptr_t>(p) &
+ std::numeric_limits<T_PointerType>::max();
+ return static_cast<T_PointerType>(ret);
+ }
+ }
+
+ static inline bool impl_is_in_same_sandbox(const void* p1, const void* p2)
+ {
+ uintptr_t heap_base_mask = std::numeric_limits<uintptr_t>::max() &
+ ~(std::numeric_limits<T_PointerType>::max());
+ return (reinterpret_cast<uintptr_t>(p1) & heap_base_mask) ==
+ (reinterpret_cast<uintptr_t>(p2) & heap_base_mask);
+ }
+
+ inline bool impl_is_pointer_in_sandbox_memory(const void* p)
+ {
+ size_t length = impl_get_total_memory();
+ uintptr_t p_val = reinterpret_cast<uintptr_t>(p);
+ return p_val >= heap_base && p_val < (heap_base + length);
+ }
+
+ inline bool impl_is_pointer_in_app_memory(const void* p)
+ {
+ return !(impl_is_pointer_in_sandbox_memory(p));
+ }
+
+ inline size_t impl_get_total_memory() { return lucet_get_heap_size(sandbox); }
+
+ inline void* impl_get_memory_location()
+ {
+ return lucet_get_heap_base(sandbox);
+ }
+
+ void* impl_lookup_symbol(const char* func_name)
+ {
+ return lucet_lookup_function(sandbox, 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_lucet_sandbox_thread_data();
+#endif
+ thread_data.sandbox = this;
+ lucet_set_curr_instance(sandbox);
+
+ // WASM functions are mangled in the following manner
+ // 1. All primitive types are left as is and follow an LP32 machine model
+ // (as opposed to the possibly 64-bit application)
+ // 2. All pointers are changed to u32 types
+ // 3. Returned class are returned as an out parameter before the actual
+ // function parameters
+ // 4. All class parameters are passed as pointers (u32 types)
+ // 5. The heap address is passed in as the first argument to the function
+ //
+ // RLBox accounts for the first 2 differences in T_Converted type, but we
+ // need to handle the rest
+
+ // Handle point 3
+ using T_Ret = lucet_detail::return_argument<T_Converted>;
+ if constexpr (std::is_class_v<T_Ret>) {
+ using T_Conv1 = lucet_detail::change_return_type<T_Converted, void>;
+ using T_Conv2 = lucet_detail::prepend_arg_type<T_Conv1, T_PointerType>;
+ auto func_ptr_conv =
+ reinterpret_cast<T_Conv2*>(reinterpret_cast<uintptr_t>(func_ptr));
+ ensure_return_slot_size(sizeof(T_Ret));
+ impl_invoke_with_func_ptr<T>(func_ptr_conv, return_slot, params...);
+
+ auto ptr = reinterpret_cast<T_Ret*>(
+ impl_get_unsandboxed_pointer<T_Ret*>(return_slot));
+ T_Ret ret = *ptr;
+ return ret;
+ }
+
+ // Handle point 4
+ constexpr size_t alloc_length = [&] {
+ if constexpr (sizeof...(params) > 0) {
+ return ((std::is_class_v<T_Args> ? 1 : 0) + ...);
+ } else {
+ return 0;
+ }
+ }();
+
+ // 0 arg functions create 0 length arrays which is not allowed
+ T_PointerType allocations_buff[alloc_length == 0 ? 1 : alloc_length];
+ T_PointerType* allocations = allocations_buff;
+
+ auto serialize_class_arg =
+ [&](auto arg) -> std::conditional_t<std::is_class_v<decltype(arg)>,
+ T_PointerType,
+ decltype(arg)> {
+ using T_Arg = decltype(arg);
+ if constexpr (std::is_class_v<T_Arg>) {
+ auto slot = impl_malloc_in_sandbox(sizeof(T_Arg));
+ auto ptr =
+ reinterpret_cast<T_Arg*>(impl_get_unsandboxed_pointer<T_Arg*>(slot));
+ *ptr = arg;
+ allocations[0] = slot;
+ allocations++;
+ return slot;
+ } else {
+ return arg;
+ }
+ };
+
+ // 0 arg functions don't use serialize
+ RLBOX_LUCET_UNUSED(serialize_class_arg);
+
+ using T_ConvNoClass =
+ lucet_detail::change_class_arg_types<T_Converted, T_PointerType>;
+
+ // Handle Point 5
+ using T_ConvHeap = lucet_detail::prepend_arg_type<T_ConvNoClass, uint64_t>;
+
+ // Function invocation
+ auto func_ptr_conv =
+ reinterpret_cast<T_ConvHeap*>(reinterpret_cast<uintptr_t>(func_ptr));
+
+ using T_NoVoidRet =
+ std::conditional_t<std::is_void_v<T_Ret>, uint32_t, T_Ret>;
+ T_NoVoidRet ret;
+
+ if constexpr (std::is_void_v<T_Ret>) {
+ RLBOX_LUCET_UNUSED(ret);
+ func_ptr_conv(heap_base, serialize_class_arg(params)...);
+ } else {
+ ret = func_ptr_conv(heap_base, serialize_class_arg(params)...);
+ }
+
+ for (size_t i = 0; i < alloc_length; i++) {
+ impl_free_in_sandbox(allocations_buff[i]);
+ }
+
+ if constexpr (!std::is_void_v<T_Ret>) {
+ return ret;
+ }
+ }
+
+ inline T_PointerType impl_malloc_in_sandbox(size_t size)
+ {
+ detail::dynamic_check(size <= std::numeric_limits<uint32_t>::max(),
+ "Attempting to malloc more than the heap size");
+ using T_Func = void*(size_t);
+ using T_Converted = T_PointerType(uint32_t);
+ T_PointerType ret = impl_invoke_with_func_ptr<T_Func, T_Converted>(
+ reinterpret_cast<T_Converted*>(malloc_index),
+ static_cast<uint32_t>(size));
+ return ret;
+ }
+
+ inline void impl_free_in_sandbox(T_PointerType p)
+ {
+ using T_Func = void(void*);
+ using T_Converted = void(T_PointerType);
+ impl_invoke_with_func_ptr<T_Func, T_Converted>(
+ reinterpret_cast<T_Converted*>(free_index), p);
+ }
+
+ template<typename T_Ret, typename... T_Args>
+ inline T_PointerType impl_register_callback(void* key, void* callback)
+ {
+ int32_t type_index = get_lucet_type_index<T_Ret, T_Args...>();
+
+ detail::dynamic_check(
+ type_index != -1,
+ "Could not find lucet type for callback signature. This can "
+ "happen if you tried to register a callback whose signature "
+ "does not correspond to any callbacks used in the library.");
+
+ bool found = false;
+ uint32_t found_loc = 0;
+ uint32_t slot_number = 0;
+
+ {
+ std::lock_guard<std::mutex> lock(callback_table_mutex);
+
+ // need a compile time for loop as we we need I to be a compile time value
+ // this is because we are setting the I'th callback ineterceptor
+ lucet_detail::compile_time_for<MAX_CALLBACKS>([&](auto I) {
+ constexpr auto i = I.value;
+ if (!found && callback_slots->elements[i]->rf == 0) {
+ found = true;
+ found_loc = i;
+ slot_number = callback_slots->slot_number[i];
+
+ void* chosen_interceptor;
+ if constexpr (std::is_class_v<T_Ret>) {
+ chosen_interceptor = reinterpret_cast<void*>(
+ callback_interceptor_promoted<i, T_Ret, T_Args...>);
+ } else {
+ chosen_interceptor = reinterpret_cast<void*>(
+ callback_interceptor<i, T_Ret, T_Args...>);
+ }
+ callback_slots->elements[i]->ty = type_index;
+ callback_slots->elements[i]->rf =
+ reinterpret_cast<uintptr_t>(chosen_interceptor);
+ }
+ });
+ }
+
+ detail::dynamic_check(
+ found,
+ "Could not find an empty slot in sandbox function table. This would "
+ "happen if you have registered too many callbacks, or unsandboxed "
+ "too many function pointers. You can file a bug if you want to "
+ "increase the maximum allowed callbacks or unsadnboxed functions "
+ "pointers");
+
+ {
+ RLBOX_ACQUIRE_UNIQUE_GUARD(lock, callback_mutex);
+ callback_unique_keys[found_loc] = key;
+ callbacks[found_loc] = callback;
+ callback_slot_assignment[found_loc] = slot_number;
+ }
+
+ return static_cast<T_PointerType>(slot_number);
+ }
+
+ static inline std::pair<rlbox_lucet_sandbox*, void*>
+ impl_get_executed_callback_sandbox_and_key()
+ {
+#ifdef RLBOX_EMBEDDER_PROVIDES_TLS_STATIC_VARIABLES
+ auto& thread_data = *get_rlbox_lucet_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)
+ {
+ bool found = false;
+ uint32_t i = 0;
+ {
+ RLBOX_ACQUIRE_UNIQUE_GUARD(lock, callback_mutex);
+ for (; i < MAX_CALLBACKS; i++) {
+ if (callback_unique_keys[i] == key) {
+ callback_unique_keys[i] = nullptr;
+ callbacks[i] = nullptr;
+ callback_slot_assignment[i] = 0;
+ found = true;
+ break;
+ }
+ }
+ }
+
+ detail::dynamic_check(
+ found, "Internal error: Could not find callback to unregister");
+
+ std::lock_guard<std::mutex> shared_lock(callback_table_mutex);
+ callback_slots->elements[i]->rf = 0;
+ return;
+ }
+};
+
+} // namespace rlbox \ No newline at end of file