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.
-
Update the application
main.cpp
to use thewasm2c
sandbox backend instead ofnoop
. -
Compile our library e.g.
mylib.c
to wasm i.e.mylib.wasm
- adding isolation to your library. -
Compile that resulting
mylib.wasm
file to C (mylib.wasm.c
andmylib.wasm.h
) with thewasm2c
compiler - allow it to be compiled and linked with our application. -
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,7
3 >
4 > // Provide the sandbox a name
5 > #define RLBOX_WASM2C_MODULE_NAME mylib
6 >
7 5c9
8 < #define RLBOX_USE_STATIC_CALLS() rlbox_noop_sandbox_lookup_symbol
9 ---
10 > #define RLBOX_USE_STATIC_CALLS() rlbox_wasm2c_sandbox_lookup_symbol
11 9,11d12
12 < #include <rlbox/rlbox.hpp>
13 < #include <rlbox/rlbox_noop_sandbox.hpp>
14 <
15 12a14,17
16 > #include "mylib.wasm.h"
17 > #include "rlbox.hpp"
18 > #include "rlbox_wasm2c_sandbox.hpp"
19 >
20 18c23
21 < RLBOX_DEFINE_BASE_TYPES_FOR(mylib, noop);
22 ---
23 > 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 the backend type that we include.
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
, and
#define RLBOX_WASM2C_MODULE_NAME mylib
which will provide a unique name
for this sandbox.
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 17 and 18 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-mem-impl.c)
#Some more wasi/wasm runtime files
WASM2C_RUNTIME_FILES=$(addprefix $(RLBOX_ROOT)/src, /wasm2c_rt_minwasi.c /wasm2c_rt_mem.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 to place nice with wasm2c
WASM_CFLAGS=-Wl,--export-all -Wl,--stack-first -Wl,-z,stack-size=262144 -Wl,--no-entry -Wl,--growable-table -Wl,--import-memory -Wl,--import-table
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) $(WASM2C_RUNTIME_FILES) -I$(RLBOX_INCLUDE) -I$(RLBOX_ROOT)/include -I$(WASM2C_RUNTIME_PATH) mylib.wasm.c
$(CXX) -std=c++17 main.cpp -o myapp -I$(RLBOX_INCLUDE) -I$(RLBOX_ROOT)/include -I$(WASM2C_RUNTIME_PATH) *.o -lpthread
Definitions
To start we can see our Makefile begins with RLBOX_ROOT:
RLBOX_ROOT=../rlbox_wasm2c_sandbox
Which just specifies 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
, 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. By default, the flags we use
forces the Wasm module to use an "imported" memory allocation and "imported"
indirection function table that is created by RLBox for optimal performance.
(See Using a Wasm module with imported memory and imported tables
for other options)
Notice the dummy_main.c
file to keep wasi-clang happy. You can
find a copy of this file at rlbox_wasm2c_sandbox/c_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 wasm2c
to generates a mylib.wasm.c
C file which implements and
can be linked with an application.
The wasi runtime that ships with the rlbox_wasm2c_sandbox plugin at present implements only a subset of the Wasi API and denies all access to the file system and network.
Step 3: Compiling and linking our application with our library
myapp: mylib.wasm.c
$(CC) -c $(WASI_RUNTIME_FILES) $(WASM2C_RUNTIME_FILES) -I$(RLBOX_INCLUDE) -I$(RLBOX_ROOT)/include -I$(WASM2C_RUNTIME_PATH) mylib.wasm.c
$(CXX) -std=c++17 main.cpp -o myapp -I$(RLBOX_INCLUDE) -I$(RLBOX_ROOT)/include -I$(WASM2C_RUNTIME_PATH) *.o -lpthread