Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement a capability restriction system. #89

Merged
merged 27 commits into from
Dec 22, 2020
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
7f0b181
implement a capability restriction mechanism
ryanapilado Nov 3, 2020
0b28799
stubs log the capability they replaced
ryanapilado Nov 5, 2020
a3e393a
initialize capability restriction parameters on construction
ryanapilado Nov 9, 2020
056b80a
rename capability to abi_function
ryanapilado Nov 13, 2020
bdc82eb
remove enforcement flag
ryanapilado Nov 13, 2020
86eed01
clang format
ryanapilado Nov 14, 2020
cd44da9
Merge branch 'master' into capability-restriction
ryanapilado Nov 18, 2020
5971335
switch to absl::flat_hash_set
ryanapilado Nov 19, 2020
8d24cf5
rename abi function -> capability
ryanapilado Nov 19, 2020
18a6367
restrict WASI
ryanapilado Nov 19, 2020
a180877
build format
ryanapilado Nov 19, 2020
26de5b9
fix names
ryanapilado Nov 20, 2020
ceaef93
for all module functions
ryanapilado Nov 20, 2020
a94857a
remove pthread equal
ryanapilado Nov 20, 2020
0ad3d42
refactor registerCallbacks()
ryanapilado Nov 20, 2020
254328b
restore pthread_equal
ryanapilado Nov 21, 2020
57b6abb
use map instead of set
ryanapilado Dec 1, 2020
a8d5b6c
Merge remote-tracking branch 'upstream/master' into capability-restri…
ryanapilado Dec 16, 2020
18cf449
change absl::flat_hash_map to std::unordered_map
ryanapilado Dec 17, 2020
f37f375
update error call
ryanapilado Dec 17, 2020
d8ae296
Merge remote-tracking branch 'upstream/master' into capability-restri…
ryanapilado Dec 17, 2020
228350f
create SanitizerConfig
ryanapilado Dec 18, 2020
bd262ef
Merge branch 'master' into capability-restriction
PiotrSikora Dec 21, 2020
ed8cbca
change enum to bool
ryanapilado Dec 21, 2020
68a6cf0
Merge branch 'capability-restriction' of github.com:ryanapilado/proxy…
ryanapilado Dec 22, 2020
980be06
create separate WASI stub
ryanapilado Dec 22, 2020
7ef7473
remove void variant from proxy-wasm stub
ryanapilado Dec 22, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 48 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,51 @@ 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 export.
#define _CREATE_EXPORT_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 capability: " #_fn); \
return WasmResult::InternalFailure; \
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: @PiotrSikora we should have a new Wasm result like Unauthorized as WASI's acces Permission denied. (hopefully in the next ABI ). https://github.com/WebAssembly/WASI/blob/master/phases/snapshot/docs.md#variants-1

} \
}; \
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 capability: " #_fn); \
} \
};
FOR_ALL_HOST_FUNCTIONS(_CREATE_EXPORT_STUB)
FOR_ALL_HOST_FUNCTIONS_ABI_SPECIFIC(_CREATE_EXPORT_STUB)
FOR_ALL_WASI_FUNCTIONS(_CREATE_EXPORT_STUB)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For WASI, I think it would be better if we had a dedicated stub that returns __WASI_ENOTCAPABLE (76), since WASI modules should be aware of that error code, and they know nothing about Proxy-Wasm return values.

#undef _CREATE_EXPORT_STUB

} // namespace exports
} // namespace proxy_wasm
30 changes: 29 additions & 1 deletion include/proxy-wasm/wasm.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,19 @@ 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;
enum class ListType : int { Allowlist = 0, Denylist = 1 };
ListType list_type;
};
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 +100,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();
Copy link
Contributor

@mathetake mathetake Nov 20, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so basically if the configuration is null, all functions are allowed, and if it is the empty list (or the meaningless list like [""]), all functions are disabled, right? It seems a little bit confusing to me.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But, as long as there would be no use cases for disabling all exported functions (and I don't come up with anything), this is just fine as it is.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it's empty then all functions are allowed. It does feel a little unintuitive, but yeah I don't think there's any case where you would want to have all capabilities restricted.

}

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 +239,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