Adding isolation with the Wasm sandbox backend

The noop backend makes it easy to add security checks. However, it does not enforce isolation. To finish sandboxing your library, we will need to.

  1. Update the application main.cpp to use the wasm2c sandbox backend instead of noop.

  2. Compile our library e.g. mylib.c to wasm i.e. mylib.wasm -- adding isolation to your library.

  3. Compile that resulting mylib.wasm file to C (mylib.wasm.c and mylib.wasm.h) with the wasm2c compiler -- allow it to be compiled and linked with our application.

  4. Compile and link the sandboxed library and our application.

We will look at each these steps next.

Switching to the wasm2c backend

Show below is a diff of main.cpp in our first example (using the noop sandbox backend) and in our current example (using the wasm2c backend).

1   $diff ../noop-hello-example/main.cpp main.cpp
2   3a4
3   >
4   5c6
5   < #define RLBOX_USE_STATIC_CALLS() rlbox_noop_sandbox_lookup_symbol
6   ---
7   > #define RLBOX_USE_STATIC_CALLS() rlbox_wasm2c_sandbox_lookup_symbol
8   9,11d9
9   < #include <rlbox/rlbox.hpp>
10  < #include <rlbox/rlbox_noop_sandbox.hpp>
11  <
12  12a11,14
13  > #include "mylib.wasm.h"
14  > #include "rlbox.hpp"
15  > #include "rlbox_wasm2c_sandbox.hpp"
16  >
17  18c20
18  < RLBOX_DEFINE_BASE_TYPES_FOR(mylib, noop);
19  ---
20  > RLBOX_DEFINE_BASE_TYPES_FOR(mylib, wasm2c);

As you can see, most of what has changed is renaming a few key instances of noop to wasm2c, most notably to change our backend type on line 10.

The only other changes are #include "mylib.wasm.h" on line 13, which brings in the new header for our sandboxed library generated by wasm2c.

These are essentially all the changes you will need to make to your application to switch wasm backends.

The changes to how rlbox is included on lines 14 and 15 are just an artifact of differences how our examples are built.

Our example Makefile

Doing all of these steps a command at a time would be terribly tedious. Instead, we automate all these steps with a simple make file. Lets take a look at our full Makefile, then walk through each part.

RLBOX_ROOT=../rlbox_wasm2c_sandbox

#RLBOX headers
RLBOX_INCLUDE=$(RLBOX_ROOT)/build/_deps/rlbox-src/code/include


#Our Wasi-SDK
WASI_SDK_ROOT=$(RLBOX_ROOT)/build/_deps/wasiclang-src/

#location of our wasi/wasm runtime
WASM2C_RUNTIME_PATH=$(RLBOX_ROOT)/build/_deps/mod_wasm2c-src/wasm2c
WASI_RUNTIME_FILES=$(addprefix $(WASM2C_RUNTIME_PATH), /wasm-rt-impl.c /wasm-rt-os-win.c  /wasm-rt-os-unix.c  /wasm-rt-wasi.c)

WASI_CLANG=$(WASI_SDK_ROOT)/bin/clang
WASI_SYSROOT=$(WASI_SDK_ROOT)/share/wasi-sysroot
WASM2C=$(RLBOX_ROOT)/build/_deps/mod_wasm2c-src/bin/wasm2c

#CFLAGS for compiling files output by wasm2co
WASM_CFLAGS=-Wl,--export-all -Wl,--no-entry -Wl,--growable-table -Wl,--stack-first -Wl,-z,stack-size=1048576

all: mylib.wasm mylib.wasm.c myapp

clean:
	rm -rf mylib.wasm mylib.wasm.c mylib.wasm.h myapp *.o

#Step 1: build our library into wasm, using clang from the wasi-sdk
mylib.wasm: mylib.c
	$(WASI_CLANG) --sysroot $(WASI_SYSROOT) $(WASM_CFLAGS) dummy_main.c mylib.c -o mylib.wasm

#Step 2: use wasm2c to convert our wasm to a C implementation of wasm we can link with our app.
mylib.wasm.c: mylib.wasm
	$(WASM2C) mylib.wasm -o mylib.wasm.c

#Step 3: compiling and linking our application with our library
myapp: mylib.wasm.c
	$(CC) -c $(WASI_RUNTIME_FILES) -I$(RLBOX_INCLUDE) -I$(RLBOX_ROOT)/include -I$(WASM2C_RUNTIME_PATH) mylib.wasm.c
	$(CXX) main.cpp -o myapp -I$(RLBOX_INCLUDE) -I$(RLBOX_ROOT)/include -I$(WASM2C_RUNTIME_PATH) *.o

Definitions

To start we can see our Makefile begins with RLBOX_ROOT:

RLBOX_ROOT=../rlbox_wasm2c_sandbox

Which just specificies where our rlbox_wasm2c_sandbox repo's root directory lives. This repo contains all the tools we will need build our sandboxed library e.g. wasm2c, our wasi-sdk (which CMake downloads), RLbox, etc.

Step 1: Compiling our library to Wasm

mylib.wasm: mylib.c
	$(WASI_CLANG) --sysroot $(WASI_SYSROOT) $(WASM_CFLAGS) dummy_main.c mylib.c -o mylib.wasm

Here we are building our library to wasm. Typically you will just want to update your build system to use the wasi-sdk clang (or wasi-clang) as your compiler. wasi-clang will link wasi-libc (a custom version of musl) with your library instead of the system libc. The wasi-libc library and headers live in $WASI_SYSROOT

Also note worthy are the $(WASM_CFLAGS) which are important to ensure that our output plays nicely with the rest of the toolchain.

Notice the dummy_main.c file to keep wasi-clang happy, you can find a copy rlbox_wasm2c_sandbox/_src/wasm2c_sandbox_wrapper.c

wasi-libc at the moment has a variety of limitations such as lack of pthread support (though this should be fixed soon!). Anything platform specific such as OS specific system calls (or just system calls that Wasi doesn't support), or platform specific code e.g. inline assembly will also fail at this stage.

Step 2: Using wasm2c to generate our sandboxed library

mylib.wasm.c: mylib.wasm
	$(WASM2C) mylib.wasm -o mylib.wasm.c

Here we use our fork of wasm2 to generates a mylib.wasm.c C file which implements and can be linked with an application.

The wasi runtime that ships with wasm2c at present implements only a subset of the Wasi API and denies all access to the file system and network.

Note: While RLBox currently only works with our fork of wasm2c we hope to upstream our changes to wasm2c in the near future.

Step 3: Compiling and linking our application with our library

myapp: mylib.wasm.c
	$(CC) -c $(WASI_RUNTIME_FILES) -I$(RLBOX_INCLUDE) -I$(RLBOX_ROOT)/include -I$(WASM2C_RUNTIME_PATH) mylib.wasm.c
	$(CXX) main.cpp -o myapp -I$(RLBOX_INCLUDE) -I$(RLBOX_ROOT)/include -I$(WASM2C_RUNTIME_PATH) *.o