|
| 1 | +// Copyright 2016-2019 Envoy Project Authors |
| 2 | +// Copyright 2020 Google LLC |
| 3 | +// |
| 4 | +// Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | +// you may not use this file except in compliance with the License. |
| 6 | +// You may obtain a copy of the License at |
| 7 | +// |
| 8 | +// http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | +// |
| 10 | +// Unless required by applicable law or agreed to in writing, software |
| 11 | +// distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | +// See the License for the specific language governing permissions and |
| 14 | +// limitations under the License. |
| 15 | + |
| 16 | +#pragma once |
| 17 | + |
| 18 | +#include <functional> |
| 19 | +#include <memory> |
| 20 | +#include <optional> |
| 21 | +#include <string> |
| 22 | +#include <string_view> |
| 23 | +#include <unordered_map> |
| 24 | +#include <unordered_set> |
| 25 | +#include <vector> |
| 26 | + |
| 27 | +#include "include/proxy-wasm/sdk.h" |
| 28 | +#include "include/proxy-wasm/word.h" |
| 29 | + |
| 30 | +namespace proxy_wasm { |
| 31 | + |
| 32 | +class ContextBase; |
| 33 | + |
| 34 | +// These are templates and its helper for constructing signatures of functions calling into Wasm |
| 35 | +// VMs. |
| 36 | +// - WasmCallInFuncTypeHelper is a helper for WasmFuncType and shouldn't be used anywhere else than |
| 37 | +// WasmFuncType definition. |
| 38 | +// - WasmCallInFuncType takes 4 template parameter which are number of argument, return type, |
| 39 | +// context type and param type respectively, resolve to a function type. |
| 40 | +// For example `WasmFuncType<3, void, Context*, Word>` resolves to `void(Context*, Word, Word, |
| 41 | +// Word)` |
| 42 | +template <size_t N, class ReturnType, class ContextType, class ParamType, |
| 43 | + class FuncBase = ReturnType(ContextType)> |
| 44 | +struct WasmCallInFuncTypeHelper {}; |
| 45 | + |
| 46 | +template <size_t N, class ReturnType, class ContextType, class ParamType, class... Args> |
| 47 | +struct WasmCallInFuncTypeHelper<N, ReturnType, ContextType, ParamType, |
| 48 | + ReturnType(ContextType, Args...)> { |
| 49 | + // NOLINTNEXTLINE(readability-identifier-naming) |
| 50 | + using type = typename WasmCallInFuncTypeHelper<N - 1, ReturnType, ContextType, ParamType, |
| 51 | + ReturnType(ContextType, Args..., ParamType)>::type; |
| 52 | +}; |
| 53 | + |
| 54 | +template <class ReturnType, class ContextType, class ParamType, class... Args> |
| 55 | +struct WasmCallInFuncTypeHelper<0, ReturnType, ContextType, ParamType, |
| 56 | + ReturnType(ContextType, Args...)> { |
| 57 | + using type = ReturnType(ContextType, Args...); // NOLINT(readability-identifier-naming) |
| 58 | +}; |
| 59 | + |
| 60 | +template <size_t N, class ReturnType, class ContextType, class ParamType> |
| 61 | +using WasmCallInFuncType = |
| 62 | + typename WasmCallInFuncTypeHelper<N, ReturnType, ContextType, ParamType>::type; |
| 63 | + |
| 64 | +// Calls into the WASM VM. |
| 65 | +// 1st arg is always a pointer to Context (Context*). |
| 66 | +template <size_t N> |
| 67 | +using WasmCallVoid = std::function<WasmCallInFuncType<N, void, ContextBase *, Word>>; |
| 68 | +template <size_t N> |
| 69 | +using WasmCallWord = std::function<WasmCallInFuncType<N, Word, ContextBase *, Word>>; |
| 70 | + |
| 71 | +#define FOR_ALL_WASM_VM_EXPORTS(_f) \ |
| 72 | + _f(proxy_wasm::WasmCallVoid<0>) _f(proxy_wasm::WasmCallVoid<1>) _f(proxy_wasm::WasmCallVoid<2>) \ |
| 73 | + _f(proxy_wasm::WasmCallVoid<3>) _f(proxy_wasm::WasmCallVoid<5>) \ |
| 74 | + _f(proxy_wasm::WasmCallWord<1>) _f(proxy_wasm::WasmCallWord<2>) \ |
| 75 | + _f(proxy_wasm::WasmCallWord<3>) |
| 76 | + |
| 77 | +// These are templates and its helper for constructing signatures of functions callbacks from Wasm |
| 78 | +// VMs. |
| 79 | +// - WasmCallbackFuncTypeHelper is a helper for WasmFuncType and shouldn't be used anywhere else |
| 80 | +// than WasmFuncType definition. |
| 81 | +// - WasmCallbackFuncType takes 3 template parameter which are number of argument, return type, and |
| 82 | +// param type respectively, resolve to a function type. |
| 83 | +// For example `WasmFuncType<3, Word>` resolves to `void(Word, Word, Word)` |
| 84 | +template <size_t N, class ReturnType, class ParamType, class FuncBase = ReturnType()> |
| 85 | +struct WasmCallbackFuncTypeHelper {}; |
| 86 | + |
| 87 | +template <size_t N, class ReturnType, class ParamType, class... Args> |
| 88 | +struct WasmCallbackFuncTypeHelper<N, ReturnType, ParamType, ReturnType(Args...)> { |
| 89 | + // NOLINTNEXTLINE(readability-identifier-naming) |
| 90 | + using type = typename WasmCallbackFuncTypeHelper<N - 1, ReturnType, ParamType, |
| 91 | + ReturnType(Args..., ParamType)>::type; |
| 92 | +}; |
| 93 | + |
| 94 | +template <class ReturnType, class ParamType, class... Args> |
| 95 | +struct WasmCallbackFuncTypeHelper<0, ReturnType, ParamType, ReturnType(Args...)> { |
| 96 | + using type = ReturnType(Args...); // NOLINT(readability-identifier-naming) |
| 97 | +}; |
| 98 | + |
| 99 | +template <size_t N, class ReturnType, class ParamType> |
| 100 | +using WasmCallbackFuncType = typename WasmCallbackFuncTypeHelper<N, ReturnType, ParamType>::type; |
| 101 | + |
| 102 | +// Calls out of the WASM VM. |
| 103 | +template <size_t N> using WasmCallbackVoid = WasmCallbackFuncType<N, void, Word> *; |
| 104 | +template <size_t N> using WasmCallbackWord = WasmCallbackFuncType<N, Word, Word> *; |
| 105 | + |
| 106 | +// Using the standard g++/clang mangling algorithm: |
| 107 | +// https://itanium-cxx-abi.github.io/cxx-abi/abi.html#mangling-builtin |
| 108 | +// Extended with W = Word |
| 109 | +// Z = void, j = uint32_t, l = int64_t, m = uint64_t |
| 110 | +using WasmCallback_WWl = Word (*)(Word, int64_t); |
| 111 | +using WasmCallback_WWlWW = Word (*)(Word, int64_t, Word, Word); |
| 112 | +using WasmCallback_WWm = Word (*)(Word, uint64_t); |
| 113 | +using WasmCallback_WWmW = Word (*)(Word, uint64_t, Word); |
| 114 | +using WasmCallback_WWWWWWllWW = Word (*)(Word, Word, Word, Word, Word, int64_t, int64_t, Word, |
| 115 | + Word); |
| 116 | +using WasmCallback_dd = double (*)(double); |
| 117 | + |
| 118 | +#define FOR_ALL_WASM_VM_IMPORTS(_f) \ |
| 119 | + _f(proxy_wasm::WasmCallbackVoid<0>) _f(proxy_wasm::WasmCallbackVoid<1>) \ |
| 120 | + _f(proxy_wasm::WasmCallbackVoid<2>) _f(proxy_wasm::WasmCallbackVoid<3>) \ |
| 121 | + _f(proxy_wasm::WasmCallbackVoid<4>) _f(proxy_wasm::WasmCallbackWord<0>) \ |
| 122 | + _f(proxy_wasm::WasmCallbackWord<1>) _f(proxy_wasm::WasmCallbackWord<2>) \ |
| 123 | + _f(proxy_wasm::WasmCallbackWord<3>) _f(proxy_wasm::WasmCallbackWord<4>) \ |
| 124 | + _f(proxy_wasm::WasmCallbackWord<5>) _f(proxy_wasm::WasmCallbackWord<6>) \ |
| 125 | + _f(proxy_wasm::WasmCallbackWord<7>) _f(proxy_wasm::WasmCallbackWord<8>) \ |
| 126 | + _f(proxy_wasm::WasmCallbackWord<9>) \ |
| 127 | + _f(proxy_wasm::WasmCallbackWord<10>) \ |
| 128 | + _f(proxy_wasm::WasmCallbackWord<12>) \ |
| 129 | + _f(proxy_wasm::WasmCallback_WWl) \ |
| 130 | + _f(proxy_wasm::WasmCallback_WWlWW) \ |
| 131 | + _f(proxy_wasm::WasmCallback_WWm) \ |
| 132 | + _f(proxy_wasm::WasmCallback_WWmW) \ |
| 133 | + _f(proxy_wasm::WasmCallback_WWWWWWllWW) \ |
| 134 | + _f(proxy_wasm::WasmCallback_dd) |
| 135 | + |
| 136 | +enum class Cloneable { |
| 137 | + NotCloneable, // VMs can not be cloned and should be created from scratch. |
| 138 | + CompiledBytecode, // VMs can be cloned with compiled bytecode. |
| 139 | + InstantiatedModule // VMs can be cloned from an instantiated module. |
| 140 | +}; |
| 141 | + |
| 142 | +enum class AbiVersion { ProxyWasm_0_1_0, ProxyWasm_0_2_0, ProxyWasm_0_2_1, Unknown }; |
| 143 | + |
| 144 | +class NullPlugin; |
| 145 | + |
| 146 | +// Integrator specific WasmVm operations. |
| 147 | +struct WasmVmIntegration { |
| 148 | + virtual ~WasmVmIntegration() {} |
| 149 | + virtual WasmVmIntegration *clone() = 0; |
| 150 | + virtual proxy_wasm::LogLevel getLogLevel() = 0; |
| 151 | + virtual void error(std::string_view message) = 0; |
| 152 | + virtual void trace(std::string_view message) = 0; |
| 153 | + // Get a NullVm implementation of a function. |
| 154 | + // @param function_name is the name of the function with the implementation specific prefix. |
| 155 | + // @param returns_word is true if the function returns a Word and false if it returns void. |
| 156 | + // @param number_of_arguments is the number of Word arguments to the function. |
| 157 | + // @param plugin is the Null VM plugin on which the function will be called. |
| 158 | + // @param ptr_to_function_return is the location to write the function e.g. of type |
| 159 | + // WasmCallWord<3>. |
| 160 | + // @return true if the function was found. ptr_to_function_return could still be set to nullptr |
| 161 | + // (of the correct type) if the function has no implementation. Returning true will prevent a |
| 162 | + // "Missing getFunction" error. |
| 163 | + virtual bool getNullVmFunction(std::string_view function_name, bool returns_word, |
| 164 | + int number_of_arguments, NullPlugin *plugin, |
| 165 | + void *ptr_to_function_return) = 0; |
| 166 | +}; |
| 167 | + |
| 168 | +enum class FailState : int { |
| 169 | + Ok = 0, |
| 170 | + UnableToCreateVm = 1, |
| 171 | + UnableToCloneVm = 2, |
| 172 | + MissingFunction = 3, |
| 173 | + UnableToInitializeCode = 4, |
| 174 | + StartFailed = 5, |
| 175 | + ConfigureFailed = 6, |
| 176 | + RuntimeError = 7, |
| 177 | +}; |
| 178 | + |
| 179 | +// Wasm VM instance. Provides the low level WASM interface. |
| 180 | +class WasmVm { |
| 181 | +public: |
| 182 | + virtual ~WasmVm() = default; |
| 183 | + /** |
| 184 | + * Identify the Wasm engine. |
| 185 | + * @return the name of the underlying Wasm engine. |
| 186 | + */ |
| 187 | + virtual std::string_view getEngineName() = 0; |
| 188 | + |
| 189 | + /** |
| 190 | + * Whether or not the VM implementation supports cloning. Cloning is VM system dependent. |
| 191 | + * When a VM is configured a single VM is instantiated to check that the .wasm file is valid and |
| 192 | + * to do VM system specific initialization. Then, if cloning is supported, we clone that VM for |
| 193 | + * each worker, potentially copying and sharing the initialized data structures for efficiency. |
| 194 | + * Otherwise we create an new VM from scratch for each worker. |
| 195 | + * @return one of enum Cloneable with the VMs cloneability. |
| 196 | + */ |
| 197 | + virtual Cloneable cloneable() = 0; |
| 198 | + |
| 199 | + /** |
| 200 | + * Make a worker/thread-specific copy if supported by the underlying VM system (see cloneable() |
| 201 | + * above). If not supported, the caller will need to create a new VM from scratch. If supported, |
| 202 | + * the clone may share compiled code and other read-only data with the source VM. |
| 203 | + * @return a clone of 'this' (e.g. for a different worker/thread). |
| 204 | + */ |
| 205 | + virtual std::unique_ptr<WasmVm> clone() = 0; |
| 206 | + |
| 207 | + /** |
| 208 | + * Load the WASM code from a file. Return true on success. Once the module is loaded it can be |
| 209 | + * queried, e.g. to see which version of emscripten support is required. After loading, the |
| 210 | + * appropriate ABI callbacks can be registered and then the module can be link()ed (see below). |
| 211 | + * @param bytecode the Wasm bytecode or registered NullVm plugin name. |
| 212 | + * @param precompiled (optional) the precompiled Wasm module. |
| 213 | + * @param function_names (optional) an index-to-name mapping for the functions. |
| 214 | + * @return whether or not the load was successful. |
| 215 | + */ |
| 216 | + virtual bool load(std::string_view bytecode, std::string_view precompiled, |
| 217 | + const std::unordered_map<uint32_t, std::string> &function_names) = 0; |
| 218 | + |
| 219 | + /** |
| 220 | + * Link the WASM code to the host-provided functions, e.g. the ABI. Prior to linking, the module |
| 221 | + * should be loaded and the ABI callbacks registered (see above). Linking should be done once |
| 222 | + * after load(). |
| 223 | + * @param debug_name user-provided name for use in log and error messages. |
| 224 | + * @return whether or not the link was successful. |
| 225 | + */ |
| 226 | + virtual bool link(std::string_view debug_name) = 0; |
| 227 | + |
| 228 | + /** |
| 229 | + * Get size of the currently allocated memory in the VM. |
| 230 | + * @return the size of memory in bytes. |
| 231 | + */ |
| 232 | + virtual uint64_t getMemorySize() = 0; |
| 233 | + |
| 234 | + /** |
| 235 | + * Convert a block of memory in the VM to a std::string_view. |
| 236 | + * @param pointer the offset into VM memory of the requested VM memory block. |
| 237 | + * @param size the size of the requested VM memory block. |
| 238 | + * @return if std::nullopt then the pointer/size pair were invalid, otherwise returns |
| 239 | + * a host std::string_view pointing to the pointer/size pair in VM memory. |
| 240 | + */ |
| 241 | + virtual std::optional<std::string_view> getMemory(uint64_t pointer, uint64_t size) = 0; |
| 242 | + |
| 243 | + /** |
| 244 | + * Set a block of memory in the VM, returns true on success, false if the pointer/size is invalid. |
| 245 | + * @param pointer the offset into VM memory describing the start of a region of VM memory. |
| 246 | + * @param size the size of the region of VM memory. |
| 247 | + * @return whether or not the pointer/size pair was a valid VM memory block. |
| 248 | + */ |
| 249 | + virtual bool setMemory(uint64_t pointer, uint64_t size, const void *data) = 0; |
| 250 | + |
| 251 | + /** |
| 252 | + * Get a VM native Word (e.g. sizeof(void*) or sizeof(size_t)) from VM memory, returns true on |
| 253 | + * success, false if the pointer is invalid. WASM-32 VMs have 32-bit native words and WASM-64 VMs |
| 254 | + * (not yet supported) will have 64-bit words as does the Null VM (compiled into a 64-bit proxy). |
| 255 | + * This function can be used to chase pointers in VM memory. |
| 256 | + * @param pointer the offset into VM memory describing the start of VM native word size block. |
| 257 | + * @param data a pointer to a Word whose contents will be filled from the VM native word at |
| 258 | + * 'pointer'. |
| 259 | + * @return whether or not the pointer was to a valid VM memory block of VM native word size. |
| 260 | + */ |
| 261 | + virtual bool getWord(uint64_t pointer, Word *data) = 0; |
| 262 | + |
| 263 | + /** |
| 264 | + * Set a Word in the VM, returns true on success, false if the pointer is invalid. |
| 265 | + * See getWord above for details. This function can be used (for example) to set indirect pointer |
| 266 | + * return values (e.g. proxy_getHeaderHapValue(... const char** value_ptr, size_t* value_size). |
| 267 | + * @param pointer the offset into VM memory describing the start of VM native word size block. |
| 268 | + * @param data a Word whose contents will be written in VM native word size at 'pointer'. |
| 269 | + * @return whether or not the pointer was to a valid VM memory block of VM native word size. |
| 270 | + */ |
| 271 | + virtual bool setWord(uint64_t pointer, Word data) = 0; |
| 272 | + |
| 273 | + /** |
| 274 | + * @return the Word size in this VM. |
| 275 | + */ |
| 276 | + virtual size_t getWordSize() = 0; |
| 277 | + |
| 278 | + /** |
| 279 | + * Get the name of the custom section that contains precompiled module. |
| 280 | + * @return the name of the custom section that contains precompiled module. |
| 281 | + */ |
| 282 | + virtual std::string_view getPrecompiledSectionName() = 0; |
| 283 | + |
| 284 | + /** |
| 285 | + * Get typed function exported by the WASM module. |
| 286 | + */ |
| 287 | +#define _GET_FUNCTION(_T) virtual void getFunction(std::string_view function_name, _T *f) = 0; |
| 288 | + FOR_ALL_WASM_VM_EXPORTS(_GET_FUNCTION) |
| 289 | +#undef _GET_FUNCTION |
| 290 | + |
| 291 | + /** |
| 292 | + * Register typed callbacks exported by the host environment. |
| 293 | + */ |
| 294 | +#define _REGISTER_CALLBACK(_T) \ |
| 295 | + virtual void registerCallback(std::string_view moduleName, std::string_view function_name, _T f, \ |
| 296 | + typename ConvertFunctionTypeWordToUint32<_T>::type) = 0; |
| 297 | + FOR_ALL_WASM_VM_IMPORTS(_REGISTER_CALLBACK) |
| 298 | +#undef _REGISTER_CALLBACK |
| 299 | + |
| 300 | + /** |
| 301 | + * Terminate execution of this WasmVM. It shouldn't be used after being terminated. |
| 302 | + */ |
| 303 | + virtual void terminate() = 0; |
| 304 | + |
| 305 | + /** |
| 306 | + * Byte order flag (host or wasm). |
| 307 | + * @return 'false' for a null VM and 'true' for a wasm VM. |
| 308 | + */ |
| 309 | + virtual bool usesWasmByteOrder() = 0; |
| 310 | + |
| 311 | + bool isFailed() { return failed_ != FailState::Ok; } |
| 312 | + void fail(FailState fail_state, std::string_view message) { |
| 313 | + integration()->error(message); |
| 314 | + failed_ = fail_state; |
| 315 | + for (auto &callback : fail_callbacks_) { |
| 316 | + callback(fail_state); |
| 317 | + } |
| 318 | + } |
| 319 | + void addFailCallback(std::function<void(FailState)> fail_callback) { |
| 320 | + fail_callbacks_.push_back(fail_callback); |
| 321 | + } |
| 322 | + |
| 323 | + bool isHostFunctionAllowed(const std::string &name) { |
| 324 | + return !restricted_callback_ || allowed_hostcalls_.find(name) != allowed_hostcalls_.end(); |
| 325 | + } |
| 326 | + |
| 327 | + void setRestrictedCallback(bool restricted, |
| 328 | + std::unordered_set<std::string> allowed_hostcalls = {}) { |
| 329 | + restricted_callback_ = restricted; |
| 330 | + allowed_hostcalls_ = std::move(allowed_hostcalls); |
| 331 | + } |
| 332 | + |
| 333 | + // Integrator operations. |
| 334 | + std::unique_ptr<WasmVmIntegration> &integration() { return integration_; } |
| 335 | + bool cmpLogLevel(proxy_wasm::LogLevel level) { return integration_->getLogLevel() <= level; } |
| 336 | + |
| 337 | +protected: |
| 338 | + std::unique_ptr<WasmVmIntegration> integration_; |
| 339 | + FailState failed_ = FailState::Ok; |
| 340 | + std::vector<std::function<void(FailState)>> fail_callbacks_; |
| 341 | + |
| 342 | +private: |
| 343 | + bool restricted_callback_{false}; |
| 344 | + std::unordered_set<std::string> allowed_hostcalls_{}; |
| 345 | +}; |
| 346 | + |
| 347 | +// Thread local state set during a call into a WASM VM so that calls coming out of the |
| 348 | +// VM can be attributed correctly to calling Filter. We use thread_local instead of ThreadLocal |
| 349 | +// because this state is live only during the calls and does not need to be initialized consistently |
| 350 | +// over all workers as with ThreadLocal data. |
| 351 | +extern thread_local ContextBase *current_context_; |
| 352 | + |
| 353 | +// Requested effective context set by code within the VM to request that the calls coming out of the |
| 354 | +// VM be attributed to another filter, for example if a control plane gRPC comes back to the |
| 355 | +// RootContext which effects some set of waiting filters. |
| 356 | +extern thread_local uint32_t effective_context_id_; |
| 357 | + |
| 358 | +// Helper to save and restore thread local VM call context information to support reentrant calls. |
| 359 | +// NB: this happens for example when a call from the VM invokes a handler which needs to _malloc |
| 360 | +// memory in the VM. |
| 361 | +struct SaveRestoreContext { |
| 362 | + explicit SaveRestoreContext(ContextBase *context) { |
| 363 | + saved_context = current_context_; |
| 364 | + saved_effective_context_id_ = effective_context_id_; |
| 365 | + current_context_ = context; |
| 366 | + effective_context_id_ = 0; // No effective context id. |
| 367 | + } |
| 368 | + ~SaveRestoreContext() { |
| 369 | + current_context_ = saved_context; |
| 370 | + effective_context_id_ = saved_effective_context_id_; |
| 371 | + } |
| 372 | + ContextBase *saved_context; |
| 373 | + uint32_t saved_effective_context_id_; |
| 374 | +}; |
| 375 | + |
| 376 | +} // namespace proxy_wasm |
0 commit comments