RLBox is a toolkit for sandboxing third-party libraries. The toolkit consists of (1) a Wasm-based sandbox and (2) an API for retrofitting existing application code to interface with a sandboxed library. In this overview, we focus on the API, which abstracts over the underlying sandboxing mechanism. This lets you port your application without worrying about the Wasm sandboxing details. The Wasm-based sandbox is documented in a separate chaper.
Sandboxing libraries without the RLBox API is tedious and error-prone. This is especially the case when retrofitting an existing codebase like Firefox where libraries are trusted and thus the application-library boundary is blurry. To sandbox a library — and thus to move to a world where the library is no longer trusted — we need to modify this application-library boundary. For example, we need to add security checks in Firefox to ensure that any value from the sandboxed library is properly validated before it is used. Otherwise, the library (when compromised) may be able to abuse Firefox code to hijack its control flow 1. The RLBox API is explicitly designed to make retrofitting of existing application code simpler and less error-prone.2
RLBox ensures that a sandboxed library is memory isolated from the rest of the application — the library cannot directly access memory outside its designated region — and that all boundary crossings are explicit. This ensures that the library cannot, for example, corrupt Firefox's address space. It also ensures that Firefox cannot inadvertently expose sensitive data to the library. The figure below illustrates this idea.
Memory isolation is enforced by the underlying sandboxing mechanism (e.g.,
using Wasm3) from the start, when you create the sandbox with
create_sandbox(). Explicit boundary
crossings are enforced by RLBox (either at compile- or and run-time). For
example, with RLBox you can't call library functions directly; instead, you
must use the
invoke_sandbox_function() method. Similarly, the library cannot
call arbitrary Firefox functions; instead, it can only call functions that you
expose with the
method. (To simplify the sandboxing task, though, RLBox does expose a standard
library as described in the Standard Library.)
When calling a library function, RLBox copies simple values into the sandbox
memory before calling the function. For larger data types, such as structs and
arrays, you can't simply pass a pointer to the object. This would leak
more importantly, would not work: sandboxed code cannot access application
memory. So, you must explicitly allocate memory in the sandbox via
malloc_in_sandbox() and copy application
data to this region of memory (e.g., via
RLBox similarly copies simple return values and callback arguments. Larger data structures, however, must (again) be passed by sandbox-reference, i.e., via a reference/pointer to sandbox memory.
To ensure that application code doesn't unsafely use values that originate in the sandbox -- and may thus be under the control of an attacker -- RLBox considers all such values as untrusted and taints them. Tainted values are essentially opaque values (though RLBox does provide some basic operators on tainted values). To use a tainted value, you must unwrap it by (typically) copying the value into application memory -- and thus out of the reach of the attacker -- and verifying it. Indeed, RLBox forces application code to perform the copy and verification in sync using verification functions (see this chapter).
Retrofitting Fine Grain Isolation in the Firefox Renderer by S. Narayan, et al.
The Road to Less Trusted Code: Lowering the Barrier to In-Process Sandboxing by T. Garfinkel et al.