Running Untrusted Code in Spring
with WebAssembly
Dave Syer (2022)
@david_syer dsyer@vmware.com
What is WebAssembly?
● Host = application code (e.g. browser)
● Guest = WASM, compiled from C, C#, AssemblyScript, Rust, etc.
● Spec: https://github.com/WebAssembly/spec
● Originally targeted at browsers, so JavaScript is most common host
● Other hosts include Rust, Go, Python, C#, Java
● Sandbox - flexible with secure defaults
Host
Guest
Show me Some Code
(module
(func (export "add") (param i32) (param i32)
(result i32)
local.get 0
local.get 1
i32.add
)
)
Example playground:
https://developer.mozilla.org/en-US/docs/WebAssembly/Reference/Numeric/Addition
Linear Memory
(module
(memory (export "memory") 1)
(func (export "get") (param i32)
(result i32)
(i32.load (local.get 0))
)
)
1 page of memory is exported and accessed externally via the “get” function
Imports and Exports
(module
(import "env" "get" (func $get (result i32)))
(func (export "echo") (result i32)
(call $get)
)
)
The result of the “get” function is echoed back to the caller of “echo”
Options:
● Emscripten: https://github.com/emscripten-core/emscripten
● Wasi SDK: https://github.com/WebAssembly/wasi-sdk
● Binaryen: https://github.com/WebAssembly/binaryen
● Llvm/Clang: https://github.com/llvm/llvm-project
Echo Guest: C
int get();
int echo() {
return get();
}
Try it out at https://wasdk.github.io/WasmFiddle/
Echo Guest: AssemblyScript
// @ts-ignore: decorator
@external("env", "get")
declare function get(): i32
export function echo() : i32 {
return get();
}
Echo Guest: Rust
extern "C" {
pub fn get() -> i32;
}
#[no_mangle]
pub extern "C" fn echo() -> i32 {
get()
}
Echo Guest: Java
Options:
● TeamVM: https://github.com/konsoletyper/teavm
● JSweet: https://github.com/cincheo/jsweet
● J2cl: https://github.com/google/j2cl
public class HelloWorld {
public static void main(String[] args) throws Exception {}
@Export(name = "echo")
public static int echo() { return get(); }
@Import(module = "env", name = "get")
public static native int get();
}
Echo Host: JavaScript
var wasmModule = new WebAssembly.Module(wasmCode);
var wasmInstance = new WebAssembly.Instance(
wasmModule,
{"env": {"get": () => 1234}}
);
log(wasmInstance.exports.echo());
byte array
Echo Host: Java
try (Store<Void> store = Store.withoutData();
Engine engine = store.engine();
Module module = new Module(engine, wasmCode);
Linker linker = new Linker(store.engine())) {
linker.define("env", "get", Extern.fromFunc(WasmFunctions.wrap(store, I32, () -> 1234)));
linker.module(store, "", module);
try (Func func = linker.get(store, "", "echo").get().func()) {
Function0<Integer> echo = WasmFunctions.func(store, func, I32);
int result = echo.call();
System.out.println(result);
}
}
Using https://github.com/kawamuray/wasmtime-java
Something Less Trivial?
● Strings
● POJOs
● JSON
Exchanging Data Between Host and Guest
Host
Guest
memory
Exchanging Data Between Host and Guest
● Choose a binary format, e.g. Protobuf, Avro, MessagePack, JSON string
● Convert input and copy into shared memory
● Call WASM function with [ptr, len] tuple
● Output is another [ptr, len] tuple
● Copy output from shared memory and convert
ptr (input)
len
ptr (output)
len
Application Binary Interface (ABI)
Contract for exchanging data:
● Allocate and free memory (host and guest have to agree on location)
● Binary encoding format, e.g. Protobuf definitions
● Signature for exports - structure of input and output pointers
● (Optional as necessary) signature of imports
Draft spec for standardization: https://github.com/WebAssembly/component-model
Message Exchange Host: JavaScript
var encoded = encode(msg);
const bytes = malloc(encoded.length);
new Uint8Array(memory.buffer).set(encoded, bytes);
const output = malloc(8);
const input = malloc(8);
new Uint32Array(memory.buffer, input, 2).set([bytes, encoded.length]);
wasm.instance.exports.call(output, input);
var buffer = new Uint32Array(memory.buffer, output, 2).slice();
var result = message.SpringMessage.deserializeBinary(new Uint8Array(memory.buffer, buffer[0],
buffer[1]));
free(output);
free(input);
return decode(result);
input object
memory management (imported from wasm)
Message Exchange Host: Java
var buffer = memory.buffer(store);
try (var input = new Wrapper(buffer, message);
var output = new Wrapper(buffer)) {
linker.get(store, "", "call").get().func().call(store, Val.fromI32(output.ptr()),
Val.fromI32(input.ptr()));
return output.get(SpringMessage.class);
}
input object
memory management
Spring Host Ideas
● Some glue code for boilerplate WASM host stuff
● Spring Cloud Gateway - predicates and filters
● Spring Cloud Function - generic data transformation
● Kubernetes operator - webhook or controller, e.g. Cartographer
Demos:
● https://github.com/dsyer/spring-wasm-demo (client: C; host: Spring)
● https://github.com/dsyer/async-wasm (client: C, AS, Rust; host: javascript)
Links
● https://github.com/dsyer/spring-wasm-demo
● https://developer.mozilla.org/en-US/docs/WebAssembly/Reference - MDN docs with
WAT/Javascript playground
● https://mbebenita.github.io/WasmExplorer/ - playground with C/C++/WAT/assembly
● https://github.com/WebAssembly/component-model
● https://cartographer.sh/

Running Untrusted Code in Spring with WebAssembly

  • 1.
    Running Untrusted Codein Spring with WebAssembly Dave Syer (2022) @david_syer dsyer@vmware.com
  • 2.
    What is WebAssembly? ●Host = application code (e.g. browser) ● Guest = WASM, compiled from C, C#, AssemblyScript, Rust, etc. ● Spec: https://github.com/WebAssembly/spec ● Originally targeted at browsers, so JavaScript is most common host ● Other hosts include Rust, Go, Python, C#, Java ● Sandbox - flexible with secure defaults Host Guest
  • 3.
    Show me SomeCode (module (func (export "add") (param i32) (param i32) (result i32) local.get 0 local.get 1 i32.add ) ) Example playground: https://developer.mozilla.org/en-US/docs/WebAssembly/Reference/Numeric/Addition
  • 4.
    Linear Memory (module (memory (export"memory") 1) (func (export "get") (param i32) (result i32) (i32.load (local.get 0)) ) ) 1 page of memory is exported and accessed externally via the “get” function
  • 5.
    Imports and Exports (module (import"env" "get" (func $get (result i32))) (func (export "echo") (result i32) (call $get) ) ) The result of the “get” function is echoed back to the caller of “echo”
  • 6.
    Options: ● Emscripten: https://github.com/emscripten-core/emscripten ●Wasi SDK: https://github.com/WebAssembly/wasi-sdk ● Binaryen: https://github.com/WebAssembly/binaryen ● Llvm/Clang: https://github.com/llvm/llvm-project Echo Guest: C int get(); int echo() { return get(); } Try it out at https://wasdk.github.io/WasmFiddle/
  • 7.
    Echo Guest: AssemblyScript //@ts-ignore: decorator @external("env", "get") declare function get(): i32 export function echo() : i32 { return get(); }
  • 8.
    Echo Guest: Rust extern"C" { pub fn get() -> i32; } #[no_mangle] pub extern "C" fn echo() -> i32 { get() }
  • 9.
    Echo Guest: Java Options: ●TeamVM: https://github.com/konsoletyper/teavm ● JSweet: https://github.com/cincheo/jsweet ● J2cl: https://github.com/google/j2cl public class HelloWorld { public static void main(String[] args) throws Exception {} @Export(name = "echo") public static int echo() { return get(); } @Import(module = "env", name = "get") public static native int get(); }
  • 10.
    Echo Host: JavaScript varwasmModule = new WebAssembly.Module(wasmCode); var wasmInstance = new WebAssembly.Instance( wasmModule, {"env": {"get": () => 1234}} ); log(wasmInstance.exports.echo()); byte array
  • 11.
    Echo Host: Java try(Store<Void> store = Store.withoutData(); Engine engine = store.engine(); Module module = new Module(engine, wasmCode); Linker linker = new Linker(store.engine())) { linker.define("env", "get", Extern.fromFunc(WasmFunctions.wrap(store, I32, () -> 1234))); linker.module(store, "", module); try (Func func = linker.get(store, "", "echo").get().func()) { Function0<Integer> echo = WasmFunctions.func(store, func, I32); int result = echo.call(); System.out.println(result); } } Using https://github.com/kawamuray/wasmtime-java
  • 12.
    Something Less Trivial? ●Strings ● POJOs ● JSON
  • 13.
    Exchanging Data BetweenHost and Guest Host Guest memory
  • 14.
    Exchanging Data BetweenHost and Guest ● Choose a binary format, e.g. Protobuf, Avro, MessagePack, JSON string ● Convert input and copy into shared memory ● Call WASM function with [ptr, len] tuple ● Output is another [ptr, len] tuple ● Copy output from shared memory and convert ptr (input) len ptr (output) len
  • 15.
    Application Binary Interface(ABI) Contract for exchanging data: ● Allocate and free memory (host and guest have to agree on location) ● Binary encoding format, e.g. Protobuf definitions ● Signature for exports - structure of input and output pointers ● (Optional as necessary) signature of imports Draft spec for standardization: https://github.com/WebAssembly/component-model
  • 16.
    Message Exchange Host:JavaScript var encoded = encode(msg); const bytes = malloc(encoded.length); new Uint8Array(memory.buffer).set(encoded, bytes); const output = malloc(8); const input = malloc(8); new Uint32Array(memory.buffer, input, 2).set([bytes, encoded.length]); wasm.instance.exports.call(output, input); var buffer = new Uint32Array(memory.buffer, output, 2).slice(); var result = message.SpringMessage.deserializeBinary(new Uint8Array(memory.buffer, buffer[0], buffer[1])); free(output); free(input); return decode(result); input object memory management (imported from wasm)
  • 17.
    Message Exchange Host:Java var buffer = memory.buffer(store); try (var input = new Wrapper(buffer, message); var output = new Wrapper(buffer)) { linker.get(store, "", "call").get().func().call(store, Val.fromI32(output.ptr()), Val.fromI32(input.ptr())); return output.get(SpringMessage.class); } input object memory management
  • 18.
    Spring Host Ideas ●Some glue code for boilerplate WASM host stuff ● Spring Cloud Gateway - predicates and filters ● Spring Cloud Function - generic data transformation ● Kubernetes operator - webhook or controller, e.g. Cartographer Demos: ● https://github.com/dsyer/spring-wasm-demo (client: C; host: Spring) ● https://github.com/dsyer/async-wasm (client: C, AS, Rust; host: javascript)
  • 19.
    Links ● https://github.com/dsyer/spring-wasm-demo ● https://developer.mozilla.org/en-US/docs/WebAssembly/Reference- MDN docs with WAT/Javascript playground ● https://mbebenita.github.io/WasmExplorer/ - playground with C/C++/WAT/assembly ● https://github.com/WebAssembly/component-model ● https://cartographer.sh/