Skip to content

Commit

Permalink
Implement a capability restriction system. (#89)
Browse files Browse the repository at this point in the history
Signed-off-by: Ryan Apilado <[email protected]>
  • Loading branch information
ryanapilado authored Dec 22, 2020
1 parent 3750104 commit 9937669
Show file tree
Hide file tree
Showing 3 changed files with 137 additions and 110 deletions.
65 changes: 64 additions & 1 deletion include/proxy-wasm/exports.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include <memory>

#include "include/proxy-wasm/word.h"
#include "include/proxy-wasm/wasm_vm.h"

namespace proxy_wasm {

Expand Down Expand Up @@ -57,7 +58,7 @@ template <typename Pairs> void marshalPairs(const Pairs &result, char *buffer) {
}
}

// ABI functions exported from envoy to wasm.
// ABI functions exported from host to wasm.

Word get_configuration(void *raw_context, Word address, Word size);
Word get_status(void *raw_context, Word status_code, Word address, Word size);
Expand Down Expand Up @@ -153,5 +154,67 @@ Word pthread_equal(void *, Word left, Word right);
// Any currently executing Wasm call context.
::proxy_wasm::ContextBase *ContextOrEffectiveContext(::proxy_wasm::ContextBase *context);

#define FOR_ALL_HOST_FUNCTIONS(_f) \
_f(log) _f(get_status) _f(set_property) _f(get_property) _f(send_local_response) \
_f(get_shared_data) _f(set_shared_data) _f(register_shared_queue) _f(resolve_shared_queue) \
_f(dequeue_shared_queue) _f(enqueue_shared_queue) _f(get_header_map_value) \
_f(add_header_map_value) _f(replace_header_map_value) _f(remove_header_map_value) \
_f(get_header_map_pairs) _f(set_header_map_pairs) _f(get_header_map_size) \
_f(get_buffer_status) _f(get_buffer_bytes) _f(set_buffer_bytes) \
_f(http_call) _f(grpc_call) _f(grpc_stream) _f(grpc_close) \
_f(grpc_cancel) _f(grpc_send) _f(set_tick_period_milliseconds) \
_f(get_current_time_nanoseconds) _f(define_metric) \
_f(increment_metric) _f(record_metric) _f(get_metric) \
_f(set_effective_context) _f(done) \
_f(call_foreign_function)

#define FOR_ALL_HOST_FUNCTIONS_ABI_SPECIFIC(_f) \
_f(get_configuration) _f(continue_request) _f(continue_response) _f(clear_route_cache) \
_f(continue_stream) _f(close_stream) _f(get_log_level)

#define FOR_ALL_WASI_FUNCTIONS(_f) \
_f(fd_write) _f(fd_read) _f(fd_seek) _f(fd_close) _f(fd_fdstat_get) _f(environ_get) \
_f(environ_sizes_get) _f(args_get) _f(args_sizes_get) _f(clock_time_get) _f(random_get) \
_f(proc_exit)

// Helpers to generate a stub to pass to VM, in place of a restricted proxy-wasm capability.
#define _CREATE_PROXY_WASM_STUB(_fn) \
template <typename F> struct _fn##Stub; \
template <typename... Args> struct _fn##Stub<Word(void *, Args...)> { \
static Word stub(void *raw_context, Args...) { \
auto context = exports::ContextOrEffectiveContext( \
static_cast<ContextBase *>((void)raw_context, current_context_)); \
context->wasmVm()->integration()->error( \
"Attempted call to restricted proxy-wasm capability: proxy_" #_fn); \
return WasmResult::InternalFailure; \
} \
};
FOR_ALL_HOST_FUNCTIONS(_CREATE_PROXY_WASM_STUB)
FOR_ALL_HOST_FUNCTIONS_ABI_SPECIFIC(_CREATE_PROXY_WASM_STUB)
#undef _CREATE_PROXY_WASM_STUB

// Helpers to generate a stub to pass to VM, in place of a restricted WASI capability.
#define _CREATE_WASI_STUB(_fn) \
template <typename F> struct _fn##Stub; \
template <typename... Args> struct _fn##Stub<Word(void *, Args...)> { \
static Word stub(void *raw_context, Args...) { \
auto context = exports::ContextOrEffectiveContext( \
static_cast<ContextBase *>((void)raw_context, current_context_)); \
context->wasmVm()->integration()->error( \
"Attempted call to restricted WASI capability: " #_fn); \
return 76; /* __WASI_ENOTCAPABLE */ \
} \
}; \
template <typename... Args> struct _fn##Stub<void(void *, Args...)> { \
static void stub(void *raw_context, Args...) { \
auto context = exports::ContextOrEffectiveContext( \
static_cast<ContextBase *>((void)raw_context, current_context_)); \
context->wasmVm()->integration()->error( \
"Attempted call to restricted WASI capability: " #_fn); \
} \
};
FOR_ALL_WASI_FUNCTIONS(_CREATE_WASI_STUB)
#undef _CREATE_WASI_STUB

} // namespace exports
} // namespace proxy_wasm
29 changes: 28 additions & 1 deletion include/proxy-wasm/wasm.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,18 @@ using WasmForeignFunction =
using WasmVmFactory = std::function<std::unique_ptr<WasmVm>()>;
using CallOnThreadFunction = std::function<void(std::function<void()>)>;

struct SanitizationConfig {
std::vector<std::string> argument_list;
bool is_allowlist;
};
using AllowedCapabilitiesMap = std::unordered_map<std::string, SanitizationConfig>;

// Wasm execution instance. Manages the host side of the Wasm interface.
class WasmBase : public std::enable_shared_from_this<WasmBase> {
public:
WasmBase(std::unique_ptr<WasmVm> wasm_vm, std::string_view vm_id,
std::string_view vm_configuration, std::string_view vm_key);
std::string_view vm_configuration, std::string_view vm_key,
AllowedCapabilitiesMap allowed_capabilities);
WasmBase(const std::shared_ptr<WasmHandleBase> &other, WasmVmFactory factory);
virtual ~WasmBase();

Expand Down Expand Up @@ -92,6 +99,12 @@ class WasmBase : public std::enable_shared_from_this<WasmBase> {
return nullptr;
}

// Capability restriction (restricting/exposing the ABI).
bool capabilityAllowed(std::string capability_name) {
return allowed_capabilities_.empty() ||
allowed_capabilities_.find(capability_name) != allowed_capabilities_.end();
}

virtual ContextBase *createVmContext() { return new ContextBase(this); }
virtual ContextBase *createRootContext(const std::shared_ptr<PluginBase> &plugin) {
return new ContextBase(this, plugin);
Expand Down Expand Up @@ -225,6 +238,20 @@ class WasmBase : public std::enable_shared_from_this<WasmBase> {
WasmCallVoid<1> on_log_;
WasmCallVoid<1> on_delete_;

#define FOR_ALL_MODULE_FUNCTIONS(_f) \
_f(validate_configuration) _f(on_vm_start) _f(on_configure) _f(on_tick) _f(on_context_create) \
_f(on_new_connection) _f(on_downstream_data) _f(on_upstream_data) \
_f(on_downstream_connection_close) _f(on_upstream_connection_close) _f(on_request_body) \
_f(on_request_trailers) _f(on_request_metadata) _f(on_response_body) \
_f(on_response_trailers) _f(on_response_metadata) _f(on_http_call_response) \
_f(on_grpc_receive) _f(on_grpc_close) _f(on_grpc_receive_initial_metadata) \
_f(on_grpc_receive_trailing_metadata) _f(on_queue_ready) _f(on_done) \
_f(on_log) _f(on_delete)

// Capabilities which are allowed to be linked to the module. If this is empty, restriction
// is not enforced.
AllowedCapabilitiesMap allowed_capabilities_;

std::shared_ptr<WasmHandleBase> base_wasm_handle_;

// Used by the base_wasm to enable non-clonable thread local Wasm(s) to be constructed.
Expand Down
153 changes: 45 additions & 108 deletions src/wasm.cc
Original file line number Diff line number Diff line change
Expand Up @@ -100,83 +100,30 @@ void WasmBase::registerCallbacks() {
_REGISTER(pthread_equal);
#undef _REGISTER

#define _REGISTER_WASI(_fn) \
wasm_vm_->registerCallback( \
"wasi_unstable", #_fn, &exports::wasi_unstable_##_fn, \
&ConvertFunctionWordToUint32<decltype(exports::wasi_unstable_##_fn), \
exports::wasi_unstable_##_fn>::convertFunctionWordToUint32); \
wasm_vm_->registerCallback( \
"wasi_snapshot_preview1", #_fn, &exports::wasi_unstable_##_fn, \
&ConvertFunctionWordToUint32<decltype(exports::wasi_unstable_##_fn), \
exports::wasi_unstable_##_fn>::convertFunctionWordToUint32)
_REGISTER_WASI(fd_write);
_REGISTER_WASI(fd_read);
_REGISTER_WASI(fd_seek);
_REGISTER_WASI(fd_close);
_REGISTER_WASI(fd_fdstat_get);
_REGISTER_WASI(environ_get);
_REGISTER_WASI(environ_sizes_get);
_REGISTER_WASI(args_get);
_REGISTER_WASI(args_sizes_get);
_REGISTER_WASI(clock_time_get);
_REGISTER_WASI(random_get);
_REGISTER_WASI(proc_exit);
#undef _REGISTER_WASI

// Calls with the "proxy_" prefix.
#define _REGISTER_PROXY(_fn) \
wasm_vm_->registerCallback( \
"env", "proxy_" #_fn, &exports::_fn, \
&ConvertFunctionWordToUint32<decltype(exports::_fn), \
exports::_fn>::convertFunctionWordToUint32);
_REGISTER_PROXY(log);

_REGISTER_PROXY(get_status);

_REGISTER_PROXY(set_property);
_REGISTER_PROXY(get_property);

_REGISTER_PROXY(send_local_response);

_REGISTER_PROXY(get_shared_data);
_REGISTER_PROXY(set_shared_data);

_REGISTER_PROXY(register_shared_queue);
_REGISTER_PROXY(resolve_shared_queue);
_REGISTER_PROXY(dequeue_shared_queue);
_REGISTER_PROXY(enqueue_shared_queue);

_REGISTER_PROXY(get_header_map_value);
_REGISTER_PROXY(add_header_map_value);
_REGISTER_PROXY(replace_header_map_value);
_REGISTER_PROXY(remove_header_map_value);
_REGISTER_PROXY(get_header_map_pairs);
_REGISTER_PROXY(set_header_map_pairs);
_REGISTER_PROXY(get_header_map_size);

_REGISTER_PROXY(get_buffer_status);
_REGISTER_PROXY(get_buffer_bytes);
_REGISTER_PROXY(set_buffer_bytes);

_REGISTER_PROXY(http_call);

_REGISTER_PROXY(grpc_call);
_REGISTER_PROXY(grpc_stream);
_REGISTER_PROXY(grpc_close);
_REGISTER_PROXY(grpc_cancel);
_REGISTER_PROXY(grpc_send);

_REGISTER_PROXY(set_tick_period_milliseconds);
_REGISTER_PROXY(get_current_time_nanoseconds);

_REGISTER_PROXY(define_metric);
_REGISTER_PROXY(increment_metric);
_REGISTER_PROXY(record_metric);
_REGISTER_PROXY(get_metric);

_REGISTER_PROXY(set_effective_context);
_REGISTER_PROXY(done);
_REGISTER_PROXY(call_foreign_function);
// Register the capability with the VM if it has been allowed, otherwise register a stub.
#define _REGISTER(module_name, name_prefix, export_prefix, _fn) \
if (capabilityAllowed(name_prefix #_fn)) { \
wasm_vm_->registerCallback( \
module_name, name_prefix #_fn, &exports::export_prefix##_fn, \
&ConvertFunctionWordToUint32<decltype(exports::export_prefix##_fn), \
exports::export_prefix##_fn>::convertFunctionWordToUint32); \
} else { \
typedef decltype(exports::export_prefix##_fn) export_type; \
constexpr export_type *stub = &exports::_fn##Stub<export_type>::stub; \
wasm_vm_->registerCallback( \
module_name, name_prefix #_fn, stub, \
&ConvertFunctionWordToUint32<export_type, stub>::convertFunctionWordToUint32); \
}

#define _REGISTER_WASI_UNSTABLE(_fn) _REGISTER("wasi_unstable", , wasi_unstable_, _fn)
#define _REGISTER_WASI_SNAPSHOT(_fn) _REGISTER("wasi_snapshot_preview1", , wasi_unstable_, _fn)
FOR_ALL_WASI_FUNCTIONS(_REGISTER_WASI_UNSTABLE);
FOR_ALL_WASI_FUNCTIONS(_REGISTER_WASI_SNAPSHOT);
#undef _REGISTER_WASI_UNSTABLE
#undef _REGISTER_WASI_SNAPSHOT

#define _REGISTER_PROXY(_fn) _REGISTER("env", "proxy_", , _fn)
FOR_ALL_HOST_FUNCTIONS(_REGISTER_PROXY);

if (abiVersion() == AbiVersion::ProxyWasm_0_1_0) {
_REGISTER_PROXY(get_configuration);
Expand All @@ -192,6 +139,8 @@ void WasmBase::registerCallbacks() {
_REGISTER_PROXY(get_log_level);
}
#undef _REGISTER_PROXY

#undef _REGISTER
}

void WasmBase::getFunctions() {
Expand All @@ -211,36 +160,21 @@ void WasmBase::getFunctions() {
#undef _GET_ALIAS
#undef _GET

#define _GET_PROXY(_fn) wasm_vm_->getFunction("proxy_" #_fn, &_fn##_);
#define _GET_PROXY_ABI(_fn, _abi) wasm_vm_->getFunction("proxy_" #_fn, &_fn##_abi##_);
_GET_PROXY(validate_configuration);
_GET_PROXY(on_vm_start);
_GET_PROXY(on_configure);
_GET_PROXY(on_tick);

_GET_PROXY(on_context_create);

_GET_PROXY(on_new_connection);
_GET_PROXY(on_downstream_data);
_GET_PROXY(on_upstream_data);
_GET_PROXY(on_downstream_connection_close);
_GET_PROXY(on_upstream_connection_close);

_GET_PROXY(on_request_body);
_GET_PROXY(on_request_trailers);
_GET_PROXY(on_request_metadata);
_GET_PROXY(on_response_body);
_GET_PROXY(on_response_trailers);
_GET_PROXY(on_response_metadata);
_GET_PROXY(on_http_call_response);
_GET_PROXY(on_grpc_receive);
_GET_PROXY(on_grpc_close);
_GET_PROXY(on_grpc_receive_initial_metadata);
_GET_PROXY(on_grpc_receive_trailing_metadata);
_GET_PROXY(on_queue_ready);
_GET_PROXY(on_done);
_GET_PROXY(on_log);
_GET_PROXY(on_delete);
// Try to point the capability to one of the module exports, if the capability has been allowed.
#define _GET_PROXY(_fn) \
if (capabilityAllowed("proxy_" #_fn)) { \
wasm_vm_->getFunction("proxy_" #_fn, &_fn##_); \
} else { \
_fn##_ = nullptr; \
}
#define _GET_PROXY_ABI(_fn, _abi) \
if (capabilityAllowed("proxy_" #_fn)) { \
wasm_vm_->getFunction("proxy_" #_fn, &_fn##_abi##_); \
} else { \
_fn##_abi##_ = nullptr; \
}

FOR_ALL_MODULE_FUNCTIONS(_GET_PROXY);

if (abiVersion() == AbiVersion::ProxyWasm_0_1_0) {
_GET_PROXY_ABI(on_request_headers, _abi_01);
Expand All @@ -259,6 +193,7 @@ WasmBase::WasmBase(const std::shared_ptr<WasmHandleBase> &base_wasm_handle, Wasm
: std::enable_shared_from_this<WasmBase>(*base_wasm_handle->wasm()),
vm_id_(base_wasm_handle->wasm()->vm_id_), vm_key_(base_wasm_handle->wasm()->vm_key_),
started_from_(base_wasm_handle->wasm()->wasm_vm()->cloneable()),
allowed_capabilities_(base_wasm_handle->wasm()->allowed_capabilities_),
base_wasm_handle_(base_wasm_handle) {
if (started_from_ != Cloneable::NotCloneable) {
wasm_vm_ = base_wasm_handle->wasm()->wasm_vm()->clone();
Expand All @@ -273,8 +208,10 @@ WasmBase::WasmBase(const std::shared_ptr<WasmHandleBase> &base_wasm_handle, Wasm
}

WasmBase::WasmBase(std::unique_ptr<WasmVm> wasm_vm, std::string_view vm_id,
std::string_view vm_configuration, std::string_view vm_key)
std::string_view vm_configuration, std::string_view vm_key,
AllowedCapabilitiesMap allowed_capabilities)
: vm_id_(std::string(vm_id)), vm_key_(std::string(vm_key)), wasm_vm_(std::move(wasm_vm)),
allowed_capabilities_(std::move(allowed_capabilities)),
vm_configuration_(std::string(vm_configuration)), vm_id_handle_(getVmIdHandle(vm_id)) {
if (!wasm_vm_) {
failed_ = FailState::UnableToCreateVM;
Expand Down

0 comments on commit 9937669

Please sign in to comment.