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:

  1. it revokes all privileges for the sandbox process except for “exec”
  2. … including revoking the ability to call unveil(2) .
  3. 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.