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/src | |
parent | Initial commit. (diff) | |
download | firefox-esr-upstream.tar.xz firefox-esr-upstream.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/src')
-rw-r--r-- | third_party/rlbox_wasm2c_sandbox/src/wasm2c_rt_mem.c | 454 | ||||
-rw-r--r-- | third_party/rlbox_wasm2c_sandbox/src/wasm2c_rt_minwasi.c | 799 |
2 files changed, 1253 insertions, 0 deletions
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)); +} |