irssi is an IRC client.
Unlike most daemons, irssi
runs as a normal user and so the privileges of irssi
are just those of the invoking user.
By sandboxing irssi
, it is restricted beyond those privileges of the invoking user.
For example, irssi
is able to read /etc/passwd
and also run other programs.
This page describes how to sandbox irssi
under OpenBSD
using its builtin sandboxing mechanisms pledge(2)
and unveil(2)
.
Each restricts the capabilities of a process in different ways.
pledge(2)
limits the system calls that a process may use (e.g., whether another program can be run or not).
unveil(2)
restricts the filesystem access of a process (e.g., whether /etc/passwd
can be read or not).
The sandbox is another program that as it’s last action invokes irssi
, having revoked many of the user’s privileges.
The sandbox is written in C.
First include system headers and define the main function of the sandbox:
#include <unistd.h>
#include <err.h>
int main(int argc, char **argv, char **envp) {
Next define the path to the irssi
binary:
const char *prog = "/usr/local/bin/irssi";
Now define a list the files that irssi
can read and write.
Note that in the following you should take care to replace /home/USER
with the home directory of the user
that will run irssi
: ideally the sandbox would work all this out by itself but I have not done so for simplicity.
const char *rw_files[] = {
"/dev/null",
"/home/USER/.irssi",
"/home/USER/.terminfo",
};
List the files that irssi
that irssi
can read from (but not write):
const char *ro_files[] = {
"/etc/localtime",
"/etc/malloc.conf",
"/etc/resolv.conf",
"/etc/ssl/cert.pem",
"/usr/lib",
"/usr/libdata/perl5",
"/usr/libexec/ld.so",
"/usr/local/lib",
"/usr/local/libdata/perl5",
"/usr/local/share/irssi/help",
"/usr/share/terminfo",
"/usr/share/zoneinfo",
"/var/run/ld.so.hints",
};
The first argument to unveil(2) is the path to restrict and the second argument specifies the kind of access: {r}ead, {w}rite, {c}reate and/or e{x}ecute access, so a string “r” means read-only access and “rwc” requests read, write and create access. The sandbox iterates over all of the readable and writable files and uses unveil(2) to declare
for (int i = 0; i < sizeof(rw_files)/sizeof(rw_files[0]); i++) {
if (unveil(rw_files[i], "rwc") == -1) {
err(1, "unveil");
}
}
Then the sandbox iterates over all of the read-only files and invokes unveil(2) appropriately
for (int i = 0; i < sizeof(ro_files)/sizeof(ro_files[0]); i++) {
if (unveil(ro_files[i], "r") == -1) {
err(1, "unveil");
}
}
Finally the sandbox must give itself execute access to the irssi
binary
if (unveil(prog, "x") == -1) {
err(1, "unveil");
}
The next step is to call pledge(2) , which does many things:
- it revokes all privileges for the sandbox process except for “exec”
- … including revoking the ability to call unveil(2) .
- it sets the privileges of the process after the next exec(3) : see pledge(2) for a full description of the privileges.
This is all done by the following
if (pledge("exec", "stdio cpath rpath wpath inet dns tty proc getpw") == -1) {
err(1, "pledge");
}
Finally, runs irssi
.
execve(prog, argv, envp);
err(1, "execve");
}
Placing all of the above code in src/irssi.c
, it may be compiled as follows:
$ cc -D_FORTIFY_SOURCE=2 -O2 -Wl,-z,now -Wl,-z,relno -o bin/irssi src/irssi.c
Then if bin
is in your PATH
before /usr/local/bin
then when you type irssi
at the shell, you should invoke the sandboxed irssi
.
To test, type /exec cat /etc/passwd
inside irssi
: the process should be killed.