diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /third_party/rlbox_wasm2c_sandbox | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rlbox_wasm2c_sandbox')
8 files changed, 2369 insertions, 0 deletions
diff --git a/third_party/rlbox_wasm2c_sandbox/LICENSE b/third_party/rlbox_wasm2c_sandbox/LICENSE new file mode 100755 index 0000000000..87e0ce55af --- /dev/null +++ b/third_party/rlbox_wasm2c_sandbox/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 UCSD PLSysSec + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/third_party/rlbox_wasm2c_sandbox/c_src/wasm2c_sandbox_wrapper.c b/third_party/rlbox_wasm2c_sandbox/c_src/wasm2c_sandbox_wrapper.c new file mode 100755 index 0000000000..99b1e7ec56 --- /dev/null +++ b/third_party/rlbox_wasm2c_sandbox/c_src/wasm2c_sandbox_wrapper.c @@ -0,0 +1,7 @@ +#include <stdlib.h> + +int main(int argc, char *argv[]) { + (void) argc; + (void) argv; + abort(); +} diff --git a/third_party/rlbox_wasm2c_sandbox/include/rlbox_wasm2c_sandbox.hpp b/third_party/rlbox_wasm2c_sandbox/include/rlbox_wasm2c_sandbox.hpp new file mode 100644 index 0000000000..ed6975e27e --- /dev/null +++ b/third_party/rlbox_wasm2c_sandbox/include/rlbox_wasm2c_sandbox.hpp @@ -0,0 +1,971 @@ +#pragma once + +#include "rlbox_wasm2c_tls.hpp" +#include "wasm-rt.h" +#include "wasm2c_rt_mem.h" +#include "wasm2c_rt_minwasi.h" + +// Pull the helper header from the main repo for dynamic_check and scope_exit +#include "rlbox_helpers.hpp" + +#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 <string> +#include <type_traits> +#include <utility> +#include <vector> + +#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 + +#define RLBOX_WASM2C_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 + +#define DEFINE_RLBOX_WASM2C_MODULE_TYPE(modname) \ + struct rlbox_wasm2c_module_type_##modname \ + { \ + using instance_t = w2c_##modname; \ + \ + using create_instance_t = void (*)(instance_t*, \ + struct w2c_env*, \ + struct w2c_wasi__snapshot__preview1*); \ + static constexpr create_instance_t create_instance = \ + &wasm2c_##modname##_instantiate; \ + \ + using free_instance_t = void (*)(instance_t*); \ + static constexpr free_instance_t free_instance = &wasm2c_##modname##_free; \ + \ + using get_func_type_t = wasm_rt_func_type_t (*)(uint32_t, uint32_t, ...); \ + static constexpr get_func_type_t get_func_type = \ + &wasm2c_##modname##_get_func_type; \ + \ + static constexpr const uint64_t* initial_memory_pages = \ + &wasm2c_##modname##_min_env_memory; \ + static constexpr const uint8_t* is_memory_64 = \ + &wasm2c_##modname##_is64_env_memory; \ + static constexpr const uint32_t* initial_func_elements = \ + &wasm2c_##modname##_min_env_0x5F_indirect_function_table; \ + \ + static constexpr const char* prefix = #modname; \ + \ + /* A function that returns the address of the func specified as a \ + * constexpr string */ \ + /* Unfortunately, there is no way to implement the below in C++. */ \ + /* Implement this to fully support multiple static modules. */ \ + /* static constexpr void* dlsym_in_w2c_module(const char* func_name) { */ \ + /* return &w2c_##modname##_%func%; */ \ + /* } */ \ + \ + static constexpr auto malloc_address = &w2c_##modname##_malloc; \ + static constexpr auto free_address = &w2c_##modname##_free; \ + } + +// wasm_module_name module name used when compiling with wasm2c +#ifndef RLBOX_WASM2C_MODULE_NAME +# error "Expected definition for RLBOX_WASM2C_MODULE_NAME" +#endif + +// Need an extra macro to expand RLBOX_WASM2C_MODULE_NAME +#define INVOKE_DEFINE_RLBOX_WASM2C_MODULE_TYPE(modname) \ + DEFINE_RLBOX_WASM2C_MODULE_TYPE(modname) + +INVOKE_DEFINE_RLBOX_WASM2C_MODULE_TYPE(RLBOX_WASM2C_MODULE_NAME); + +// Concat after macro expansion +#define RLBOX_WASM2C_CONCAT2(x, y) x##y +#define RLBOX_WASM2C_CONCAT(x, y) RLBOX_WASM2C_CONCAT2(x, y) + +#define RLBOX_WASM_MODULE_TYPE_CURR \ + RLBOX_WASM2C_CONCAT(rlbox_wasm2c_module_type_, RLBOX_WASM2C_MODULE_NAME) + +#define RLBOX_WASM2C_STRINGIFY(x) RLBOX_WASM2C_STRINGIFY2(x) +#define RLBOX_WASM2C_STRINGIFY2(x) #x + +#define RLBOX_WASM2C_MODULE_NAME_STR \ + RLBOX_WASM2C_STRINGIFY(RLBOX_WASM2C_MODULE_NAME) + +#define RLBOX_WASM2C_MODULE_FUNC_HELPER2(part1, part2, part3) \ + part1##part2##part3 +#define RLBOX_WASM2C_MODULE_FUNC_HELPER(part1, part2, part3) \ + RLBOX_WASM2C_MODULE_FUNC_HELPER2(part1, part2, part3) +#define RLBOX_WASM2C_MODULE_FUNC(name) \ + RLBOX_WASM2C_MODULE_FUNC_HELPER(w2c_, RLBOX_WASM2C_MODULE_NAME, name) + +namespace rlbox { + +namespace wasm2c_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; + // wasm2c has no void type so use i32 for now + static constexpr wasm_rt_type_t wasm2c_type = WASM_RT_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(T) <= + sizeof(uint32_t)>> + { + using type = uint32_t; + static constexpr wasm_rt_type_t wasm2c_type = WASM_RT_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 wasm_rt_type_t wasm2c_type = WASM_RT_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 wasm_rt_type_t wasm2c_type = WASM_RT_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 wasm_rt_type_t wasm2c_type = WASM_RT_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 wasm_rt_type_t wasm2c_type = WASM_RT_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 wasm2c_detail + +// declare the static symbol with weak linkage to keep this header only +#if defined(_MSC_VER) +__declspec(selectany) +#else +__attribute__((weak)) +#endif + std::once_flag rlbox_wasm2c_initialized; + +class rlbox_wasm2c_sandbox +{ +public: + using T_LongLongType = int64_t; + using T_LongType = int32_t; + using T_IntType = int32_t; + using T_PointerType = uint32_t; + using T_ShortType = int16_t; + +private: + mutable typename RLBOX_WASM_MODULE_TYPE_CURR::instance_t wasm2c_instance{ 0 }; + struct w2c_env sandbox_memory_env; + struct w2c_wasi__snapshot__preview1 wasi_env; + bool instance_initialized = false; + wasm_rt_memory_t sandbox_memory_info; + mutable wasm_rt_funcref_table_t sandbox_callback_table; + uintptr_t heap_base; + size_t return_slot_size = 0; + T_PointerType return_slot = 0; + mutable std::vector<T_PointerType> callback_free_list; + + static const size_t MAX_CALLBACKS = 128; + mutable 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 }; + mutable std::map<const void*, uint32_t> internal_callbacks; + mutable std::map<uint32_t, const void*> slot_assignments; + +#ifndef RLBOX_EMBEDDER_PROVIDES_TLS_STATIC_VARIABLES + thread_local static inline rlbox_wasm2c_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; + } + } + + template<uint32_t N, typename T_Ret, typename... T_Args> + static typename wasm2c_detail::convert_type_to_wasm_type<T_Ret>::type + callback_interceptor( + void* /* vmContext */, + typename wasm2c_detail::convert_type_to_wasm_type<T_Args>::type... params) + { +#ifdef RLBOX_EMBEDDER_PROVIDES_TLS_STATIC_VARIABLES + auto& thread_data = *get_rlbox_wasm2c_sandbox_thread_data(); +#endif + thread_data.last_callback_invoked = N; + using T_Func = T_Ret (*)(T_Args...); + T_Func func; + { +#ifndef RLBOX_SINGLE_THREADED_INVOCATIONS + RLBOX_ACQUIRE_SHARED_GUARD(lock, thread_data.sandbox->callback_mutex); +#endif + 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->template serialize_to_sandbox<T_Args>(params)...); + } + + template<uint32_t N, typename T_Ret, typename... T_Args> + static void callback_interceptor_promoted( + void* /* vmContext */, + typename wasm2c_detail::convert_type_to_wasm_type<T_Ret>::type ret, + typename wasm2c_detail::convert_type_to_wasm_type<T_Args>::type... params) + { +#ifdef RLBOX_EMBEDDER_PROVIDES_TLS_STATIC_VARIABLES + auto& thread_data = *get_rlbox_wasm2c_sandbox_thread_data(); +#endif + thread_data.last_callback_invoked = N; + using T_Func = T_Ret (*)(T_Args...); + T_Func func; + { +#ifndef RLBOX_SINGLE_THREADED_INVOCATIONS + RLBOX_ACQUIRE_SHARED_GUARD(lock, thread_data.sandbox->callback_mutex); +#endif + 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->template 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 wasm_rt_func_type_t get_wasm2c_func_index( + // dummy for template inference + T_Ret (*)(T_Args...) = nullptr) const + { + // Class return types as promoted to args + constexpr bool promoted = std::is_class_v<T_Ret>; + constexpr uint32_t param_count = + promoted ? (sizeof...(T_Args) + 1) : (sizeof...(T_Args)); + constexpr uint32_t ret_count = + promoted ? 0 : (std::is_void_v<T_Ret> ? 0 : 1); + + wasm_rt_func_type_t ret = nullptr; + if constexpr (ret_count == 0) { + ret = RLBOX_WASM_MODULE_TYPE_CURR::get_func_type( + param_count, + ret_count, + wasm2c_detail::convert_type_to_wasm_type<T_Args>::wasm2c_type...); + } else { + ret = RLBOX_WASM_MODULE_TYPE_CURR::get_func_type( + param_count, + ret_count, + wasm2c_detail::convert_type_to_wasm_type<T_Args>::wasm2c_type..., + wasm2c_detail::convert_type_to_wasm_type<T_Ret>::wasm2c_type); + } + + return ret; + } + + 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; + } + } + + // function takes a 32-bit value and returns the next power of 2 + // return is a 64-bit value as large 32-bit values will return 2^32 + static inline uint64_t next_power_of_two(uint32_t value) + { + uint64_t power = 1; + while (power < value) { + power *= 2; + } + return power; + } + +protected: +#define rlbox_wasm2c_sandbox_lookup_symbol(func_name) \ + reinterpret_cast<void*>(&RLBOX_WASM2C_MODULE_FUNC(_##func_name)) /* NOLINT \ + */ + + // adding a template so that we can use static_assert to fire only if this + // function is invoked + template<typename T = void> + void* impl_lookup_symbol(const char* func_name) + { + constexpr bool fail = std::is_same_v<T, void>; + static_assert( + !fail, + "The wasm2c_sandbox uses static calls and thus developers should add\n\n" + "#define RLBOX_USE_STATIC_CALLS() rlbox_wasm2c_sandbox_lookup_symbol\n\n" + "to their code, to ensure that static calls are handled correctly."); + return nullptr; + } + +public: +#define FALLIBLE_DYNAMIC_CHECK(infallible, cond, msg) \ + if (infallible) { \ + detail::dynamic_check(cond, msg); \ + } else if (!(cond)) { \ + impl_destroy_sandbox(); \ + return false; \ + } + + /** + * @brief creates the Wasm sandbox from the given shared library + * + * @param infallible if set to true, the sandbox aborts on failure. If false, + * the sandbox returns creation status as a return value + * @param custom_capacity allows optionally overriding the platform-specified + * maximum size of the wasm heap allowed for this sandbox instance. + * @return true when sandbox is successfully created. false when infallible is + * set to false and sandbox was not successfully created. If infallible is set + * to true, this function will never return false. + */ + inline bool impl_create_sandbox( + bool infallible = true, + const w2c_mem_capacity* custom_capacity = nullptr) + { + FALLIBLE_DYNAMIC_CHECK( + infallible, instance_initialized == false, "Sandbox already initialized"); + + bool minwasi_init_succeeded = true; + + std::call_once(rlbox_wasm2c_initialized, [&]() { + wasm_rt_init(); + minwasi_init_succeeded = minwasi_init(); + }); + + FALLIBLE_DYNAMIC_CHECK( + infallible, minwasi_init_succeeded, "Could not initialize min wasi"); + + const bool minwasi_init_inst_succeeded = minwasi_init_instance(&wasi_env); + FALLIBLE_DYNAMIC_CHECK( + infallible, minwasi_init_inst_succeeded, "Could not initialize min wasi instance"); + + if (custom_capacity) { + FALLIBLE_DYNAMIC_CHECK( + infallible, custom_capacity->is_valid, "Invalid capacity"); + } + + sandbox_memory_info = create_wasm2c_memory( + *RLBOX_WASM_MODULE_TYPE_CURR::initial_memory_pages, custom_capacity); + FALLIBLE_DYNAMIC_CHECK(infallible, + sandbox_memory_info.data != nullptr, + "Could not allocate a heap for the wasm2c sandbox"); + + FALLIBLE_DYNAMIC_CHECK(infallible, + *RLBOX_WASM_MODULE_TYPE_CURR::is_memory_64 == 0, + "Does not support Wasm with memory64"); + + const uint32_t max_table_size = 0xffffffffu; /* this means unlimited */ + wasm_rt_allocate_funcref_table( + &sandbox_callback_table, + *RLBOX_WASM_MODULE_TYPE_CURR::initial_func_elements, + max_table_size); + + sandbox_memory_env.sandbox_memory_info = &sandbox_memory_info; + sandbox_memory_env.sandbox_callback_table = &sandbox_callback_table; + wasi_env.instance_memory = &sandbox_memory_info; + RLBOX_WASM_MODULE_TYPE_CURR::create_instance( + &wasm2c_instance, &sandbox_memory_env, &wasi_env); + + heap_base = reinterpret_cast<uintptr_t>(impl_get_memory_location()); + + if constexpr (sizeof(uintptr_t) != sizeof(uint32_t)) { + // On larger platforms, 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(); + FALLIBLE_DYNAMIC_CHECK(infallible, + (heap_base & heap_offset_mask) == 0, + "Sandbox heap not aligned to 4GB"); + } + + instance_initialized = true; + + return true; + } + +#undef FALLIBLE_DYNAMIC_CHECK + + inline void impl_destroy_sandbox() + { + if (return_slot_size) { + impl_free_in_sandbox(return_slot); + } + + if (instance_initialized) { + instance_initialized = false; + RLBOX_WASM_MODULE_TYPE_CURR::free_instance(&wasm2c_instance); + } + + destroy_wasm2c_memory(&sandbox_memory_info); + wasm_rt_free_funcref_table(&sandbox_callback_table); + minwasi_cleanup_instance(&wasi_env); + } + + template<typename T> + inline void* impl_get_unsandboxed_pointer(T_PointerType p) const + { + if constexpr (std::is_function_v<std::remove_pointer_t<T>>) { + RLBOX_ACQUIRE_UNIQUE_GUARD(lock, callback_mutex); + auto found = slot_assignments.find(p); + if (found != slot_assignments.end()) { + auto ret = found->second; + return const_cast<void*>(ret); + } else { + return nullptr; + } + } 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>>) { + RLBOX_ACQUIRE_UNIQUE_GUARD(lock, callback_mutex); + + uint32_t slot_number = 0; + auto found = internal_callbacks.find(p); + if (found != internal_callbacks.end()) { + slot_number = found->second; + } else { + + slot_number = new_callback_slot(); + wasm_rt_funcref_t func_val; + func_val.func_type = get_wasm2c_func_index(static_cast<T>(nullptr)); + func_val.func = + reinterpret_cast<wasm_rt_function_ptr_t>(const_cast<void*>(p)); + func_val.module_instance = &wasm2c_instance; + + sandbox_callback_table.data[slot_number] = func_val; + internal_callbacks[p] = slot_number; + slot_assignments[slot_number] = p; + } + return static_cast<T_PointerType>(slot_number); + } else { + if constexpr (sizeof(uintptr_t) == sizeof(uint32_t)) { + return static_cast<T_PointerType>(reinterpret_cast<uintptr_t>(p) - + heap_base); + } 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_wasm2c_sandbox* (*expensive_sandbox_finder)( + const void* example_unsandboxed_ptr)) + { + // on 32-bit platforms we don't assume the heap is aligned + if constexpr (sizeof(uintptr_t) == sizeof(uint32_t)) { + auto sandbox = expensive_sandbox_finder(example_unsandboxed_ptr); + return sandbox->template impl_get_unsandboxed_pointer<T>(p); + } else { + 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->template 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_wasm2c_sandbox* (*expensive_sandbox_finder)( + const void* example_unsandboxed_ptr)) + { + // on 32-bit platforms we don't assume the heap is aligned + if constexpr (sizeof(uintptr_t) == sizeof(uint32_t)) { + auto sandbox = expensive_sandbox_finder(example_unsandboxed_ptr); + return sandbox->template impl_get_sandboxed_pointer<T>(p); + } else { + 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->template impl_get_sandboxed_pointer<T>(p); + } else { + // Just clear the memory base to leave the offset + RLBOX_WASM2C_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 sandbox_memory_info.size; } + + inline void* impl_get_memory_location() const + { + return sandbox_memory_info.data; + } + + 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_wasm2c_sandbox_thread_data(); +#endif + auto old_sandbox = thread_data.sandbox; + thread_data.sandbox = this; + auto on_exit = + detail::make_scope_exit([&] { thread_data.sandbox = old_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 = wasm2c_detail::return_argument<T_Converted>; + if constexpr (std::is_class_v<T_Ret>) { + using T_Conv1 = wasm2c_detail::change_return_type<T_Converted, void>; + using T_Conv2 = wasm2c_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_WASM2C_UNUSED(serialize_class_arg); + + using T_ConvNoClass = + wasm2c_detail::change_class_arg_types<T_Converted, T_PointerType>; + + // Handle Point 5 + using T_ConvHeap = wasm2c_detail::prepend_arg_type< + T_ConvNoClass, + typename RLBOX_WASM_MODULE_TYPE_CURR::instance_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_WASM2C_UNUSED(ret); + func_ptr_conv(&wasm2c_instance, serialize_class_arg(params)...); + } else { + ret = func_ptr_conv(&wasm2c_instance, 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) + { + if constexpr (sizeof(size) > sizeof(uint32_t)) { + 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*>( + RLBOX_WASM_MODULE_TYPE_CURR::malloc_address), + 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*>(RLBOX_WASM_MODULE_TYPE_CURR::free_address), + p); + } + +private: + // Should be called with callback_mutex held + uint32_t new_callback_slot() const + { + if (callback_free_list.size() > 0) { + uint32_t ret = callback_free_list.back(); + callback_free_list.pop_back(); + return ret; + } + + const uint32_t curr_size = sandbox_callback_table.size; + + detail::dynamic_check( + curr_size < sandbox_callback_table.max_size, + "Could not find an empty row in Wasm instance table. This would " + "happen if you have registered too many callbacks, or unsandboxed " + "too many function pointers."); + + wasm_rt_funcref_t func_val{ 0 }; + // on success, this returns the previous number of elements in the table + const uint32_t ret = + wasm_rt_grow_funcref_table(&sandbox_callback_table, 1, func_val); + + detail::dynamic_check( + ret != 0 && ret != (uint32_t)-1, + "Adding a new callback slot to the wasm instance failed."); + + // We have expanded the number of slots + // Previous slots size: ret + // New slot is at index: ret + const uint32_t slot_number = ret; + return slot_number; + } + + void free_callback_slot(uint32_t slot) const + { + callback_free_list.push_back(slot); + } + +public: + template<typename T_Ret, typename... T_Args> + inline T_PointerType impl_register_callback(void* key, void* callback) + { + bool found = false; + uint32_t found_loc = 0; + wasm_rt_function_ptr_t chosen_interceptor = nullptr; + + RLBOX_ACQUIRE_UNIQUE_GUARD(lock, callback_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 + wasm2c_detail::compile_time_for<MAX_CALLBACKS>([&](auto I) { + constexpr auto i = I.value; + if (!found && callbacks[i] == nullptr) { + found = true; + found_loc = i; + + if constexpr (std::is_class_v<T_Ret>) { + chosen_interceptor = (wasm_rt_function_ptr_t)( + callback_interceptor_promoted<i, T_Ret, T_Args...>); + } else { + chosen_interceptor = + (wasm_rt_function_ptr_t)(callback_interceptor<i, T_Ret, T_Args...>); + } + } + }); + + 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"); + + wasm_rt_funcref_t func_val; + func_val.func_type = get_wasm2c_func_index<T_Ret, T_Args...>(); + func_val.func = chosen_interceptor; + func_val.module_instance = &wasm2c_instance; + + const uint32_t slot_number = new_callback_slot(); + sandbox_callback_table.data[slot_number] = func_val; + + callback_unique_keys[found_loc] = key; + callbacks[found_loc] = callback; + callback_slot_assignment[found_loc] = slot_number; + slot_assignments[slot_number] = callback; + + return static_cast<T_PointerType>(slot_number); + } + + static inline std::pair<rlbox_wasm2c_sandbox*, void*> + impl_get_executed_callback_sandbox_and_key() + { +#ifdef RLBOX_EMBEDDER_PROVIDES_TLS_STATIC_VARIABLES + auto& thread_data = *get_rlbox_wasm2c_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) { + const uint32_t slot_number = callback_slot_assignment[i]; + wasm_rt_funcref_t func_val{ 0 }; + sandbox_callback_table.data[slot_number] = func_val; + + 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"); + + return; + } +}; + +} // namespace rlbox diff --git a/third_party/rlbox_wasm2c_sandbox/include/rlbox_wasm2c_tls.hpp b/third_party/rlbox_wasm2c_sandbox/include/rlbox_wasm2c_tls.hpp new file mode 100644 index 0000000000..b87ac7bf5f --- /dev/null +++ b/third_party/rlbox_wasm2c_sandbox/include/rlbox_wasm2c_tls.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include <stdint.h> + +namespace rlbox { + +class rlbox_wasm2c_sandbox; + +struct rlbox_wasm2c_sandbox_thread_data +{ + rlbox_wasm2c_sandbox* sandbox; + uint32_t last_callback_invoked; +}; + +#ifdef RLBOX_EMBEDDER_PROVIDES_TLS_STATIC_VARIABLES + +rlbox_wasm2c_sandbox_thread_data* get_rlbox_wasm2c_sandbox_thread_data(); + +# define RLBOX_WASM2C_SANDBOX_STATIC_VARIABLES() \ + thread_local rlbox::rlbox_wasm2c_sandbox_thread_data \ + rlbox_wasm2c_sandbox_thread_info{ 0, 0 }; \ + \ + namespace rlbox { \ + rlbox_wasm2c_sandbox_thread_data* get_rlbox_wasm2c_sandbox_thread_data() \ + { \ + return &rlbox_wasm2c_sandbox_thread_info; \ + } \ + } \ + static_assert(true, "Enforce semi-colon") + +#endif + +} // namespace rlbox diff --git a/third_party/rlbox_wasm2c_sandbox/include/wasm2c_rt_mem.h b/third_party/rlbox_wasm2c_sandbox/include/wasm2c_rt_mem.h new file mode 100644 index 0000000000..e61b9fa3eb --- /dev/null +++ b/third_party/rlbox_wasm2c_sandbox/include/wasm2c_rt_mem.h @@ -0,0 +1,46 @@ +#ifndef WASM_RT_OS_H_ +#define WASM_RT_OS_H_ + +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> +#include <time.h> + +#include "wasm-rt.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + + typedef struct w2c_env + { + wasm_rt_memory_t* sandbox_memory_info; + wasm_rt_funcref_table_t* sandbox_callback_table; + } w2c_env; + + wasm_rt_memory_t* w2c_env_memory(struct w2c_env* instance); + wasm_rt_funcref_table_t* w2c_env_0x5F_indirect_function_table( + struct w2c_env*); + + typedef struct w2c_mem_capacity + { + bool is_valid; + bool is_mem_32; + uint64_t max_pages; + uint64_t max_size; + } w2c_mem_capacity; + + w2c_mem_capacity get_valid_wasm2c_memory_capacity(uint64_t min_capacity, + bool is_mem_32); + + wasm_rt_memory_t create_wasm2c_memory( + uint32_t initial_pages, + const w2c_mem_capacity* custom_capacity); + void destroy_wasm2c_memory(wasm_rt_memory_t* memory); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/third_party/rlbox_wasm2c_sandbox/include/wasm2c_rt_minwasi.h b/third_party/rlbox_wasm2c_sandbox/include/wasm2c_rt_minwasi.h new file mode 100644 index 0000000000..51542b5d1e --- /dev/null +++ b/third_party/rlbox_wasm2c_sandbox/include/wasm2c_rt_minwasi.h @@ -0,0 +1,38 @@ +#ifndef WASM_RT_MINWASI_H_ +#define WASM_RT_MINWASI_H_ + +/* A minimum wasi implementation supporting only stdin, stdout, stderr, argv + * (upto 100 args) and clock functions. */ + +#include <stdbool.h> +#include <stdint.h> + +#include "wasm-rt.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + + typedef struct w2c_wasi__snapshot__preview1 + { + wasm_rt_memory_t* instance_memory; + + uint32_t main_argc; + const char** main_argv; + + uint32_t env_count; + const char** env; + + void* clock_data; + } w2c_wasi__snapshot__preview1; + + bool minwasi_init(); + bool minwasi_init_instance(w2c_wasi__snapshot__preview1* wasi_data); + void minwasi_cleanup_instance(w2c_wasi__snapshot__preview1* wasi_data); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/third_party/rlbox_wasm2c_sandbox/src/wasm2c_rt_mem.c b/third_party/rlbox_wasm2c_sandbox/src/wasm2c_rt_mem.c new file mode 100644 index 0000000000..1bdf6f715c --- /dev/null +++ b/third_party/rlbox_wasm2c_sandbox/src/wasm2c_rt_mem.c @@ -0,0 +1,454 @@ +#include "wasm2c_rt_mem.h" +#include "wasm-rt.h" + +#include <errno.h> +#include <inttypes.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +enum +{ + MMAP_PROT_NONE = 0, + MMAP_PROT_READ = 1, + MMAP_PROT_WRITE = 2, + MMAP_PROT_EXEC = 4 +}; + +/* Memory map flags */ +enum +{ + MMAP_MAP_NONE = 0, + /* Put the mapping into 0 to 2 G, supported only on x86_64 */ + MMAP_MAP_32BIT = 1, + /* Don't interpret addr as a hint: place the mapping at exactly + that address. */ + MMAP_MAP_FIXED = 2 +}; + +// Try reserving an aligned memory space. +// Returns pointer to allocated space on success, 0 on failure. +static void* os_mmap_aligned(void* addr, + size_t requested_length, + int prot, + int flags, + size_t alignment, + size_t alignment_offset); +// Unreserve the memory space +static void os_munmap(void* addr, size_t size); +// Allocates and sets the permissions on the previously reserved memory space +// Returns 0 on success, non zero on failure. +static int os_mmap_commit(void* curr_heap_end_pointer, + size_t expanded_size, + int prot); + +wasm_rt_memory_t* w2c_env_memory(struct w2c_env* instance) +{ + return instance->sandbox_memory_info; +} + +wasm_rt_funcref_table_t* w2c_env_0x5F_indirect_function_table( + struct w2c_env* instance) +{ + return instance->sandbox_callback_table; +} + +#define WASM_PAGE_SIZE 65536 +#define RLBOX_FOUR_GIG 0x100000000ull + +#if UINTPTR_MAX == 0xffffffffffffffff +// Guard page of 4GiB +# define WASM_HEAP_GUARD_PAGE_SIZE 0x100000000ull +// Heap aligned to 4GB +# define WASM_HEAP_ALIGNMENT 0x100000000ull +// By default max heap is 4GB +# define WASM_HEAP_DEFAULT_MAX_PAGES 65536 +#elif UINTPTR_MAX == 0xffffffff +// No guard pages +# define WASM_HEAP_GUARD_PAGE_SIZE 0 +// Unaligned heap +# define WASM_HEAP_ALIGNMENT 0 +// Default max heap is 16MB +# define WASM_HEAP_DEFAULT_MAX_PAGES 256 +#else +# error "Unknown pointer size" +#endif + +static uint64_t compute_heap_reserve_space(uint32_t chosen_max_pages) +{ + const uint64_t heap_reserve_size = + ((uint64_t)chosen_max_pages) * WASM_PAGE_SIZE + WASM_HEAP_GUARD_PAGE_SIZE; + return heap_reserve_size; +} + +w2c_mem_capacity get_valid_wasm2c_memory_capacity(uint64_t min_capacity, + bool is_mem_32) +{ + const w2c_mem_capacity err_val = { false /* is_valid */, + false /* is_mem_32 */, + 0 /* max_pages */, + 0 /* max_size */ }; + + // We do not handle memory 64 + if (!is_mem_32) { + return err_val; + } + + const uint64_t default_capacity = + ((uint64_t)WASM_HEAP_DEFAULT_MAX_PAGES) * WASM_PAGE_SIZE; + + if (min_capacity <= default_capacity) { + // Handle 0 case and small values + const w2c_mem_capacity ret = { true /* is_valid */, + true /* is_mem_32 */, + WASM_HEAP_DEFAULT_MAX_PAGES /* max_pages */, + default_capacity /* max_size */ }; + return ret; + } else if (min_capacity > UINT32_MAX) { + // Handle out of range values + return err_val; + } + + const uint64_t page_size_minus_1 = WASM_PAGE_SIZE - 1; + // Get number of pages greater than min_capacity + const uint64_t capacity_pages = ((min_capacity - 1) / page_size_minus_1) + 1; + + const w2c_mem_capacity ret = { true /* is_valid */, + true /* is_mem_32 */, + capacity_pages /* max_pages */, + capacity_pages * + WASM_PAGE_SIZE /* max_size */ }; + return ret; +} + +wasm_rt_memory_t create_wasm2c_memory(uint32_t initial_pages, + const w2c_mem_capacity* custom_capacity) +{ + + if (custom_capacity && !custom_capacity->is_valid) { + wasm_rt_memory_t ret = { 0 }; + return ret; + } + + const uint32_t byte_length = initial_pages * WASM_PAGE_SIZE; + const uint64_t chosen_max_pages = + custom_capacity ? custom_capacity->max_pages : WASM_HEAP_DEFAULT_MAX_PAGES; + const uint64_t heap_reserve_size = + compute_heap_reserve_space(chosen_max_pages); + + uint8_t* data = 0; + const uint64_t retries = 10; + for (uint64_t i = 0; i < retries; i++) { + data = (uint8_t*)os_mmap_aligned(0, + heap_reserve_size, + MMAP_PROT_NONE, + MMAP_MAP_NONE, + WASM_HEAP_ALIGNMENT, + 0 /* alignment_offset */); + if (data) { + int ret = + os_mmap_commit(data, byte_length, MMAP_PROT_READ | MMAP_PROT_WRITE); + if (ret != 0) { + // failed to set permissions + os_munmap(data, heap_reserve_size); + data = 0; + } + break; + } + } + + wasm_rt_memory_t ret; + ret.data = data; + ret.max_pages = chosen_max_pages; + ret.pages = initial_pages; + ret.size = byte_length; + return ret; +} + +void destroy_wasm2c_memory(wasm_rt_memory_t* memory) +{ + if (memory->data != 0) { + const uint64_t heap_reserve_size = + compute_heap_reserve_space(memory->max_pages); + os_munmap(memory->data, heap_reserve_size); + memory->data = 0; + } +} + +#undef WASM_HEAP_DEFAULT_MAX_PAGES +#undef WASM_HEAP_ALIGNMENT +#undef WASM_HEAP_GUARD_PAGE_SIZE +#undef RLBOX_FOUR_GIG +#undef WASM_PAGE_SIZE + +// Based on +// https://web.archive.org/web/20191012035921/http://nadeausoftware.com/articles/2012/01/c_c_tip_how_use_compiler_predefined_macros_detect_operating_system#BSD +// Check for windows (non cygwin) environment +#if defined(_WIN32) + +# include <windows.h> + +static size_t os_getpagesize() +{ + SYSTEM_INFO S; + GetNativeSystemInfo(&S); + return S.dwPageSize; +} + +static void* win_mmap(void* hint, + size_t size, + int prot, + int flags, + DWORD alloc_flag) +{ + DWORD flProtect = PAGE_NOACCESS; + size_t request_size, page_size; + void* addr; + + page_size = os_getpagesize(); + request_size = (size + page_size - 1) & ~(page_size - 1); + + if (request_size < size) + /* integer overflow */ + return NULL; + + if (request_size == 0) + request_size = page_size; + + if (prot & MMAP_PROT_EXEC) { + if (prot & MMAP_PROT_WRITE) + flProtect = PAGE_EXECUTE_READWRITE; + else + flProtect = PAGE_EXECUTE_READ; + } else if (prot & MMAP_PROT_WRITE) + flProtect = PAGE_READWRITE; + else if (prot & MMAP_PROT_READ) + flProtect = PAGE_READONLY; + + addr = VirtualAlloc((LPVOID)hint, request_size, alloc_flag, flProtect); + return addr; +} + +static void* os_mmap_aligned(void* addr, + size_t requested_length, + int prot, + int flags, + size_t alignment, + size_t alignment_offset) +{ + size_t padded_length = requested_length + alignment + alignment_offset; + uintptr_t unaligned = + (uintptr_t)win_mmap(addr, padded_length, prot, flags, MEM_RESERVE); + + if (!unaligned) { + return (void*)unaligned; + } + + // Round up the next address that has addr % alignment = 0 + const size_t alignment_corrected = alignment == 0 ? 1 : alignment; + uintptr_t aligned_nonoffset = + (unaligned + (alignment_corrected - 1)) & ~(alignment_corrected - 1); + + // Currently offset 0 is aligned according to alignment + // Alignment needs to be enforced at the given offset + uintptr_t aligned = 0; + if ((aligned_nonoffset - alignment_offset) >= unaligned) { + aligned = aligned_nonoffset - alignment_offset; + } else { + aligned = aligned_nonoffset - alignment_offset + alignment; + } + + if (aligned == unaligned && padded_length == requested_length) { + return (void*)aligned; + } + + // Sanity check + if (aligned < unaligned || + (aligned + (requested_length - 1)) > (unaligned + (padded_length - 1)) || + (aligned + alignment_offset) % alignment_corrected != 0) { + os_munmap((void*)unaligned, padded_length); + return NULL; + } + + // windows does not support partial unmapping, so unmap and remap + os_munmap((void*)unaligned, padded_length); + aligned = (uintptr_t)win_mmap( + (void*)aligned, requested_length, prot, flags, MEM_RESERVE); + return (void*)aligned; +} + +static void os_munmap(void* addr, size_t size) +{ + DWORD alloc_flag = MEM_RELEASE; + if (addr) { + if (VirtualFree(addr, 0, alloc_flag) == 0) { + size_t page_size = os_getpagesize(); + size_t request_size = (size + page_size - 1) & ~(page_size - 1); + int64_t curr_err = errno; + printf("os_munmap error addr:%p, size:0x%zx, errno:%" PRId64 "\n", + addr, + request_size, + curr_err); + } + } +} + +static int os_mmap_commit(void* curr_heap_end_pointer, + size_t expanded_size, + int prot) +{ + uintptr_t addr = (uintptr_t)win_mmap( + curr_heap_end_pointer, expanded_size, prot, MMAP_MAP_NONE, MEM_COMMIT); + int ret = addr ? 0 : -1; + return ret; +} + +#elif !defined(_WIN32) && (defined(__unix__) || defined(__unix) || \ + (defined(__APPLE__) && defined(__MACH__))) + +# include <sys/mman.h> +# include <unistd.h> + +static size_t os_getpagesize() +{ + return getpagesize(); +} + +static void* os_mmap(void* hint, size_t size, int prot, int flags) +{ + int map_prot = PROT_NONE; + int map_flags = MAP_ANONYMOUS | MAP_PRIVATE; + uint64_t request_size, page_size; + void* addr; + + page_size = (uint64_t)os_getpagesize(); + request_size = (size + page_size - 1) & ~(page_size - 1); + + if ((size_t)request_size < size) + /* integer overflow */ + return NULL; + + if (request_size > 16 * (uint64_t)UINT32_MAX) + /* At most 16 G is allowed */ + return NULL; + + if (prot & MMAP_PROT_READ) + map_prot |= PROT_READ; + + if (prot & MMAP_PROT_WRITE) + map_prot |= PROT_WRITE; + + if (prot & MMAP_PROT_EXEC) + map_prot |= PROT_EXEC; + +# if defined(BUILD_TARGET_X86_64) || defined(BUILD_TARGET_AMD_64) +# ifndef __APPLE__ + if (flags & MMAP_MAP_32BIT) + map_flags |= MAP_32BIT; +# endif +# endif + + if (flags & MMAP_MAP_FIXED) + map_flags |= MAP_FIXED; + + addr = mmap(hint, request_size, map_prot, map_flags, -1, 0); + + if (addr == MAP_FAILED) + return NULL; + + return addr; +} + +static void* os_mmap_aligned(void* addr, + size_t requested_length, + int prot, + int flags, + size_t alignment, + size_t alignment_offset) +{ + size_t padded_length = requested_length + alignment + alignment_offset; + uintptr_t unaligned = (uintptr_t)os_mmap(addr, padded_length, prot, flags); + + if (!unaligned) { + return (void*)unaligned; + } + + // Round up the next address that has addr % alignment = 0 + const size_t alignment_corrected = alignment == 0 ? 1 : alignment; + uintptr_t aligned_nonoffset = + (unaligned + (alignment_corrected - 1)) & ~(alignment_corrected - 1); + + // Currently offset 0 is aligned according to alignment + // Alignment needs to be enforced at the given offset + uintptr_t aligned = 0; + if ((aligned_nonoffset - alignment_offset) >= unaligned) { + aligned = aligned_nonoffset - alignment_offset; + } else { + aligned = aligned_nonoffset - alignment_offset + alignment; + } + + // Sanity check + if (aligned < unaligned || + (aligned + (requested_length - 1)) > (unaligned + (padded_length - 1)) || + (aligned + alignment_offset) % alignment_corrected != 0) { + os_munmap((void*)unaligned, padded_length); + return NULL; + } + + { + size_t unused_front = aligned - unaligned; + if (unused_front != 0) { + os_munmap((void*)unaligned, unused_front); + } + } + + { + size_t unused_back = + (unaligned + (padded_length - 1)) - (aligned + (requested_length - 1)); + if (unused_back != 0) { + os_munmap((void*)(aligned + requested_length), unused_back); + } + } + + return (void*)aligned; +} + +static void os_munmap(void* addr, size_t size) +{ + uint64_t page_size = (uint64_t)os_getpagesize(); + uint64_t request_size = (size + page_size - 1) & ~(page_size - 1); + + if (addr) { + if (munmap(addr, request_size)) { + printf("os_munmap error addr:%p, size:0x%" PRIx64 ", errno:%d\n", + addr, + request_size, + errno); + } + } +} + +static int os_mmap_commit(void* addr, size_t size, int prot) +{ + int map_prot = PROT_NONE; + uint64_t page_size = (uint64_t)os_getpagesize(); + uint64_t request_size = (size + page_size - 1) & ~(page_size - 1); + + if (!addr) + return 0; + + if (prot & MMAP_PROT_READ) + map_prot |= PROT_READ; + + if (prot & MMAP_PROT_WRITE) + map_prot |= PROT_WRITE; + + if (prot & MMAP_PROT_EXEC) + map_prot |= PROT_EXEC; + + return mprotect(addr, request_size, map_prot); +} + +#else +# error "Unknown OS" +#endif diff --git a/third_party/rlbox_wasm2c_sandbox/src/wasm2c_rt_minwasi.c b/third_party/rlbox_wasm2c_sandbox/src/wasm2c_rt_minwasi.c new file mode 100644 index 0000000000..9289cd75b1 --- /dev/null +++ b/third_party/rlbox_wasm2c_sandbox/src/wasm2c_rt_minwasi.c @@ -0,0 +1,799 @@ +/* A minimum wasi implementation supporting only stdin, stdout, stderr, argv + * (upto 1000 args), env (upto 1000 env), and clock functions. */ + +#include <assert.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#ifdef _WIN32 +# include <windows.h> +#endif + +#if defined(__APPLE__) && defined(__MACH__) +// Macs priors to OSX 10.12 don't have the clock functions. So we will use mac +// specific options +# include <mach/mach_time.h> +# include <sys/time.h> +#endif + +#include "wasm-rt.h" +#include "wasm2c_rt_minwasi.h" + +#ifndef WASM_RT_CORE_TYPES_DEFINED +# define WASM_RT_CORE_TYPES_DEFINED +typedef uint8_t u8; +typedef int8_t s8; +typedef uint16_t u16; +typedef int16_t s16; +typedef uint32_t u32; +typedef int32_t s32; +typedef uint64_t u64; +typedef int64_t s64; +typedef float f32; +typedef double f64; +#endif + +#ifndef UNLIKELY +# if defined(__GNUC__) +# define UNLIKELY(x) __builtin_expect(!!(x), 0) +# define LIKELY(x) __builtin_expect(!!(x), 1) +# else +# define UNLIKELY(x) (x) +# define LIKELY(x) (x) +# endif +#endif + +#define TRAP(x) wasm_rt_trap(WASM_RT_TRAP_##x) + +#define WASI_MEMACCESS(mem, a) ((void*)&(mem->data[a])) + +#define WASI_MEMCHECK_SIZE(mem, a, sz) \ + if (UNLIKELY(((u64)(a)) + sz > mem->size)) \ + TRAP(OOB) + +#define WASI_CHECK_COPY(mem, a, sz, src) \ + do { \ + WASI_MEMCHECK_SIZE(mem, a, sz); \ + memcpy(WASI_MEMACCESS(mem, a), src, sz); \ + } while (0) + +#define WASI_MEMCHECK(mem, a, t) WASI_MEMCHECK_SIZE(mem, a, sizeof(t)) + +#define DEFINE_WASI_LOAD(name, t1, t2, t3) \ + static inline t3 name(wasm_rt_memory_t* mem, u64 addr) \ + { \ + WASI_MEMCHECK(mem, addr, t1); \ + t1 result; \ + memcpy(&result, WASI_MEMACCESS(mem, addr), sizeof(t1)); \ + return (t3)(t2)result; \ + } + +#define DEFINE_WASI_STORE(name, t1, t2) \ + static inline void name(wasm_rt_memory_t* mem, u64 addr, t2 value) \ + { \ + WASI_MEMCHECK(mem, addr, t1); \ + t1 wrapped = (t1)value; \ + memcpy(WASI_MEMACCESS(mem, addr), &wrapped, sizeof(t1)); \ + } + +DEFINE_WASI_LOAD(wasm_i32_load, u32, u32, u32); +DEFINE_WASI_STORE(wasm_i32_store, u32, u32); +DEFINE_WASI_STORE(wasm_i64_store, u64, u64); + +static bool safe_add_u32(u32* ret, u32 a, u32 b) +{ + if (UINT32_MAX - a < b) { + *ret = 0; + return false; + } + *ret = a + b; + return true; +} + +// clang-format off + +////////////// Supported WASI APIs +// +// Clock operations +// ---------------- +// errno_t clock_res_get(void* ctx, clockid_t clock_id, timestamp_t* resolution); +// errno_t clock_time_get(void* ctx, clockid_t clock_id, timestamp_t precision, timestamp_t* time); +// +// File operations +// ---------------- +// Only the default descriptors of STDIN, STDOUT, STDERR are allowed by the +// runtime. +// +// errno_t fd_prestat_get(void* ctx, fd_t fd, prestat_t* buf); +// errno_t fd_read(void* ctx, fd_t fd, const iovec_t* iovs, size_t iovs_len, size_t* nread); +// errno_t fd_write(void* ctx, fd_t fd, const ciovec_t* iovs, size_t iovs_len, size_t* nwritten); + +////////////// Partially supported WASI APIs +// +// App environment operations +// -------------------------- +// These APIs work but return an empty buffer +// +// errno_t args_get(void* ctx, char** argv, char* argv_buf); +// errno_t args_sizes_get(void* ctx, size_t* argc, size_t* argv_buf_size); +// errno_t environ_get(void* ctx, char** environment, char* environ_buf); +// errno_t environ_sizes_get(void* ctx, size_t* environ_count, size_t* environ_buf_size); +// +// Proc exit operation +// ------------------- +// This is a no-op here in this runtime as the focus is on library +// sandboxing +// +// errno_t proc_exit(void* ctx, exitcode_t rval); + +////////////// Unsupported WASI APIs +// errno_t fd_advise(void* ctx, fd_t fd, filesize_t offset, filesize_t len, advice_t advice); +// errno_t fd_allocate(void* ctx, fd_t fd, filesize_t offset, filesize_t len); +// errno_t fd_close(void* ctx, fd_t fd); +// errno_t fd_datasync(void* ctx, fd_t fd); +// errno_t fd_fdstat_get(void* ctx, fd_t fd, fdstat_t* buf); +// errno_t fd_fdstat_set_flags(void* ctx, fd_t fd, fdflags_t flags); +// errno_t fd_fdstat_set_rights(void* ctx, fd_t fd, rights_t fs_rights_base, rights_t fs_rights_inheriting); +// errno_t fd_filestat_get(void* ctx, fd_t fd, filestat_t* buf); +// errno_t fd_filestat_set_size(void* ctx, fd_t fd, filesize_t st_size); +// errno_t fd_filestat_set_times(void* ctx, fd_t fd, timestamp_t st_atim, timestamp_t st_mtim, fstflags_t fst_flags); +// errno_t fd_pread(void* ctx, fd_t fd, const iovec_t* iovs, size_t iovs_len, filesize_t offset, size_t* nread); +// errno_t fd_prestat_dir_name(void* ctx, fd_t fd, char* path, size_t path_len); +// errno_t fd_pwrite(void* ctx, fd_t fd, const ciovec_t* iovs, size_t iovs_len, filesize_t offset, size_t* nwritten); +// errno_t fd_readdir(void* ctx, fd_t fd, void* buf, size_t buf_len, dircookie_t cookie, size_t* bufused); +// errno_t fd_renumber(void* ctx, fd_t from, fd_t to); +// errno_t fd_seek(void* ctx, fd_t fd, filedelta_t offset, whence_t whence, filesize_t* newoffset); +// errno_t fd_sync(void* ctx, fd_t fd); +// errno_t fd_tell(void* ctx, fd_t fd, filesize_t* offset); +// errno_t path_create_directory(void* ctx, fd_t fd, const char* path, size_t path_len); +// errno_t path_filestat_get(void* ctx, fd_t fd, lookupflags_t flags, const char* path, size_t path_len, filestat_t* buf); +// errno_t path_filestat_set_times(void* ctx, fd_t fd, lookupflags_t flags, const char* path, size_t path_len, timestamp_t st_atim, timestamp_t st_mtim, fstflags_t fst_flags); +// errno_t path_link(void* ctx, fd_t old_fd, lookupflags_t old_flags, const char* old_path, size_t old_path_len, fd_t new_fd, const char* new_path, size_t new_path_len); +// errno_t path_open(void* ctx, fd_t dirfd, lookupflags_t dirflags, const char* path, size_t path_len, oflags_t o_flags, rights_t fs_rights_base, rights_t fs_rights_inheriting, fdflags_t fs_flags, fd_t* fd); +// errno_t path_readlink(void* ctx, fd_t fd, const char* path, size_t path_len, char* buf, size_t buf_len, size_t* bufused); +// errno_t path_remove_directory(void* ctx, fd_t fd, const char* path, size_t path_len); +// errno_t path_rename(void* ctx, fd_t old_fd, const char* old_path, size_t old_path_len, fd_t new_fd, const char* new_path, size_t new_path_len); +// errno_t path_symlink(void* ctx, const char* old_path, size_t old_path_len, fd_t fd, const char* new_path, size_t new_path_len); +// errno_t path_unlink_file(void* ctx, fd_t fd, const char* path, size_t path_len); +// errno_t poll_oneoff(void* ctx, const subscription_t* in, event_t* out, size_t nsubscriptions, size_t* nevents); +// errno_t proc_raise(void* ctx, signal_t sig); +// errno_t random_get(void* ctx, void* buf, size_t buf_len); +// errno_t sched_yield(t* uvwasi); +// errno_t sock_accept(void* ctx, fd_t sock, flags_t fdflags, fd* fd); +// errno_t sock_recv(void* ctx, fd_t sock, const iovec_t* ri_data, size_t ri_data_len, riflags_t ri_flags, size_t* ro_datalen, roflags_t* ro_flags); +// errno_t sock_send(void* ctx, fd_t sock, const ciovec_t* si_data, size_t si_data_len, siflags_t si_flags, size_t* so_datalen); +// errno_t sock_shutdown(void* ctx, fd_t sock, sdflags_t how); + +// clang-format on + +// Success +#define WASI_SUCCESS 0 +// Bad file descriptor. +#define WASI_BADF_ERROR 8 +// Invalid argument +#define WASI_INVAL_ERROR 28 +// Operation not permitted. +#define WASI_PERM_ERROR 63 +// Syscall not implemented +#define WASI_NOSYS_ERROR 53 + +#define WASI_RET_ERR_ON_FAIL(exp) \ + if (!(exp)) { \ + return WASI_INVAL_ERROR; \ + } + +///////////////////////////////////////////////////////////// +// Clock operations +///////////////////////////////////////////////////////////// + +#if defined(_WIN32) + +typedef struct +{ + LARGE_INTEGER counts_per_sec; /* conversion factor */ +} wasi_win_clock_info_t; + +static wasi_win_clock_info_t g_wasi_win_clock_info; +static int g_os_data_initialized = 0; + +static bool os_clock_init() +{ + // From here: + // https://stackoverflow.com/questions/5404277/porting-clock-gettime-to-windows/38212960#38212960 + if (QueryPerformanceFrequency(&g_wasi_win_clock_info.counts_per_sec) == 0) { + return false; + } + g_os_data_initialized = 1; + return true; +} + +static bool os_clock_init_instance(void** clock_data_pointer) +{ + if (!g_os_data_initialized) { + os_clock_init(); + } + + wasi_win_clock_info_t* alloc = + (wasi_win_clock_info_t*)malloc(sizeof(wasi_win_clock_info_t)); + if (!alloc) { + return false; + } + memcpy(alloc, &g_wasi_win_clock_info, sizeof(wasi_win_clock_info_t)); + *clock_data_pointer = alloc; + return true; +} + +static void os_clock_cleanup_instance(void** clock_data_pointer) +{ + if (*clock_data_pointer == 0) { + free(*clock_data_pointer); + *clock_data_pointer = 0; + } +} + +static int os_clock_gettime(void* clock_data, + int clock_id, + struct timespec* out_struct) +{ + wasi_win_clock_info_t* alloc = (wasi_win_clock_info_t*)clock_data; + + LARGE_INTEGER count; + (void)clock_id; + + if (alloc->counts_per_sec.QuadPart <= 0 || + QueryPerformanceCounter(&count) == 0) { + return -1; + } + +# define BILLION 1000000000LL + out_struct->tv_sec = count.QuadPart / alloc->counts_per_sec.QuadPart; + out_struct->tv_nsec = + ((count.QuadPart % alloc->counts_per_sec.QuadPart) * BILLION) / + alloc->counts_per_sec.QuadPart; +# undef BILLION + + return 0; +} + +static int os_clock_getres(void* clock_data, + int clock_id, + struct timespec* out_struct) +{ + (void)clock_id; + out_struct->tv_sec = 0; + out_struct->tv_nsec = 1000; + return 0; +} + +#elif defined(__APPLE__) && defined(__MACH__) + +typedef struct +{ + mach_timebase_info_data_t timebase; /* numer = 0, denom = 0 */ + struct timespec inittime; /* nanoseconds since 1-Jan-1970 to init() */ + uint64_t initclock; /* ticks since boot to init() */ +} wasi_mac_clock_info_t; + +static wasi_mac_clock_info_t g_wasi_mac_clock_info; +static int g_os_data_initialized = 0; + +static bool os_clock_init() +{ + // From here: + // https://stackoverflow.com/questions/5167269/clock-gettime-alternative-in-mac-os-x/21352348#21352348 + if (mach_timebase_info(&g_wasi_mac_clock_info.timebase) != 0) { + return false; + } + + // microseconds since 1 Jan 1970 + struct timeval micro; + if (gettimeofday(µ, NULL) != 0) { + return false; + } + + g_wasi_mac_clock_info.initclock = mach_absolute_time(); + + g_wasi_mac_clock_info.inittime.tv_sec = micro.tv_sec; + g_wasi_mac_clock_info.inittime.tv_nsec = micro.tv_usec * 1000; + + g_os_data_initialized = 1; + return true; +} + +static bool os_clock_init_instance(void** clock_data_pointer) +{ + if (!g_os_data_initialized) { + os_clock_init(); + } + + wasi_mac_clock_info_t* alloc = + (wasi_mac_clock_info_t*)malloc(sizeof(wasi_mac_clock_info_t)); + if (!alloc) { + return false; + } + memcpy(alloc, &g_wasi_mac_clock_info, sizeof(wasi_mac_clock_info_t)); + *clock_data_pointer = alloc; + return true; +} + +static void os_clock_cleanup_instance(void** clock_data_pointer) +{ + if (*clock_data_pointer == 0) { + free(*clock_data_pointer); + *clock_data_pointer = 0; + } +} + +static int os_clock_gettime(void* clock_data, + int clock_id, + struct timespec* out_struct) +{ + int ret = 0; + wasi_mac_clock_info_t* alloc = (wasi_mac_clock_info_t*)clock_data; + + // From here: + // https://stackoverflow.com/questions/5167269/clock-gettime-alternative-in-mac-os-x/21352348#21352348 + + (void)clock_id; + // ticks since init + uint64_t clock = mach_absolute_time() - alloc->initclock; + // nanoseconds since init + uint64_t nano = clock * (uint64_t)(alloc->timebase.numer) / + (uint64_t)(alloc->timebase.denom); + *out_struct = alloc->inittime; + +# define BILLION 1000000000L + out_struct->tv_sec += nano / BILLION; + out_struct->tv_nsec += nano % BILLION; + // normalize + out_struct->tv_sec += out_struct->tv_nsec / BILLION; + out_struct->tv_nsec = out_struct->tv_nsec % BILLION; +# undef BILLION + return ret; +} + +static int os_clock_getres(void* clock_data, + int clock_id, + struct timespec* out_struct) +{ + int ret = 0; + (void)clock_id; + out_struct->tv_sec = 0; + out_struct->tv_nsec = 1; + return ret; +} + +#else + +static bool os_clock_init() +{ + return true; +} + +static bool os_clock_init_instance(void** clock_data_pointer) +{ + (void)clock_data_pointer; + return true; +} + +static void os_clock_cleanup_instance(void** clock_data_pointer) +{ + (void)clock_data_pointer; +} + +static int os_clock_gettime(void* clock_data, + int clock_id, + struct timespec* out_struct) +{ + (void)clock_data; + int ret = clock_gettime(clock_id, out_struct); + return ret; +} + +static int os_clock_getres(void* clock_data, + int clock_id, + struct timespec* out_struct) +{ + (void)clock_data; + int ret = clock_getres(clock_id, out_struct); + return ret; +} + +#endif + +#define WASM_CLOCK_REALTIME 0 +#define WASM_CLOCK_MONOTONIC 1 +#define WASM_CLOCK_PROCESS_CPUTIME 2 +#define WASM_CLOCK_THREAD_CPUTIME_ID 3 + +static int check_clock(u32 clock_id) +{ + return clock_id == WASM_CLOCK_REALTIME || clock_id == WASM_CLOCK_MONOTONIC || + clock_id == WASM_CLOCK_PROCESS_CPUTIME || + clock_id == WASM_CLOCK_THREAD_CPUTIME_ID; +} + +// out is a pointer to a u64 timestamp in nanoseconds +// https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#-timestamp-u64 +u32 w2c_wasi__snapshot__preview1_clock_time_get( + w2c_wasi__snapshot__preview1* wasi_data, + u32 clock_id, + u64 precision, + u32 out) +{ + if (!check_clock(clock_id)) { + return WASI_INVAL_ERROR; + } + + struct timespec out_struct; + int ret = os_clock_gettime(wasi_data->clock_data, clock_id, &out_struct); + u64 result = + ((u64)out_struct.tv_sec) * 1000 * 1000 * 1000 + ((u64)out_struct.tv_nsec); + wasm_i64_store(wasi_data->instance_memory, out, result); + return ret; +} + +u32 w2c_wasi__snapshot__preview1_clock_res_get( + w2c_wasi__snapshot__preview1* wasi_data, + u32 clock_id, + u32 out) +{ + if (!check_clock(clock_id)) { + return WASI_INVAL_ERROR; + } + + struct timespec out_struct; + int ret = os_clock_getres(wasi_data->clock_data, clock_id, &out_struct); + u64 result = + ((u64)out_struct.tv_sec) * 1000 * 1000 * 1000 + ((u64)out_struct.tv_nsec); + wasm_i64_store(wasi_data->instance_memory, out, result); + return ret; +} + +///////////////////////////////////////////////////////////// +////////// File operations +///////////////////////////////////////////////////////////// + +// Only allow stdin (0), stdout (1), stderr(2) + +#define WASM_STDIN 0 +#define WASM_STDOUT 1 +#define WASM_STDERR 2 + +u32 w2c_wasi__snapshot__preview1_fd_prestat_get( + w2c_wasi__snapshot__preview1* wasi_data, + u32 fd, + u32 prestat) +{ + if (fd == WASM_STDIN || fd == WASM_STDOUT || fd == WASM_STDERR) { + return WASI_PERM_ERROR; + } + return WASI_BADF_ERROR; +} + +u32 w2c_wasi__snapshot__preview1_fd_write( + w2c_wasi__snapshot__preview1* wasi_data, + u32 fd, + u32 iov, + u32 iovcnt, + u32 pnum) +{ + if (fd != WASM_STDOUT && fd != WASM_STDERR) { + return WASI_BADF_ERROR; + } + + u32 num = 0; + for (u32 i = 0; i < iovcnt; i++) { + u32 ptr = wasm_i32_load(wasi_data->instance_memory, iov + i * 8); + u32 len = wasm_i32_load(wasi_data->instance_memory, iov + i * 8 + 4); + + WASI_MEMCHECK_SIZE(wasi_data->instance_memory, ptr, len); + + size_t result = fwrite(WASI_MEMACCESS(wasi_data->instance_memory, ptr), + 1 /* size */, + len /* n */, + fd == WASM_STDOUT ? stdout : stderr); + + // Guaranteed by fwrite + assert(result <= len); + + WASI_RET_ERR_ON_FAIL(safe_add_u32(&num, num, (u32)result)); + + if (((u32)result) != len) { + wasm_i32_store(wasi_data->instance_memory, pnum, num); + return WASI_PERM_ERROR; + } + } + + wasm_i32_store(wasi_data->instance_memory, pnum, num); + return WASI_SUCCESS; +} + +u32 w2c_wasi__snapshot__preview1_fd_read( + w2c_wasi__snapshot__preview1* wasi_data, + u32 fd, + u32 iov, + u32 iovcnt, + u32 pnum) +{ + if (fd != WASM_STDIN) { + return WASI_BADF_ERROR; + } + + u32 num = 0; + for (u32 i = 0; i < iovcnt; i++) { + u32 ptr = wasm_i32_load(wasi_data->instance_memory, iov + i * 8); + u32 len = wasm_i32_load(wasi_data->instance_memory, iov + i * 8 + 4); + + WASI_MEMCHECK_SIZE(wasi_data->instance_memory, ptr, len); + size_t result = fread(WASI_MEMACCESS(wasi_data->instance_memory, ptr), + 1 /* size */, + len /* n */, + stdin); + + // Guaranteed by fwrite + assert(result <= len); + + WASI_RET_ERR_ON_FAIL(safe_add_u32(&num, num, (u32)result)); + + if (((u32)result) != len) { + break; // nothing more to read + } + } + wasm_i32_store(wasi_data->instance_memory, pnum, num); + return WASI_SUCCESS; +} + +///////////////////////////////////////////////////////////// +// App environment operations +///////////////////////////////////////////////////////////// + +#define ARGV_AND_ENV_LIMIT 1000 + +static u32 strings_sizes_get(wasm_rt_memory_t* instance_memory, + const char* name, + u32 p_str_count, + u32 p_str_buff_size, + u32 string_count, + const char** strings) +{ + u32 chosen_count = string_count; + if (chosen_count > ARGV_AND_ENV_LIMIT) { + chosen_count = ARGV_AND_ENV_LIMIT; + printf("Truncated %s args to %d\n", name, ARGV_AND_ENV_LIMIT); + } + + u32 curr_buf_size = 0; + for (u32 i = 0; i < chosen_count; i++) { + size_t original_len = strlen(strings[i]); + // len has to be at most u32 - 1 + WASI_RET_ERR_ON_FAIL(original_len < (size_t)UINT32_MAX); + + u32 len = (u32)original_len; + u32 len_plus_nullchar = len + 1; + + WASI_RET_ERR_ON_FAIL( + safe_add_u32(&curr_buf_size, curr_buf_size, len_plus_nullchar)); + } + + wasm_i32_store(instance_memory, p_str_count, chosen_count); + wasm_i32_store(instance_memory, p_str_buff_size, curr_buf_size); + return WASI_SUCCESS; +} + +static u32 strings_get(wasm_rt_memory_t* instance_memory, + const char* name, + u32 p_str_arr, + u32 p_str_buf, + u32 string_count, + const char** strings) +{ + u32 chosen_count = string_count; + if (chosen_count > ARGV_AND_ENV_LIMIT) { + chosen_count = ARGV_AND_ENV_LIMIT; + // Warning is already printed in get_size + } + + u32 curr_buf_loc = 0; + + for (u32 i = 0; i < chosen_count; i++) { + // Implement: p_str_arr[i] = p_str_buf[curr_buf_loc] + u32 target_argv_i_ref; + WASI_RET_ERR_ON_FAIL(safe_add_u32(&target_argv_i_ref, p_str_arr, i * 4)); + + u32 target_buf_curr_ref; + WASI_RET_ERR_ON_FAIL( + safe_add_u32(&target_buf_curr_ref, p_str_buf, curr_buf_loc)); + + wasm_i32_store(instance_memory, target_argv_i_ref, target_buf_curr_ref); + + // Implement: strcpy(p_str_buf[curr_buf_loc], strings[i]); + size_t original_len = strlen(strings[i]); + // len has to be at most u32 - 1 + WASI_RET_ERR_ON_FAIL(original_len < (size_t)UINT32_MAX); + + u32 len = (u32)original_len; + u32 len_plus_nullchar = len + 1; + + WASI_CHECK_COPY( + instance_memory, target_buf_curr_ref, len_plus_nullchar, strings[i]); + // Implement: curr_buf_loc += strlen(p_str_buf[curr_buf_loc]) + WASI_RET_ERR_ON_FAIL( + safe_add_u32(&curr_buf_loc, curr_buf_loc, len_plus_nullchar)); + } + return WASI_SUCCESS; +} + +u32 w2c_wasi__snapshot__preview1_args_sizes_get( + w2c_wasi__snapshot__preview1* wasi_data, + u32 p_argc, + u32 p_argv_buf_size) +{ + return strings_sizes_get(wasi_data->instance_memory, + "main", + p_argc, + p_argv_buf_size, + wasi_data->main_argc, + wasi_data->main_argv); +} + +u32 w2c_wasi__snapshot__preview1_args_get( + w2c_wasi__snapshot__preview1* wasi_data, + u32 p_argv, + u32 p_argv_buf) +{ + return strings_get(wasi_data->instance_memory, + "main", + p_argv, + p_argv_buf, + wasi_data->main_argc, + wasi_data->main_argv); +} + +u32 w2c_wasi__snapshot__preview1_environ_sizes_get( + w2c_wasi__snapshot__preview1* wasi_data, + u32 p_env_count, + u32 p_env_buf_size) +{ + return strings_sizes_get(wasi_data->instance_memory, + "env", + p_env_count, + p_env_buf_size, + wasi_data->env_count, + wasi_data->env); +} + +u32 w2c_wasi__snapshot__preview1_environ_get( + w2c_wasi__snapshot__preview1* wasi_data, + u32 p_env, + u32 p_env_buf) +{ + return strings_get(wasi_data->instance_memory, + "env", + p_env, + p_env_buf, + wasi_data->env_count, + wasi_data->env); +} + +///////////////////////////////////////////////////////////// +// Proc exit operation +///////////////////////////////////////////////////////////// + +void w2c_wasi__snapshot__preview1_proc_exit( + w2c_wasi__snapshot__preview1* wasi_data, + u32 x) +{ +#ifdef WASM2C_WASI_TRAP_ON_EXIT + TRAP(WASI); +#else + exit(x); +#endif +} + +///////////////////////////////////////////////////////////// +////////////// Unsupported WASI APIs +///////////////////////////////////////////////////////////// + +#define STUB_IMPORT_IMPL(ret, name, params) \ + ret name params { return WASI_NOSYS_ERROR; } + +// clang-format off + +STUB_IMPORT_IMPL(u32, w2c_wasi__snapshot__preview1_fd_advise, + (w2c_wasi__snapshot__preview1* wasi_data, u32 a, u64 b, u64 c, u32 d)); +STUB_IMPORT_IMPL(u32, w2c_wasi__snapshot__preview1_fd_allocate, + (w2c_wasi__snapshot__preview1* wasi_data, u32 a, u64 b, u64 c)); +STUB_IMPORT_IMPL(u32, w2c_wasi__snapshot__preview1_fd_close, + (w2c_wasi__snapshot__preview1* wasi_data, u32 fd)); +STUB_IMPORT_IMPL(u32, w2c_wasi__snapshot__preview1_fd_datasync, + (w2c_wasi__snapshot__preview1* wasi_data, u32 a)); +STUB_IMPORT_IMPL(u32, w2c_wasi__snapshot__preview1_fd_fdstat_get, + (w2c_wasi__snapshot__preview1* wasi_data, u32 a, u32 b)); +STUB_IMPORT_IMPL(u32, w2c_wasi__snapshot__preview1_fd_fdstat_set_flags, + (w2c_wasi__snapshot__preview1* wasi_data, u32 a, u32 b)); +STUB_IMPORT_IMPL(u32, w2c_wasi__snapshot__preview1_fd_fdstat_set_rights, + (w2c_wasi__snapshot__preview1* wasi_data, u32 a, u64 b, u64 c)); +STUB_IMPORT_IMPL(u32, w2c_wasi__snapshot__preview1_fd_filestat_get, + (w2c_wasi__snapshot__preview1* wasi_data, u32 a, u32 b)); +STUB_IMPORT_IMPL(u32, w2c_wasi__snapshot__preview1_fd_filestat_set_size, + (w2c_wasi__snapshot__preview1* wasi_data, u32 a, u64 b)); +STUB_IMPORT_IMPL(u32, w2c_wasi__snapshot__preview1_fd_filestat_set_times, + (w2c_wasi__snapshot__preview1* wasi_data, u32 a, u64 b, u64 c, u32 d)); +STUB_IMPORT_IMPL(u32, w2c_wasi__snapshot__preview1_fd_pread, + (w2c_wasi__snapshot__preview1* wasi_data, u32 a, u32 b, u32 c, u64 d, u32 e)); +STUB_IMPORT_IMPL(u32, w2c_wasi__snapshot__preview1_fd_prestat_dir_name, + (w2c_wasi__snapshot__preview1* wasi_data, u32 a, u32 b, u32 c)); +STUB_IMPORT_IMPL(u32, w2c_wasi__snapshot__preview1_fd_pwrite, + (w2c_wasi__snapshot__preview1* wasi_data, u32 a, u32 b, u32 c, u64 d, u32 e)); +STUB_IMPORT_IMPL(u32, w2c_wasi__snapshot__preview1_fd_readdir, + (w2c_wasi__snapshot__preview1* wasi_data, u32 a, u32 b, u32 c, u64 d, u32 e)); +STUB_IMPORT_IMPL(u32, w2c_wasi__snapshot__preview1_fd_renumber, + (w2c_wasi__snapshot__preview1* wasi_data, u32 a, u32 b)); +STUB_IMPORT_IMPL(u32, w2c_wasi__snapshot__preview1_fd_seek, + (w2c_wasi__snapshot__preview1* wasi_data, u32 fd, u64 offset, u32 whence, u32 new_offset)); +STUB_IMPORT_IMPL(u32, w2c_wasi__snapshot__preview1_fd_sync, + (w2c_wasi__snapshot__preview1* wasi_data, u32 a)); +STUB_IMPORT_IMPL(u32, w2c_wasi__snapshot__preview1_fd_tell, + (w2c_wasi__snapshot__preview1* wasi_data, u32 a, u32 b)); +STUB_IMPORT_IMPL(u32, w2c_wasi__snapshot__preview1_path_create_directory, + (w2c_wasi__snapshot__preview1* wasi_data, u32 a, u32 b, u32 c)); +STUB_IMPORT_IMPL(u32, w2c_wasi__snapshot__preview1_path_filestat_get, + (w2c_wasi__snapshot__preview1* wasi_data, u32 a, u32 b, u32 c, u32 d, u32 e)); +STUB_IMPORT_IMPL(u32, w2c_wasi__snapshot__preview1_path_filestat_set_times, + (w2c_wasi__snapshot__preview1* wasi_data, u32 a, u32 b, u32 c, u32 d, u64 e, u64 f, u32 g)); +STUB_IMPORT_IMPL(u32, w2c_wasi__snapshot__preview1_path_link, + (w2c_wasi__snapshot__preview1* wasi_data, u32 a, u32 b, u32 c, u32 d, u32 e, u32 f, u32 g)); +STUB_IMPORT_IMPL(u32, w2c_wasi__snapshot__preview1_path_open, + (w2c_wasi__snapshot__preview1* wasi_data, u32 a, u32 b, u32 c, u32 d, u32 e, u64 f, u64 g, u32 h, u32 i)); +STUB_IMPORT_IMPL(u32, w2c_wasi__snapshot__preview1_path_readlink, + (w2c_wasi__snapshot__preview1* wasi_data, u32 a, u32 b, u32 c, u32 d, u32 e, u32 f)); +STUB_IMPORT_IMPL(u32, w2c_wasi__snapshot__preview1_path_remove_directory, + (w2c_wasi__snapshot__preview1* wasi_data, u32 a, u32 b, u32 c)); +STUB_IMPORT_IMPL(u32, w2c_wasi__snapshot__preview1_path_rename, + (w2c_wasi__snapshot__preview1* wasi_data, u32 a, u32 b, u32 c, u32 d, u32 e, u32 f)); +STUB_IMPORT_IMPL(u32, w2c_wasi__snapshot__preview1_path_symlink, + (w2c_wasi__snapshot__preview1* wasi_data, u32 a, u32 b, u32 c, u32 d, u32 e)); +STUB_IMPORT_IMPL(u32, w2c_wasi__snapshot__preview1_path_unlink_file, + (w2c_wasi__snapshot__preview1* wasi_data, u32 a, u32 b, u32 c)); +STUB_IMPORT_IMPL(u32, w2c_wasi__snapshot__preview1_poll_oneoff, + (w2c_wasi__snapshot__preview1* wasi_data, u32 a, u32 b, u32 c, u32 d)); +STUB_IMPORT_IMPL(u32, w2c_wasi__snapshot__preview1_proc_raise, + (w2c_wasi__snapshot__preview1* wasi_data, u32 a)); +STUB_IMPORT_IMPL(u32, w2c_wasi__snapshot__preview1_random_get, + (w2c_wasi__snapshot__preview1* wasi_data, u32 a, u32 b)); +STUB_IMPORT_IMPL(u32, w2c_wasi__snapshot__preview1_sched_yield, + (w2c_wasi__snapshot__preview1* wasi_data)); +STUB_IMPORT_IMPL(u32, w2c_wasi__snapshot__preview1_sock_accept, + (w2c_wasi__snapshot__preview1* wasi_data, u32 a, u32 b, u32 c)); +STUB_IMPORT_IMPL(u32, w2c_wasi__snapshot__preview1_sock_recv, + (w2c_wasi__snapshot__preview1* wasi_data, u32 a, u32 b, u32 c, u32 d, u32 e, u32 f)); +STUB_IMPORT_IMPL(u32, w2c_wasi__snapshot__preview1_sock_send, + (w2c_wasi__snapshot__preview1* wasi_data, u32 a, u32 b, u32 c, u32 d, u32 e)); +STUB_IMPORT_IMPL(u32, w2c_wasi__snapshot__preview1_sock_shutdown, + (w2c_wasi__snapshot__preview1* wasi_data, u32 a, u32 b)); + +// clang-format on + +///////////////////////////////////////////////////////////// +////////// Misc +///////////////////////////////////////////////////////////// + +bool minwasi_init() +{ + return os_clock_init(); +} + +bool minwasi_init_instance(w2c_wasi__snapshot__preview1* wasi_data) +{ + return os_clock_init_instance(&(wasi_data->clock_data)); +} + +void minwasi_cleanup_instance(w2c_wasi__snapshot__preview1* wasi_data) +{ + os_clock_cleanup_instance(&(wasi_data->clock_data)); +} |