Title: How to add pledge to a program in OpenBSD
       Author: Solène
       Date: 08 September 2023
       Tags: security openbsd
       Description: In this article you will learn how to use OpenBSD specific
       feature pledge to prevent a program to misbehave.
       
       # Introduction
       
       This article is meant to be a simple guide explaining how to make use
       of the OpenBSD specific feature pledge in order to restrict a software
       capabilities for more security.
       
       While pledge falls in the sandboxing features, it's different than the
       traditional sandboxing we are used to see because it happens within the
       source code itself, and can be really tightened.  Actually, many
       programs requires lot of privileges like reading files, doing DNS
       etc... when initializing, then those privileges could be removed, this
       is possible with pledge but not for traditional sandboxing wrappers.
       
       In OpenBSD, most of the base userland have support for pledge, and more
       and more packaged software (including Chromium and Firefox) received
       some code to add pledge.  If a program tries to use a system call that
       isn't in pledge promises list, it dies and the violation is reported in
       the system logs.
       
       What makes pledge pretty cool is how it's easy to implement it in your
       software, it has a simple mechanism of system call families so you
       don't have to worry about listing every system calls, but only their
       categories (named promises), like reading a file, writing a file,
       executing binaries etc...
       
 (HTM) OpenBSD manual page for pledge(2)
       
       # Let's pledge a program
       
       I found a small utility that I will use to illustrate how to add pledge
       to a program.  The program is qprint, a C quoted printable
       encoder/decoder.  This kind of converter is quite easy to pledge
       because most of the time, they only take an input, do some computation
       and make an output, they don't run forever and don't do network.
       
 (HTM) qprint official project page
       
       ## Digging in the sources
       
       When extracting the sources, we can find a bunch of files, we will
       focus at reading the `*.c` files, the first thing we want to find is
       the function `main()`.
       
       It happens the main function is in the file `qprint.c`.  It's important
       to call pledge as soon as possible in the program, most of the time
       after variable initialization.
       
       ## Modifying the code
       
       Adding pledge to a program requires to understand how it works, because
       some feature that aren't often used may be broken by pledge, and some
       programs having live reloading or being able to change behavior during
       runtime are complicated to pledge.
       
       Within the function `main` below variables declaration, We will add a
       call to pledge for `stdio` because the program can display the result
       on the output, `rpath` because it can read files and `wpath` as it can
       also write files.
       
       ```c
       #include <unistd.h>
       [...]
       
       pledge("stdio rpath wpath", NULL);
       ```
       
       It's ok, we imported the library providing pledge, and called it from
       within.  But what if the pledge call fails for some reasons?  We need
       to ensure it worked or abort the program.  Let's add some checks.
       
       ```
       #include <unistd.h>
       #include <err.h>
       
       [...]
       
       if (pledge("stdio rpath wpath", NULL) == -1) {
           err(1, "pledge call didn't work");
       }
       ```
       
       This is a lot better now, if pledge call failed, the program will stop
       and we will be warned about it.  I don't know exactly under which
       circumstance it could fail, but maybe if promise name changes or
       doesn't exist anymore in a program, that would be bad if pledge
       silently failed.
       
       ## Testing
       
       Now we made some changes to the program, we need to verify it's still
       working as expected.
       
       Fortunately, qprint comes with a test suite which can be used with
       `make wringer`, if the test suite pass and the tests have a good
       coverage, this mean we may have not break anything.  If the test suite
       fails, we should have an error in the output of `dmesg` telling us why
       it failed.
       
       And, it failed!
       
       ```
       qprint[98802]: pledge "cpath", syscall 5
       ```
       
       This error (which killed the PID instantly) indicates that the pledge
       list is missing `cpath`, this makes sense because it has to create new
       files if you specify an output file.
       
       Adding `cpath` to the list, and running the test suite again, all tests
       pass!  Now, we exactly know that the software can't do anything except
       using the system calls we whitelisted.
       
       We could tighten pledge more by dropping `rpath` if the file is read
       from stdin, and `cpath wpath` if the output is sent to stdout.  I left
       this exercise to the reader :-)
       
       ## The diff
       
       Here is my diff to add pledge support to qprint.
       
       ```
       Index: qprint.c
       --- qprint.c.orig
       +++ qprint.c
       @@ -2,6 +2,8 @@
        #line 70 "./qprint.w"
        
        #include "config.h"                   
       +#include <unistd.h>
       +#include <err.h>
        
        #define REVDATE "16th December 2014" \
        
       @@ -747,6 +749,9 @@ char*cp;
        
        
        
       +if (pledge("stdio cpath rpath wpath", NULL) == -1) {
       +  err(1, "pledge error");
       +}
        
        fi= stdin;
        fo= stdout;
       ```
       
       # Using pledge in non-C programs
       
       It's actually possible to call pledge() in other programming languages,
       Perl has a library provided in OpenBSD base system that will work out
       of the box.  For some other, such library may be packaged already (for
       python and Golang at least).  If you use something less common, you can
       define an interface to call the library.
       
 (HTM) OpenBSD manual page for the Perl pledge library
       
       Here is an example in Common LISP to create a new function
       `c-kiosk-pledge`.
       
       ```common-lisp
       #+ecl
       (progn
         (ffi:clines "
           #include <unistd.h>
       
           void kioskPledge() {
              pledge(\"dns inet stdio tty rpath\",NULL);
           }
           #endif")
       
         #+openbsd
         (ffi:def-function
            ("kioskPledge" c-kiosk-pledge)
            () :returning :void))
       ```
       
       # Extra
       
       It's possible to find which running programs are currently using
       pledge() by using `ps auxww | awk '$8 ~ "p" { print }'`, any PID with a
       state containing `p` indicates it's pledged.
       
       If you want to add pledge to a packaged program on OpenBSD, make sure
       it still fully work.
       
       Adding pledge to a program that contain most promises won't be doing
       much...
       
       # Exercise reader
       
       Now, if you want to practice, you can tighten the pledge calls to only
       allow qprint to use the pledge `stdio` only in the case it's used in a
       pipe for input and output like this: `./qprint < input.txt >
       output.txt`.
       
       Ideally, it should add the pledge `cpath wpath` only when it writes
       into a file, and `rpath` only when it has to read a file, so in the
       case of using stdin and stdout, only `stdio` would have been added at
       the beginning.
       
       Good luck, Have fun!  Thanks to Brynet@ for the suggestion!
       
       # Conclusion
       
       The system call pledge() is a wonderful security feature that is
       reliable, and as it must be done in the source code, the program isn't
       run from within a sandboxed environment that may be possible to escape.
        I can't say pledge can't be escaped, but I think it's a lot less
       likely to be escaped than any other sandbox mechanism (especially since
       the program immediately dies if it tries to escape).
       
       Next time, I'll present its companion system called unveil which is
       used to restrict access to the filesystem, except some developer
       defined files.