recurse-talks/openbsd-pledge-unveil/openbsd-pledge-unveil.md

10 KiB
Executable File

#!/bin/env slides

OpenBSD, pledge(), and unveil()

By Bruce Hill

            _.-|-/\-._     
         \-'          '-.    
        /    /\    /\    \/          _____                 ____   _____ _____  
      \/  <    .  >  ./.  \/        / ___ \               |  _ \ / ____|  __ \ 
  _   /  <         > /___\ |.      / /  / /___  ___  ____ | |_) | (___ | |  | |
.< \ /  <     /\    > ( #) |#)    / /  / / __ \/ _ \/ __ \|  _ < \___ \| |  | |
  | |    <       /\   -.   __\   / /__/ / /_/ /  __/ / / /| |_) |____) | |__| |
   \   <  <   V      > )./_._(\  \_____/ .___/\___/_/ /_/ |____/|_____/|_____/ 
  .)/\   <  <  .-     /  \_'_) )-..   /_/                                    
      \  <   ./  /  > >       /._./
      /\   <  '-' >    >    /   
        '-._ < v    >   _.-'
          / '-.______.-' \
                 \/

(Puffy the OpenBSD mascot)


OpenBSD

  • BSD = Berkeley Software Distribution
    • Also known as "Berkeley Unix"
    • MacOS is based on BSD
sxiv -sf -f os-family-tree.png
  • Kinda like Linux, but cooler!
    • Better security
    • Simpler design
    • ...but maybe worse for personal computing
    • But great for web servers!
  • My website runs on OpenBSD!

Linux Analogy

The BSD Family:

  • FreeBSD: the Ubuntu of the BSD family
    • Relatively user-friendly
    • Good for desktops
  • NetBSD: the Linux Mint of the BSD family
    • Very portable
    • Good for low-resource machines
  • OpenBSD: the Arch/Qubes of the BSD family
    • Great documentation (manpages)
    • High emphasis on security

OpenBSD

  • Probably more secure than any OS you've ever used
    • Maybe Qubes is a contender? Debatable.
  • Really excellent code quality
    • Great learning resource
  • Ports collection for package management
    • Very cool, but out of scope for today
  • Numerous security improvements exported to other platforms:
    • OpenSSH
    • strlcpy()
    • arc4random()
  • Some security improvements that should be exported:
    • unveil()
    • pledge()

Tangent: Release Artwork

sxiv -sf -f


Tangent: Release Artwork

Every OS release has artwork and music made by the community

Every six months since 1996!


Defense in Depth

OpenBSD operates on the assumption that users will write and run software with bugs.

What happens next will amaze you!

When a program starts, it has some idea of what the expected behavior will be.

  • Which files or directories it needs to access
  • Which system calls will be needed
  • What kind of thing the program is doing

The idea is to pre-commit to only doing those things.

If a program has a bug that allows for unintended behavior, OpenBSD will limit the fallout of that bug.


Unveil

int unveil(const char *path, const char *permissions)

When a program starts, it can restrict itself to certain parts of the filesystem with unveil()

  • Limit which files and directories the program can see
  • Change whether the program has read/write/execute permissions on files

This is enforced by the OS and it will cause system calls like open() to return error values as if those files didn't exist or didn't have those permissions.

Unveil is additive so once you call it, it closes off access to all files on the filesystem except the ones you add access to.

less unveil.txt

Chroot Jail Comparison

unveil() is similar to chroot, but much, much easier to use.

Chroot:

  • Run a program in a fake (restricted) filesystem
  • Only include copies of stuff you want
  • Run your program so it thinks that is the whole filesystem
  • Annoying to set up
  • Requires copying files

Unveil:

  • Selectively mask off parts of the existing filesystem
  • Easy to add a few function calls or wrap a program
  • Don't need to copy any files
  • Can easily do read-only or write-only access

Using unveil()

unveil() is a drop-in security measure that can make a program more secure without invasive changes.

You just have to add a call to unveil() to the top of your program and any program that correctly handles non-existent files works as you would hope.

def buggy_write_to_tempfile(filename: str, contents: str):
    with open("/tmp/" + filename, "w") as f:
        f.write(contents)

# Uh oh!
buggy_write_to_tempfile("../etc/passwd", "Oh no")

Let's make it secure:

import openbsd
openbsd.unveil("/tmp", "rw")

# Now this is safe(r)!
buggy_write_to_tempfile("../etc/passwd", "Oh no")

Now we can't read or write any file outside of /tmp!


Pledge

int pledge(const char *promises, const char *execpromises)

pledge() lets you restrict which system calls the current program is allowed to make (and which ones any child processes can make).

Some interesting permissions:

  • stdio: do standard I/O operations like reading stdin and printing to stdout
  • rpath: perform read-only operations on the filesystem (e.g. cat)
  • wpath: perform write-only operations on the filesystem (e.g. a logger)
  • inet: do networking stuff like access the internet
  • exec: execute other programs
  • unveil: call unveil() to allow access to more files (!!!)
less pledge.txt

Pledge Case Study: Echo

The echo program in the OpenBSD codebase (slightly modified)

int main(int argc, char *argv[]) {
    if (pledge("stdio", NULL) == -1)
        err(1, "pledge");

    while (*argv) {
        (void)fputs(*argv, stdout);
        if (*++argv)
            putchar(' ');
    }
    putchar('\n');

    return 0;
}

Even if there were a bug in this program, it is near impossible for the program to do anything other than read stdin and print to stdout!


Pledge Narrowing: cat

Another useful technique is to start with maximum permissions and gradually drop permissions as you go.

Here's a simplified version of cat:

int main(int argc, char *argv[]) {
        pledge("stdio rpath", NULL);

        FILE *f = argc == 1 ? stdout : fopen(argv[1], "r");

        // Don't need FS read permissions anymore
        pledge("stdio", NULL);

        char buf[100];
        size_t just_read;
        while ((just_read=fread(buf, 1, sizeof(buf), f)) > 0)
                fwrite(buf, 1, just_read, stdout);

        fclose(f);
        return 0;
}

💀 BAD PRACTICE: not checking return values 💀


Combined pledge/unveil Example

From fsck.c, which checks the filesystem:

if (unveil("/dev", "rw") == -1)
    err(1, "unveil /dev");
if (unveil("/etc/fstab", "r") == -1)
    err(1, "unveil %s", _PATH_FSTAB);
if (unveil("/sbin", "x") == -1)
    err(1, "unveil /sbin");
if (pledge("stdio rpath wpath disklabel proc exec", NULL) == -1)
    err(1, "pledge");

It can:

  1. Read and write to /dev
  2. Read /etc/fstab
  3. Run programs in /sbin
  4. Do I/O, filesystem stuff, and run programs from /sbin

It cannot:

  1. Access password files
  2. Read from user's home directory
  3. Write files anywhere besides /dev
  4. Connect to the internet
  5. Kill other processes
  6. Change my pledge()/unveil() permissions further (!!!)

Case Study: Firefox

Firefox on OpenBSD is secured with pledge(2) and unveil(2) to limit the system calls and filesystem access that each of Firefox's process types (main, content, remote data decoder, audio decoder, socket and GPU) is permitted. By default, only ~/Downloads and /tmp can be written to when downloading files, or when viewing local files as file:// URLs.

Even if there are bugs in a massive program like Firefox, the scope of the damage an attacker could do is limited!


Conclusion

I hope you think these security tools are as cool as I do!

OpenBSD is a really great OS to run on a web server where you care a lot about security!

Most of the built-in software that ships with the OS makes good use of pledge() and unveil() to give you better security!

Try it yourself sometime! It's mostly familiar to Mac/Linux users, but with a few galaxy brain ideas.


The End

Thanks for your time!


Footnote 1: Code Quality

Compare OpenBSD's implementation of true:

OpenBSD - true.c

With the GNU Coure Utilities implementation used in Linux:

GNU Core Utilities - true.c


Footnote 2: Code Quality

OpenBSD comes with doas, which is under 500 lines of code:

*******************************************************************************
Language                     files          blank        comment           code
*******************************************************************************
C                                1             55             19            424
*******************************************************************************

The sudo that comes with Linux is nearly a quarter million lines of code!

*******************************************************************************
Language                     files          blank        comment           code
*******************************************************************************
C                              468         18,038         28,119        132,203
PO File                         74         30,350         48,907         78,866
Bourne Shell                    94          8,868          6,387         51,341
m4                              24           1336            607         14,761
...
*******************************************************************************
SUM:                           717         59,673         85,426        284,387
*******************************************************************************

More lines of code = more risk of vulnerabilities