Subject: bug#45198: 28.0.50; Sandbox mode

From: Philipp Stephani <[email-masked]>
Date: Mon, 14 Dec 2020 12:05:09 +0100

Am So., 13. Dez. 2020 um 19:43 Uhr schrieb Stefan Monnier
<[email-masked]>:
>
> > The sandboxing technologies I'm aware of are process-based (because
> > Linux namespaces and kernel syscall filters are per-process), so a
> > "start sandbox from here" function likely can't be implemented. The
> > interface should rather be something like
> >
> > (defun run-in-sandbox (form)
> >   (eql 0 (call-process "bwrap" ... "emacs" "--quick" "--batch" (format
> > "--eval=%S" form))))
> >
> > (Maybe with some async variant and the opportunity to return some
> > result, like emacs-async.)
>
> Thanks.  We definitely want some output, otherwise there's no point in
> running `form`, right?

In some way certainly, but it's not necessarily through stdout. I tend
to write potential output into a file whose filename is passed on the
command line. That's more robust than stdout (which often contains
spurious messages about loading files etc.).

>
> Also, I think the async option is the most important one.  How 'bout:
>
>     (sandbox-start FUNCTION)
>
>     Lunch a sandboxed Emacs subprocess running FUNCTION.

Passing a function here might be confusing because e.g. lexical
closures won't work. It might be preferable to pass a form and state
that both dynamic and lexical bindings are ignored.

>     Returns a process object.

Depending how much we care about forward compatibility, it might be
better to return an opaque sandbox object (which will initially wrap a
process object).

>     FUNCTION is called with no arguments and it can use `sandbox-read`
>     to read the data sent to the process object via `process-send-string`,
>     and `sandbox-reply` to send back a reply to the parent process
>     (which will receive it via its `process-filter`).

That is, sandbox-read and sandbox-reply just read/write stdin/stdout?
That would certainly work, but (a) it doesn't really have anything to
do with sandboxing, so these functions should rather be called
stdin-read and stdout-write or similar, and (b) especially in the
stdout case might be too brittle because Emacs likes to print
arbitrary stuff on stdout/stderr.
Alternatively, the sandbox could use dedicated files or pipes to communicate.

>     The sandboxed process has read access to all the local files
>     but no write access to them, nor any access to the network or
>     the display.

This might be a bit too specific. I'd imagine we'd want to restrict
reading files to the absolute minimum (files that Emacs itself needs
plus a fixed set of input files/directories known in advance), but
often allow writing some output files.

> WDYT?

I think the interface is mostly OK, but I think we want to restrict
the set of allowed input/output files.

>
> >> - I suspect we'll still want to use the extra "manual" checks I put in
> >>   my code (so as to get clean ELisp errors when bumping against the
> >>   walls of the sandbox, and because of the added in-depth security).
> > That's reasonable, though I'm worried that it will give users a false
> > sense of security.
>
> That would only be the case if we don't additionally use process-level
> isolation, right?

My worry is that people see a function like enter-sandbox and then
assume that Emacs will be secure after calling it, without actually
verifying the security implications.

>
> >> - This will need someone else doing the implementation.
> > Looks like we already have a volunteer for macOS.
> > For Linux, this shouldn't be that difficult either. The sandbox needs
> > to install a mount namespace that only allows read access to Emacs's
> > installation directory plus any input file and write access to known
> > output files, and enable syscall filters that forbid everything except
> > a list of known-safe syscalls (especially exec). I can take a stab at
> > that, but I can't promise anything ;-)
>
> Looking forward to it.
>

I've looked into this, and what I'd suggest for now is:
1. Add a --seccomp=FILE command-line option that loads seccomp filters
from FILE and applies them directly after startup (first thing in
main). Why do this in Emacs? Because that's the easiest way to prevent
execve. When installing a seccomp filter in a separate process, execve
needs to be allowed because otherwise there'd be no way to execute the
Emacs binary. While there are workarounds (ptrace, LD_PRELOAD), it's
easiest to install the seccomp filter directly in the Emacs process.
2. Generate appropriate seccomp filters using libseccomp or similar.
3. In the sandboxing functions, start Emacs with bwrap to set up
namespaces and invoke Emacs with the new --seccomp flag.