Skip to content

Instantly share code, notes, and snippets.

@kripken
Last active April 8, 2024 21:53
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save kripken/2d982aede096e456591b2821a9d41b31 to your computer and use it in GitHub Desktop.
Save kripken/2d982aede096e456591b2821a9d41b31 to your computer and use it in GitHub Desktop.

Simple WasmBoxC Library Example

This shows how to easily sandbox a real-world library using WasmBoxC. Specifically we sandbox the zlib compression library.

To follow along with this, you can clone this gist and then do the following commands.

First, get the emsdk:

$ git clone https://github.com/emscripten-core/emsdk.git
$ cd emsdk
$ ./emsdk install latest
$ ./emsdk activate latest
$ source ./emsdk_env.sh
$ cd ..

Next, get zlib and compile it to wasm and then to sandboxed C (see notes below on flags):

$ git clone https://github.com/madler/zlib.git
$ cd zlib
$ emconfigure ./configure --static
$ emmake make -j8
$ emcc libz.a -O3 -o libz.wasm -s WASM2C --no-entry \
  -s EXPORTED_FUNCTIONS=[_compressBound,_compress,_uncompress,_malloc]
$ cd ..

Finally, compile the main program and run it!

$ clang main.c zlib/libz.wasm.c -O3
$ ./a.out
buffer size:       100000
compressed size:    25976
decompressed size: 100000
output looks good!

Notes:

  • -s WASM2C tells emscripten to not just compile to wasm but also compile the library using wabt's wasm2c tool.
  • --no-entry says we are building a library, and not an executable.
  • -s EXPORTED_FUNCTIONS=[_compressBound,_compress,_uncompress,_malloc] indicates what we want exported from the sandbox so that we can call it: three methods from zlib, and also malloc.
  • -O3 both on emcc and clang is pretty important here, as wasm2c output depends on compiler optimizations to run quickly. It does make it compile more slowly, though, for example on my machine clang -O0 takes 0.5 seconds while clang -O3 takes 5 (which is not that surprising since it's compiling all of zlib in a single C file there!)

See comments in the main.c file for how to call the sandboxed code and interact with it.

#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
typedef uint32_t u32;
// Exports from sandboxed code (we could also include libz.wasm.h and
// wasm-rt.h).
/** A Memory object. */
typedef struct {
/** The linear memory data, with a byte length of `size`. */
uint8_t* data;
/** The current and maximum page count for this Memory object. If there is no
* maximum, `max_pages` is 0xffffffffu (i.e. UINT32_MAX). */
uint32_t pages, max_pages;
/** The current size of the linear memory, in bytes. */
uint32_t size;
} wasm_rt_memory_t;
extern wasm_rt_memory_t *Z_memory;
// The zlib methods we need
extern u32 (*Z_compressZ_iiiii)(u32, u32, u32, u32);
extern u32 (*Z_compressBoundZ_ii)(u32);
extern u32 (*Z_uncompressZ_iiiii)(u32, u32, u32, u32);
// malloc() in the sandboxed wasm.
extern u32 (*Z_mallocZ_ii)(u32);
// Initialize the compiled wasm. This must be called before doing anything.
extern void wasmbox_init(void);
// Utilities
void* wasm_addr_to_absolute(u32 wasm_addr) {
return &Z_memory->data[wasm_addr];
}
void write_u32(u32 wasm_addr, u32 value) {
*(u32*)wasm_addr_to_absolute(wasm_addr) = value;
}
u32 read_u32(u32 wasm_addr) {
return *(u32*)wasm_addr_to_absolute(wasm_addr);
}
// Main code
int main() {
wasmbox_init();
int size = 100 * 1000;
// Create a buffer with some data that will compress fairly well.
unsigned char *externalBuffer = (unsigned char*)malloc(size);
int i = 0;
int run = 0;
char runChar = 17;
while (i < size) {
if (run > 0) {
run--;
} else {
if ((i & 7) == 0) {
runChar = i & 7;
run = i & 31;
} else {
runChar = (i*i) % 6714;
}
}
externalBuffer[i] = runChar;
i++;
}
u32 zlib_result;
// Allocate a buffer in the wasm, and copy the data to it.
printf("buffer size: %u\n", size);
u32 buffer = Z_mallocZ_ii(size);
memcpy(wasm_addr_to_absolute(buffer), externalBuffer, size);
// Get the maximum compressed size and allocate a buffer of that size.
u32 maxCompressedSize = Z_compressBoundZ_ii(size);
u32 buffer2 = Z_mallocZ_ii(maxCompressedSize);
// compress() has an in-out param which receives the size of the buffer for
// the compressed data, and will contain the used space in that buffer that
// the compressed data actually takes. For this we allocate a u32 whose
// address we can pass to zlib.
u32 compressedSizeBuffer = Z_mallocZ_ii(4);
write_u32(compressedSizeBuffer, maxCompressedSize);
// Compress the data!
zlib_result = Z_compressZ_iiiii(buffer2, compressedSizeBuffer, buffer, size);
assert(zlib_result == 0); // no error
u32 compressedSize = read_u32(compressedSizeBuffer);
printf("compressed size: %u\n", compressedSize);
// decompress() also has an in-out param.
u32 decompressedSizeBuffer = Z_mallocZ_ii(4);
write_u32(decompressedSizeBuffer, size);
// Allocate a buffer for the decompressed data and decompress it!
u32 buffer3 = Z_mallocZ_ii(size);
zlib_result = Z_uncompressZ_iiiii(buffer3, decompressedSizeBuffer, buffer2, compressedSize);
assert(zlib_result == 0); // no error
u32 decompressedSize = read_u32(decompressedSizeBuffer);
printf("decompressed size: %u\n", decompressedSize);
// Verify the expected size and that the decompressed data is identical to
// what we compressed (which in turn is the same as the original data outside
// the sandbox).
assert(decompressedSize == size);
if (memcmp(wasm_addr_to_absolute(buffer), wasm_addr_to_absolute(buffer3), size) != 0) {
puts("incorrect output!");
abort();
}
if (memcmp(externalBuffer, wasm_addr_to_absolute(buffer3), size) != 0) {
puts("incorrect output!");
abort();
}
puts("output looks good!");
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment