SYDTUTORIAL(7)

# NAME

*sydtutorial* - Tutorial introduction to Syd

# SYNOPSIS

*syd* \*

# DESCRIPTION

This tutorial explains how to sandbox applications using Syd, write sandbox
profiles, and configure Syd at runtime from within the sandbox. If you are
instead primarily interested in using Syd as a package build sandbox, like
we do at Exherbo Linux, you may prefer to start with _syd_(2) and the "paludis"
profile whose rules you may list using "syd-cat -p paludis".

# INTRODUCTION

Syd is secure by default and highly configurable for your application's usecase.
As we go towards the steps you are going to learn how to restrict an application
in various ways and at the same time keep the sandbox flexible for cases where
restriction is not possible and/or needed. To make the most out of this
tutorial, you are recommended to pick an application whose systemic
functionality is known to you and try and sandbox this application similar to
the instructions in the respective chapter. This functionality, above all,
includes the system calls the process calls to interact with the Linux kernel
and which parts of the filesystem/network the application needs to access to
fulfill its functionality correctly. _bpftrace_(1) and _strace_(1) are your
friends. In a further chapter we'll also get to know _pandora_(1) which is a
tool to generate Syd profiles automatically for a given application, stay tuned!

In its simplest sense, you can think Syd as a proxy between the Linux kernel and
the sandbox process: Syd checks system call arguments for access and if access
is granted Syd will execute the system call _on behalf of the sandbox process_
and return the result to the sandbox process. Going forward this is important to
keep in mind: from the point of view of the Linux kernel, it's one of Syd's
syscall handler threads that's running the syscall _not_ the sandbox process.
This is necessary to achieve a Time-of-check-to-Time-of-use free sandbox. Syd
does their best to reduce the side-effects, e.g. with ls /proc/self, the sandbox
process will still see their own process ID, not Syd's.

. *Learn by doing*: Trace your applications, learn the ins-and-outs!
. *Experiment*: Tweak Syd in various different ways and observe the effects!
. *Make it a game*: Try and break the own sandbox profile you configured, then make it stricter and retry!

# SYD QUICK INSTALL

You have the following alternatives:

. Use the latest release binary located @ https://distfiles.exherbo.org/#sydbox/
. cave resolve sys-apps/sydbox:3 # if Exherbo (unmask with testing keyword)
. emerge sys-apps/syd            # if Gentoo
. cargo install syd              # You will not get the manual pages, check: https://man.exherbo.org
. *Take the time to package Syd for your Linux distribution and spread the love!*

Note, releases are signed with this PGP key
https://keybase.io/alip/pgp_keys.asc, so take the time to verify the tarball you
downloaded. If using cargo to install, you need to install the "libseccomp"
library manually. This is a relatively common library and it's packaged by
almost all Linux distributions these days. Two things to keep in mind:

. Install libseccomp development headers (usually included or comes with e.g. the package libseccomp-devel).
. Install libseccomp static libraries if you want to link Syd statically (usually included or comes with e.g. the package libseccomp-static).

One final note, at the time of writing with libseccomp version 2.5.5, a patched
libseccomp is required to make interrupts work correctly under Syd
(libseccomp.git has support for the new Linux kernel flag already, we also add a
patch to set it by default). The binary release is built with a patched
libseccomp and Exherbo source build patches the libseccomp package during
preparation phase. Note, in our experience, this bug is mostly noticeable when
you sandbox applications written in the Go language. Otherwise, you'll rarely
notice it with the latest libseccomp release version. For reference, the patchset
resides here:
https://gitlab.exherbo.org/exherbo/arbor/-/tree/master/packages/sys-libs/libseccomp/files

# SYD 101

First, if you run Syd without arguments, you'll silently drop into a new shell.
This is because Syd is designed to act as a login shell and in this case it
uses the "user" sandbox profile. We'll get to profiles at a later chapter but if
you're curious do "syd-cat -p user | less" and read through the rules.

Second, Syd is secure by default and allows you to construct a sandbox to your
applications' needs. Here is how the state of the sandbox looks before we pass
any options to Syd:

```
$ syd -mstat
syd:
Process ID: 0
Lock: None
Capabilities: Read, Stat, Write, Execute, Connect, Bind
Options:
Memory Max: 134217728
Virtual Memory Max: 4294967296
Pid Max: 128
SegvGuard Max Crashes: 5
SegvGuard Expiry: 120 seconds
SegvGuard Suspension: 600 seconds
Allowed UID Transitions: (total: 0, source -> target)
Allowed GID Transitions: (total: 0, source -> target)
Cidr Rules: (total 0, highest precedence first)
Glob Rules: (total 0, highest precedence first)
Mask Rules: (total 1)
1. Pattern: /proc/cmdline
Force Rules: (total 0, default action: Kill)
$
```

For now let's just take into attention the "Capabilities" line. These are the
sandboxing types that are enabled at startup by default.

Initially, we'll do the bare minimum and try to execute a statically linked
binary under Syd. _busybox_(1) is a handy tool for our experiment:

```
$ file $(which busybox)
/usr/host/bin/busybox: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, stripped
$ syd busybox true
syd: exec error: No such file or directory
$ echo $?
2
$ syd-sys -e 2
2       ENOENT  No such file or directory
$
```

We get an error that the path does not exist. This is because "Stat
Sandboxing" is on by default and the path to the _busybox_(1) binary
is hidden. We can see that Syd makes clear by its exit value which
error caused the execution to fail. We use the utility _syd-sys_(1),
one of the many utilities that come with _syd_(1), to look up the
error definition by the exit code.

Let's try to allow and retry:

```
$ syd -m'allow/stat+/usr/host/bin/busybox' busybox true
syd: exec error: No such file or directory
$
```

No luck, we get the same error. This is because the path we specified to
"allow/stat" is not a canonicalised path. A canonicalised path is a path
which begins with "/" and has neither "." nor ".." nor repeating slashes
nor any symbolic links in any of its path components. Let's find out the
canonicalised path to our _busybox_(1) binary and retry with it.

```
$ readlink -f /usr/host/bin/busybox
/usr/x86_64-pc-linux-musl/bin/busybox
$ syd -m'allow/stat+/usr/x86_64-pc-linux-musl/bin/busybox' busybox true
{"act":"Deny","cap":"x","ctx":"access","id":"nostalgic_black","l":2,"path":"/usr/x86_64-pc-linux-musl/bin/busybox","pid":2602591,"sys":"execve","uid":1000,...}
syd: exec error: Permission denied
$ echo $?
13
$ syd-sys -e 13
13      EACCES  Permission denied
$
```

We get an error again, but this time we have context. Since Stat
Sandboxing is about hiding paths, reporting access violations about it
on standard error would beat its purpose so Syd was quiet.  However,
this time we see "Exec Sandboxing" at play and Syd gives us details
about the access violation. The format is JSON lines. It may be hard to
read at first but the fact that it's easily parseable allows you to
easily search for Syd access violation logs in your system log and filter
using tools such as _jq_(1).

Back to the task, for now let's briefly observe that this was an access
violation ("ctx":"access") about the _execve_(2) system call ("sys":"execve").
The access violation is of category Exec ("cap":"x") and the target path is
"/usr/x86_64-pc-linux-musl/bin/busybox". The decision was to deny the system
call ("act":"Deny"). We also have useful metadata such as the process ID ("pid")
and the user ID ("uid") executing the offending system call. The "id" field is a
human-readable name generated from the "pid" field to make logs easier to
follow. There are more information in the omitted fields, it's recommended that
you take a look at a complete access violation log entry on your own and make
note of the fields that are of value to you. Let's this time allow our
_busybox_(1) binary for exec and retry:

```
$ syd -m'allow/exec,stat+/usr/x86_64-pc-linux-musl/bin/busybox' busybox true
$ echo $?
$ 0
```

Task accomplished! Note, how we used the short notation
"allow/exec,stat+/path" which is a convenient way to pass -m
"allow/exec+/path" -m "allow/stat+/path" as a single rule.

Now let's try again with a dynamically linked executable and figure out what we
have to add to make it work. This time we will use the _gtrue_(1) utility from
the GNU coreutils project which is dynamically linked on this system:

```
$ file $(which gtrue)
/usr/host/bin/gtrue: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /usr/x86_64-pc-linux-musl/lib/ld-musl-x86_64.so.1, stripped
$ lddtree $(which gtrue)
/usr/x86_64-pc-linux-musl/lib/ld-musl-x86_64.so.1 => /usr/x86_64-pc-linux-musl/lib/libc.so
libc.so => /usr/x86_64-pc-linux-musl/lib/libc.so
$ readlink -f $(which gtrue)
/usr/x86_64-pc-linux-musl/bin/gtrue
$ syd -m'allow/exec,stat+/usr/x86_64-pc-linux-musl/bin/gtrue' gtrue
{"act":"Kill","cap":"x","ctx":"access","id":"compassionate_spence","l":2,"path":"/usr/x86_64-pc-linux-musl/lib/libc.so","pid":2601331,"sys":"exec","uid":1000,...}
$ echo $?
137
$
```

Observing the offending path of the new access violation, we understand libc.so
is denied execution access. We can also observe, this time Syd has terminated
the process ("act":"Kill") rather than denying access to the system call
("act":"Deny"). This is also evident from the exit code which is 137 = 128 + 9
where 9 is the value of the signal "SIGKILL". The deny/kill distinction stems
from Syd internals and is not significant for us at this point. Suffice it to
say in both cases the execution has been stopped before any code of the target
binary had a chance to run.

During access check for Exec Sandboxing, Syd treats dynamically linked
executables and their tree of dynamic library dependencies as a single
unit. In that sense "allow/read+/path/to/libc.so" and
"allow/exec+/path/to/libc.so" serves two different purposes: the former
allows you to literally read the contents of the file whilst the latter
allows you to load the file into memory as part of an executable.

Having clarified that, let's allow libc.so and retry:

```
$ syd -m'allow/exec,stat+/usr/x86_64-pc-linux-musl/bin/gtrue' -m 'allow/exec+/usr/x86_64-pc-linux-musl/lib/libc.so' gtrue
$ echo $?
0
$
```

Task accomplished! Curious reader will recognise we did not have to add
an "allow/stat" clause for "libc.so". This is because the concepts of
Stat Sandboxing and Path Hiding pertain specifically to direct access to
file paths. Loading libraries into memory is part of the execution
process and is therefore only subject to Exec Sandboxing (and Force
Sandboxing, aka Binary Verification, which we'll talk more about later).

Now at the third step, let's generalise our small sandbox such that it
will allow whichever version of the _true_(1) binary we execute,
moreover it will also allow the execution of any other coreutils utility
prefixed with "g\*". We also do not want to worry if "libc.so" has a
version suffix and want to allow all libraries under the common library
paths without having to list them one by one. To achieve all this we're
going to use _glob_(3) patterns:

```
$ eclectic coreutils list
Available providers for coreutils:
[1]   gnu
[2]   busybox *
$ readlink -f /bin/true
/usr/x86_64-pc-linux-musl/bin/busybox
$ syd -m'allow/stat,exec+/usr/**/bin/{busybox,g*}' -m 'allow/exec+/usr/**/lib*/*.so*' true
$ echo $?
0
$ doas eclectic coreutils set -1
$ readlink -f /bin/true
/usr/x86_64-pc-linux-musl/bin/gtrue
$ syd -m'allow/stat,exec+/usr/**/bin/{busybox,g*}' -m 'allow/exec+/usr/**/lib*/*.so*' true
$ echo $?
0
$
```

We have seen how _glob_(3) patterns make life easy for us in configuring our
sandbox. We have seen using "\*\*" is possible to match recursively and
alternates of the form "{foo,bar}" are supported. Syd also supports *empty
alternates* of the form "foo/{bar/,}baz" and the *triple star extension*, ie
"foo/\*\*\*" is equivalent to the combination of the two patterns "foo" and
"foo/\*\*". Finally we can see we managed to allow a lot more using the same
number of rules. Syd has many more powerful features that makes rule editing
simple and efficient such as:

- You may specify denylisted paths with "deny/" in addition to "allow/".
- You may specify filtered paths with "filter/", similar to "deny/" and "allow/" to quiet access violations but still deny access.
- If more than one rule matches the target path, *the last matching rule wins.*
- Many rules may be assembled into a configuration file and passed to Syd with _-P<path>_.
- Files having common rulesets can be included from other configuration files using the "include <path>" clause.
- Relative paths in "include" clauses are canonicalised based on the parent directory of the current configuration file (_not_ the current working directory!).
- Environment variables are expanded in configuration files. Unset environment variables will cause an error.
- Configuration can be locked at any point with the "lock:on" clause preventing further edits to the sandbox.

At this point you're highly recommended to experiment with configuring
Syd. Do not be afraid to add as many rules as you like. Internally, Syd
keeps _glob_(3) patterns as *globsets* and compiles them into a single
*regular expression* for efficient matching. This offers acceptable
performance up to roughly 10k rules on my system, your mileage may vary.

We have taken a sneak peek at how to configure Syd path allowlists. This is
similar for other sandboxing types. Let's leave those for later and explore
another way of configuring Syd. This time we'll do it at runtime, from within
the sandbox. It may come as a shock from a security perspective to allow access
to the sandbox policy from within the sandbox but Syd has a fair set of
restrictions to provide this usecase securely and as we'll see later this gives
the chance to restrict the sandbox process even further.  Another alternative is
to make Syd load a dynamic library at startup rather than running a command
which is another advanced topic for later. The idea of runtime configuration
depends on the *Sandbox Lock* and the lock can have three states: "on", "off",
and "exec". The first two are self-explanatory while "exec", allows access to
the sandbox policy only for the initial sandbox process. Once the sandbox lock
is set to "on", there is no turning back so subsequent edits to the sandbox will
no longer be possible. Now let's execute a shell under Syd. This time we will
not submit any configuration at startup and run Syd without arguments. This is
going to put Syd into login mode when Syd will use the builtin, dynamic "user"
profile and spawn a shell. We will not delve into details of the user profile
for now, check out "syd-cat -p user" if you're curious. Suffice it to say it
provides a relatively safe set of access rules to system paths and
read+write access to your HOME directory and user "/run"time paths.  In
addition, Syd comes with a shell library, called "esyd", that makes Syd
interaction easier:

```
TODO
```

# AUTHORS

Maintained by Ali Polatel. Up-to-date sources can be found at
https://gitlab.exherbo.org/sydbox/sydbox.git and bugs/patches can be
submitted to https://gitlab.exherbo.org/groups/sydbox/-/issues. Discuss
in #sydbox on Libera Chat or in #sydbox:mailstation.de on Matrix.
