mirror of
https://github.com/mkxp-z/mkxp-z.git
synced 2025-08-23 15:23:44 +02:00

The copy constructors are causing problems when the `fiber.stack` vector gets reallocated when its capacity is full, since when vectors are reallocated, the elements are moved (or copied if there's no usable move constructor) to the reallocated memory and then the original elements are destroyed. This premature calling of destructors leads to double-free and use-after-free errors. I fixed it by deleting the copy constructors and explicitly defining move constructors.
218 lines
7.7 KiB
C++
218 lines
7.7 KiB
C++
/*
|
|
** binding-base.h
|
|
**
|
|
** This file is part of mkxp.
|
|
**
|
|
** Copyright (C) 2013 - 2021 Amaryllis Kulla <ancurio@mapleshrine.eu>
|
|
**
|
|
** mkxp is free software: you can redistribute it and/or modify
|
|
** it under the terms of the GNU General Public License as published by
|
|
** the Free Software Foundation, either version 2 of the License, or
|
|
** (at your option) any later version.
|
|
**
|
|
** mkxp is distributed in the hope that it will be useful,
|
|
** but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
** GNU General Public License for more details.
|
|
**
|
|
** You should have received a copy of the GNU General Public License
|
|
** along with mkxp. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#ifndef MKXPZ_SANDBOX_BINDING_BASE_H
|
|
#define MKXPZ_SANDBOX_BINDING_BASE_H
|
|
|
|
#include <cassert>
|
|
#include <cstdint>
|
|
#include <cstring>
|
|
#include <memory>
|
|
#include <unordered_map>
|
|
#include <utility>
|
|
#include <vector>
|
|
#include <boost/container_hash/hash.hpp>
|
|
#include <boost/asio/coroutine.hpp>
|
|
#include <mkxp-retro-ruby.h>
|
|
#include "types.h"
|
|
|
|
#ifdef MKXPZ_BIG_ENDIAN
|
|
# define SERIALIZE_32(value) __builtin_bswap32(value)
|
|
# define SERIALIZE_64(value) __builtin_bswap64(value)
|
|
#else
|
|
# define SERIALIZE_32(value) (value)
|
|
# define SERIALIZE_64(value) (value)
|
|
#endif
|
|
|
|
#ifdef MKXPZ_RETRO_MEMORY64
|
|
# define SERIALIZE_VALUE(value) SERIALIZE_64(value)
|
|
#else
|
|
# define SERIALIZE_VALUE(value) SERIALIZE_32(value)
|
|
#endif
|
|
|
|
// LLVM uses a stack alignment of 16 on WebAssembly targets
|
|
#define WASMSTACKALIGN 16
|
|
|
|
// Rounds a number up to the nearest multiple of the WebAssembly stack alignment
|
|
#define CEIL_WASMSTACKALIGN(x) (((x) + (size_t)(WASMSTACKALIGN - 1)) & ~(size_t)(WASMSTACKALIGN - 1))
|
|
|
|
// Same as `sizeof(T)`, but rounds the result up to the nearest multiple of the WebAssembly stack alignment
|
|
#define SIZEOF_WASMSTACKALIGN(T) CEIL_WASMSTACKALIGN(sizeof(T))
|
|
|
|
namespace mkxp_sandbox {
|
|
struct binding_base {
|
|
private:
|
|
typedef std::tuple<wasm_ptr_t, wasm_ptr_t, wasm_ptr_t> key_t;
|
|
|
|
struct stack_frame {
|
|
struct binding_base *bind;
|
|
void (*destructor)(void *ptr);
|
|
wasm_ptr_t ptr;
|
|
stack_frame(struct binding_base &bind, void (*destructor)(void *ptr), wasm_ptr_t ptr);
|
|
stack_frame(const struct stack_frame &frame) = delete;
|
|
stack_frame(struct stack_frame &&frame) noexcept;
|
|
struct stack_frame &operator=(const struct stack_frame &frame) = delete;
|
|
struct stack_frame &operator=(struct stack_frame &&frame) noexcept;
|
|
~stack_frame();
|
|
};
|
|
|
|
struct fiber {
|
|
key_t key;
|
|
std::vector<struct stack_frame> stack;
|
|
size_t stack_ptr;
|
|
};
|
|
|
|
wasm_ptr_t next_func_ptr;
|
|
std::shared_ptr<struct w2c_ruby> _instance;
|
|
std::unordered_map<key_t, struct fiber, boost::hash<key_t>> fibers;
|
|
|
|
public:
|
|
binding_base(std::shared_ptr<struct w2c_ruby> m);
|
|
~binding_base();
|
|
struct w2c_ruby &instance() const noexcept;
|
|
uint8_t *get() const noexcept;
|
|
uint8_t *operator*() const noexcept;
|
|
wasm_ptr_t sandbox_malloc(wasm_size_t);
|
|
void sandbox_free(wasm_ptr_t ptr);
|
|
wasm_ptr_t rtypeddata_data(VALUE obj) const noexcept;
|
|
void rtypeddata_dmark(wasm_ptr_t data, wasm_ptr_t ptr);
|
|
void rtypeddata_dfree(wasm_ptr_t data, wasm_ptr_t ptr);
|
|
wasm_size_t rtypeddata_dsize(wasm_ptr_t data, wasm_ptr_t ptr);
|
|
void rtypeddata_dcompact(wasm_ptr_t data, wasm_ptr_t ptr);
|
|
|
|
template <typename T> struct stack_frame_guard {
|
|
friend struct binding_base;
|
|
|
|
private:
|
|
struct binding_base *bind;
|
|
struct fiber *fiber;
|
|
wasm_ptr_t ptr;
|
|
|
|
static void stack_frame_destructor(void *ptr) {
|
|
((T *)ptr)->~T();
|
|
}
|
|
|
|
static struct fiber &init_fiber(struct binding_base &bind) {
|
|
key_t key = {
|
|
*(wasm_ptr_t *)(*bind + bind.instance().w2c_mkxp_sandbox_fiber_entry_point),
|
|
*(wasm_ptr_t *)(*bind + bind.instance().w2c_mkxp_sandbox_fiber_arg0),
|
|
*(wasm_ptr_t *)(*bind + bind.instance().w2c_mkxp_sandbox_fiber_arg1),
|
|
};
|
|
if (bind.fibers.count(key) == 0) {
|
|
bind.fibers[key] = (struct fiber){.key = key};
|
|
}
|
|
return bind.fibers[key];
|
|
}
|
|
|
|
static wasm_ptr_t init_inner(struct binding_base &bind, struct fiber &fiber) {
|
|
uint32_t state = w2c_ruby_asyncify_get_state(&bind.instance());
|
|
|
|
if (fiber.stack_ptr > fiber.stack.size()) {
|
|
std::abort();
|
|
}
|
|
|
|
// If Asyncify is rewinding, restore the stack frame from before Asyncify started unwinding
|
|
if (state == 2) {
|
|
if (fiber.stack_ptr == fiber.stack.size()) {
|
|
std::abort();
|
|
}
|
|
return fiber.stack[fiber.stack_ptr++].ptr;
|
|
}
|
|
|
|
// Otherwise, create a new stack frame
|
|
assert(state == 0);
|
|
while (fiber.stack.size() > fiber.stack_ptr) {
|
|
fiber.stack.pop_back();
|
|
}
|
|
++fiber.stack_ptr;
|
|
wasm_ptr_t sp = w2c_ruby_rb_wasm_get_stack_pointer(&bind.instance()) - SIZEOF_WASMSTACKALIGN(T);
|
|
fiber.stack.reserve(fiber.stack_ptr);
|
|
fiber.stack.emplace_back(
|
|
bind,
|
|
stack_frame_destructor,
|
|
sp
|
|
);
|
|
assert(sp % sizeof(VALUE) == 0);
|
|
assert(sp % WASMSTACKALIGN == 0);
|
|
new(*bind + sp) T(bind);
|
|
w2c_ruby_rb_wasm_set_stack_pointer(&bind.instance(), sp);
|
|
return sp;
|
|
}
|
|
|
|
stack_frame_guard(struct binding_base &b) : bind(&b), fiber(&init_fiber(b)), ptr(init_inner(b, *fiber)) {}
|
|
|
|
public:
|
|
stack_frame_guard(const stack_frame_guard &frame) = delete;
|
|
|
|
stack_frame_guard(stack_frame_guard &&frame) noexcept : bind(std::exchange(frame.bind, nullptr)), fiber(std::exchange(frame.fiber, nullptr)), ptr(std::exchange(frame.ptr, 0)) {}
|
|
|
|
struct stack_frame_guard &operator=(const stack_frame_guard &frame) = delete;
|
|
|
|
struct stack_frame_guard &operator=(stack_frame_guard &&frame) noexcept {
|
|
bind = std::exchange(frame.bind, nullptr);
|
|
fiber = std::exchange(frame.fiber, nullptr);
|
|
ptr = std::exchange(frame.ptr, 0);
|
|
return *this;
|
|
}
|
|
|
|
~stack_frame_guard() {
|
|
if (fiber == nullptr) {
|
|
return;
|
|
}
|
|
|
|
if (get()->is_complete()) {
|
|
while (fiber->stack.size() > fiber->stack_ptr) {
|
|
fiber->stack.pop_back();
|
|
}
|
|
|
|
assert(fiber->stack.size() == fiber->stack_ptr);
|
|
|
|
w2c_ruby_rb_wasm_set_stack_pointer(&bind->instance(), fiber->stack.back().ptr + SIZEOF_WASMSTACKALIGN(T));
|
|
fiber->stack.pop_back();
|
|
}
|
|
|
|
--fiber->stack_ptr;
|
|
|
|
if (fiber->stack.empty()) {
|
|
bind->fibers.erase(fiber->key);
|
|
}
|
|
}
|
|
|
|
inline T *get() const noexcept {
|
|
return (T *)(**bind + ptr);
|
|
}
|
|
|
|
inline T &operator()() const noexcept {
|
|
return *get();
|
|
}
|
|
};
|
|
|
|
template <typename T> struct stack_frame_guard<T> bind() {
|
|
return *this;
|
|
}
|
|
};
|
|
}
|
|
|
|
#undef SERIALIZE_32
|
|
#undef SERIALIZE_64
|
|
#undef SERIALIZE_VALUE
|
|
|
|
#endif // MKXPZ_SANDBOX_BINDING_BASE
|