Can I use threads?
Yes, threads are supported in Sandbox2.
All threads must be sandboxed
Because of the way Linux works, the seccomp-bpf policy is applied to the current thread only: this means the policy is not applied to other existing threads, but future threads will inherit the policy:
- If you are using Sandbox2 in the first
mode
where sandboxing is enabled before
execve()
, all threads will inherit the policy, and there is no problem. This is the preferred mode of sandboxing. - If you are using the second
mode
where the executor has
set_enable_sandbox_before_exec(false)
and the Sandboxee tells the executor when it wants to be sandboxed withSandboxMeHere()
, ensure the filter is applied to all threads. Otherwise, there is a risk of a sandbox escape: malicious code could migrate from a sandboxed thread to an unsandboxed thread.
How should I compile my Sandboxee?
In comparison to a statically linked executable, compiling the sandboxee to a
dynamically linked executable will result in a significant increase of syscalls
(e.g. open
/openat
, mmap
, etc.) that need to be allowlisted. All of these
additional syscalls are required due to the dynamic linker invocation at runtime
to load the shared libraries.
However, looking at statically linked sandboxees: While fewer syscalls need to be allowlisted, there are also security implications; the ASLR heap entropy is reduced (from 30 bits to 8 bits), which makes exploits easier.
This is a dilemma which can essentially be reduced to:
- Dynamic: good heap ASLR, potentially harder to get initial code execution but at the cost of a less effective sandbox policy, potentially easier to break out of.
- Static: bad heap ASLR, potentially easier to get initial code execution but a more effective sandbox policy, potentially harder to break out of.
Historically, statically linked binaries did not support position independent
code (pie
). In addition, Bazel added pie
by default. In order to be able to
define a tight syscall filter, you had to overwrite Bazel's default value.
Compilers have improved over the years and now support a static-pie
option.
With this option a compiler is instructed to generate position-independent code,
but compared to pie
this now also includes all the statically linked
libraries. From a security standpoint, static-pie
still reduces the ASLR
entropy (from 30 bits to 14 bits), but this is an improvement over the previous
situation without pie
.
As Bazel adds pie
by default and static is incompatible with it, consider
using the linker options flag to pass the -static-pie
linker flag to the
cc_binary
rule and overwrite the default:
linkstatic = 1,
linkopts=["-static-pie"],
For an example of these options, look at the
static example
BUILD:
static_bin.cc is linked statically with static-pie
, which makes it possible to
have a very tight syscall policy. This also works nicely for sandboxing
third-party binaries.
Can I sandbox 32-bit x86 binaries?
Sandbox2 can only sandbox the same architecture as it was compiled with.
In addition, support for 32-bit x86 has been removed from Sandbox2. If you try to use a 64-bit x86 executor to sandbox a 32-bit x86 binary, or a 64-bit x86 binary making 32-bit syscalls (via int 0x80), both will generate a sandbox violation that can be identified by the architecture label [X86-32].
The reason behind this behavior is that syscall numbers differ between architectures and since the syscall policy is written in the architecture of the executor, it would be dangerous to allow a different architecture for the Sandboxee. Indeed, this could lead to allowing a seemingly harmless syscall that in fact means another more harmful syscall could open up the sandbox to an escape.
Are there any limits on the number of sandboxes an executor process can request?
For each Sandboxee instance (new process spawned from the forkserver), a new thread is created – that's where the limitation lies.
Can an Executor request the creation of more than one Sandbox?
No. There is a 1:1 relation – an Executor instance stores the PID of the Sandboxee, manages the Comms instance to the Sandbox instance, etc.
Why do I get “Function not implemented” inside forkserver.cc?
Sandbox2 only supports running on reasonably new kernels. Our current cut-off is the 3.19 kernel, though that might change in the future. The reason for this is that we are using relatively new kernel features including user namespaces and seccomp with the TSYNC flag.
If you are running on prod, this should not be in issue, since almost the entire fleet is running a new enough kernel. If you have any issues with this, please contact us.
If you are running on Debian or Ubuntu, updating your kernel is as easy as running:
sudo apt-get install linux-image-<RECENT_VERSION>