diff --git a/openbsd-pledge-unveil/coreutils-true.c b/openbsd-pledge-unveil/coreutils-true.c new file mode 100644 index 0000000..3ce0005 --- /dev/null +++ b/openbsd-pledge-unveil/coreutils-true.c @@ -0,0 +1,80 @@ +/* Exit with a status code indicating success. + Copyright (C) 1999-2025 Free Software Foundation, Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#include +#include +#include +#include "system.h" + +/* Act like "true" by default; false.c overrides this. */ +#ifndef EXIT_STATUS +# define EXIT_STATUS EXIT_SUCCESS +#endif + +#if EXIT_STATUS == EXIT_SUCCESS +# define PROGRAM_NAME "true" +#else +# define PROGRAM_NAME "false" +#endif + +#define AUTHORS proper_name ("Jim Meyering") + +void +usage (int status) +{ + printf (_("\ +Usage: %s [ignored command line arguments]\n\ + or: %s OPTION\n\ +"), + program_name, program_name); + printf ("%s\n\n", + _(EXIT_STATUS == EXIT_SUCCESS + ? N_("Exit with a status code indicating success.") + : N_("Exit with a status code indicating failure."))); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + printf (USAGE_BUILTIN_WARNING, PROGRAM_NAME); + emit_ancillary_info (PROGRAM_NAME); + exit (status); +} + +int +main (int argc, char **argv) +{ + /* Recognize --help or --version only if it's the only command-line + argument. */ + if (argc == 2) + { + initialize_main (&argc, &argv); + set_program_name (argv[0]); + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + /* Note true(1) will return EXIT_FAILURE in the + edge case where writes fail with GNU specific options. */ + atexit (close_stdout); + + if (STREQ (argv[1], "--help")) + usage (EXIT_STATUS); + + if (STREQ (argv[1], "--version")) + version_etc (stdout, PROGRAM_NAME, PACKAGE_NAME, Version, AUTHORS, + (char *) nullptr); + } + + return EXIT_STATUS; +} diff --git a/openbsd-pledge-unveil/doas.c b/openbsd-pledge-unveil/doas.c new file mode 100644 index 0000000..fe6a816 --- /dev/null +++ b/openbsd-pledge-unveil/doas.c @@ -0,0 +1,498 @@ +/* $OpenBSD: doas.c,v 1.99 2024/02/15 18:57:58 tedu Exp $ */ +/* + * Copyright (c) 2015 Ted Unangst + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "doas.h" + +static void __dead +usage(void) +{ + fprintf(stderr, "usage: doas [-Lns] [-a style] [-C config] [-u user]" + " command [arg ...]\n"); + exit(1); +} + +static int +parseuid(const char *s, uid_t *uid) +{ + struct passwd *pw; + const char *errstr; + + if ((pw = getpwnam(s)) != NULL) { + *uid = pw->pw_uid; + if (*uid == UID_MAX) + return -1; + return 0; + } + *uid = strtonum(s, 0, UID_MAX - 1, &errstr); + if (errstr) + return -1; + return 0; +} + +static int +uidcheck(const char *s, uid_t desired) +{ + uid_t uid; + + if (parseuid(s, &uid) != 0) + return -1; + if (uid != desired) + return -1; + return 0; +} + +static int +parsegid(const char *s, gid_t *gid) +{ + struct group *gr; + const char *errstr; + + if ((gr = getgrnam(s)) != NULL) { + *gid = gr->gr_gid; + if (*gid == GID_MAX) + return -1; + return 0; + } + *gid = strtonum(s, 0, GID_MAX - 1, &errstr); + if (errstr) + return -1; + return 0; +} + +static int +match(uid_t uid, gid_t *groups, int ngroups, uid_t target, const char *cmd, + const char **cmdargs, struct rule *r) +{ + int i; + + if (r->ident[0] == ':') { + gid_t rgid; + if (parsegid(r->ident + 1, &rgid) == -1) + return 0; + for (i = 0; i < ngroups; i++) { + if (rgid == groups[i]) + break; + } + if (i == ngroups) + return 0; + } else { + if (uidcheck(r->ident, uid) != 0) + return 0; + } + if (r->target && uidcheck(r->target, target) != 0) + return 0; + if (r->cmd) { + if (strcmp(r->cmd, cmd)) + return 0; + if (r->cmdargs) { + /* if arguments were given, they should match explicitly */ + for (i = 0; r->cmdargs[i]; i++) { + if (!cmdargs[i]) + return 0; + if (strcmp(r->cmdargs[i], cmdargs[i])) + return 0; + } + if (cmdargs[i]) + return 0; + } + } + return 1; +} + +static int +permit(uid_t uid, gid_t *groups, int ngroups, const struct rule **lastr, + uid_t target, const char *cmd, const char **cmdargs) +{ + size_t i; + + *lastr = NULL; + for (i = 0; i < nrules; i++) { + if (match(uid, groups, ngroups, target, cmd, + cmdargs, rules[i])) + *lastr = rules[i]; + } + if (!*lastr) + return -1; + if ((*lastr)->action == PERMIT) + return 0; + return -1; +} + +static void +parseconfig(const char *filename, int checkperms) +{ + extern FILE *yyfp; + extern int yyparse(void); + struct stat sb; + + yyfp = fopen(filename, "r"); + if (!yyfp) + err(1, checkperms ? "doas is not enabled, %s" : + "could not open config file %s", filename); + + if (checkperms) { + if (fstat(fileno(yyfp), &sb) != 0) + err(1, "fstat(\"%s\")", filename); + if ((sb.st_mode & (S_IWGRP|S_IWOTH)) != 0) + errx(1, "%s is writable by group or other", filename); + if (sb.st_uid != 0) + errx(1, "%s is not owned by root", filename); + } + + yyparse(); + fclose(yyfp); + if (parse_error) + exit(1); +} + +static void __dead +checkconfig(const char *confpath, int argc, char **argv, + uid_t uid, gid_t *groups, int ngroups, uid_t target) +{ + const struct rule *rule; + int rv; + + setresuid(uid, uid, uid); + if (pledge("stdio rpath getpw", NULL) == -1) + err(1, "pledge"); + parseconfig(confpath, 0); + if (!argc) + exit(0); + rv = permit(uid, groups, ngroups, &rule, target, argv[0], + (const char **)argv + 1); + if (rv == 0) { + printf("permit%s\n", (rule->options & NOPASS) ? " nopass" : ""); + exit(0); + } else { + printf("deny\n"); + exit(1); + } +} + +static int +authuser_checkpass(char *myname, char *login_style) +{ + char *challenge = NULL, *response, rbuf[1024], cbuf[128]; + auth_session_t *as; + + if (!(as = auth_userchallenge(myname, login_style, "auth-doas", + &challenge))) { + warnx("Authentication failed"); + return AUTH_FAILED; + } + if (!challenge) { + char host[HOST_NAME_MAX + 1]; + + if (gethostname(host, sizeof(host))) + snprintf(host, sizeof(host), "?"); + snprintf(cbuf, sizeof(cbuf), + "\rdoas (%.32s@%.32s) password: ", myname, host); + challenge = cbuf; + } + response = readpassphrase(challenge, rbuf, sizeof(rbuf), + RPP_REQUIRE_TTY); + if (response == NULL && errno == ENOTTY) { + syslog(LOG_AUTHPRIV | LOG_NOTICE, + "tty required for %s", myname); + errx(1, "a tty is required"); + } + if (!auth_userresponse(as, response, 0)) { + explicit_bzero(rbuf, sizeof(rbuf)); + syslog(LOG_AUTHPRIV | LOG_NOTICE, + "failed auth for %s", myname); + warnx("Authentication failed"); + return AUTH_FAILED; + } + explicit_bzero(rbuf, sizeof(rbuf)); + return AUTH_OK; +} + +static void +authuser(char *myname, char *login_style, int persist) +{ + int i, fd = -1; + + if (persist) + fd = open("/dev/tty", O_RDWR); + if (fd != -1) { + if (ioctl(fd, TIOCCHKVERAUTH) == 0) + goto good; + } + for (i = 0; i < AUTH_RETRIES; i++) { + if (authuser_checkpass(myname, login_style) == AUTH_OK) + goto good; + } + exit(1); +good: + if (fd != -1) { + int secs = 5 * 60; + ioctl(fd, TIOCSETVERAUTH, &secs); + close(fd); + } +} + +int +unveilcommands(const char *ipath, const char *cmd) +{ + char *path = NULL, *p; + int unveils = 0; + + if (strchr(cmd, '/') != NULL) { + if (unveil(cmd, "x") != -1) + unveils++; + goto done; + } + + if (!ipath) { + errno = ENOENT; + goto done; + } + path = strdup(ipath); + if (!path) { + errno = ENOENT; + goto done; + } + for (p = path; p && *p; ) { + char buf[PATH_MAX]; + char *cp = strsep(&p, ":"); + + if (cp) { + int r = snprintf(buf, sizeof buf, "%s/%s", cp, cmd); + if (r >= 0 && r < sizeof buf) { + if (unveil(buf, "x") != -1) + unveils++; + } + } + } +done: + free(path); + return (unveils); +} + +int +main(int argc, char **argv) +{ + const char *safepath = "/bin:/sbin:/usr/bin:/usr/sbin:" + "/usr/local/bin:/usr/local/sbin"; + const char *confpath = NULL; + char *shargv[] = { NULL, NULL }; + char *sh; + const char *p; + const char *cmd; + char cmdline[LINE_MAX]; + char mypwbuf[_PW_BUF_LEN], targpwbuf[_PW_BUF_LEN]; + struct passwd mypwstore, targpwstore; + struct passwd *mypw, *targpw; + const struct rule *rule; + uid_t uid; + uid_t target = 0; + gid_t groups[NGROUPS_MAX + 1]; + int ngroups; + int i, ch, rv; + int sflag = 0; + int nflag = 0; + char cwdpath[PATH_MAX]; + const char *cwd; + char *login_style = NULL; + char **envp; + + setprogname("doas"); + + closefrom(STDERR_FILENO + 1); + + uid = getuid(); + + while ((ch = getopt(argc, argv, "a:C:Lnsu:")) != -1) { + switch (ch) { + case 'a': + login_style = optarg; + break; + case 'C': + confpath = optarg; + break; + case 'L': + i = open("/dev/tty", O_RDWR); + if (i != -1) + ioctl(i, TIOCCLRVERAUTH); + exit(i == -1); + case 'u': + if (parseuid(optarg, &target) != 0) + errx(1, "unknown user"); + break; + case 'n': + nflag = 1; + break; + case 's': + sflag = 1; + break; + default: + usage(); + break; + } + } + argv += optind; + argc -= optind; + + if (confpath) { + if (sflag) + usage(); + } else if ((!sflag && !argc) || (sflag && argc)) + usage(); + + rv = getpwuid_r(uid, &mypwstore, mypwbuf, sizeof(mypwbuf), &mypw); + if (rv != 0) + err(1, "getpwuid_r failed"); + if (mypw == NULL) + errx(1, "no passwd entry for self"); + ngroups = getgroups(NGROUPS_MAX, groups); + if (ngroups == -1) + err(1, "can't get groups"); + groups[ngroups++] = getgid(); + + if (sflag) { + sh = getenv("SHELL"); + if (sh == NULL || *sh == '\0') { + shargv[0] = mypw->pw_shell; + } else + shargv[0] = sh; + argv = shargv; + argc = 1; + } + + if (confpath) { + if (pledge("stdio rpath getpw id", NULL) == -1) + err(1, "pledge"); + checkconfig(confpath, argc, argv, uid, groups, ngroups, + target); + exit(1); /* fail safe */ + } + + if (geteuid()) + errx(1, "not installed setuid"); + + parseconfig("/etc/doas.conf", 1); + + /* cmdline is used only for logging, no need to abort on truncate */ + (void)strlcpy(cmdline, argv[0], sizeof(cmdline)); + for (i = 1; i < argc; i++) { + if (strlcat(cmdline, " ", sizeof(cmdline)) >= sizeof(cmdline)) + break; + if (strlcat(cmdline, argv[i], sizeof(cmdline)) >= sizeof(cmdline)) + break; + } + + cmd = argv[0]; + rv = permit(uid, groups, ngroups, &rule, target, cmd, + (const char **)argv + 1); + if (rv != 0) { + syslog(LOG_AUTHPRIV | LOG_NOTICE, + "command not permitted for %s: %s", mypw->pw_name, cmdline); + errc(1, EPERM, NULL); + } + + if (!(rule->options & NOPASS)) { + if (nflag) + errx(1, "Authentication required"); + + authuser(mypw->pw_name, login_style, rule->options & PERSIST); + } + + if ((p = getenv("PATH")) != NULL) + formerpath = strdup(p); + if (formerpath == NULL) + formerpath = ""; + + if (unveil(_PATH_LOGIN_CONF, "r") == -1) + err(1, "unveil %s", _PATH_LOGIN_CONF); + if (unveil(_PATH_LOGIN_CONF ".db", "r") == -1) + err(1, "unveil %s.db", _PATH_LOGIN_CONF); + if (unveil(_PATH_LOGIN_CONF_D, "r") == -1) + err(1, "unveil %s", _PATH_LOGIN_CONF_D); + if (rule->cmd) { + if (setenv("PATH", safepath, 1) == -1) + err(1, "failed to set PATH '%s'", safepath); + } + if (unveilcommands(getenv("PATH"), cmd) == 0) + goto fail; + + if (pledge("stdio rpath getpw exec id", NULL) == -1) + err(1, "pledge"); + + rv = getpwuid_r(target, &targpwstore, targpwbuf, sizeof(targpwbuf), &targpw); + if (rv != 0) + err(1, "getpwuid_r failed"); + if (targpw == NULL) + errx(1, "no passwd entry for target"); + + if (setusercontext(NULL, targpw, target, LOGIN_SETGROUP | + LOGIN_SETPATH | + LOGIN_SETPRIORITY | LOGIN_SETRESOURCES | LOGIN_SETUMASK | + LOGIN_SETUSER | LOGIN_SETENV | LOGIN_SETRTABLE) != 0) + errx(1, "failed to set user context for target"); + + if (pledge("stdio rpath exec", NULL) == -1) + err(1, "pledge"); + + if (getcwd(cwdpath, sizeof(cwdpath)) == NULL) + cwd = "(failed)"; + else + cwd = cwdpath; + + if (pledge("stdio exec", NULL) == -1) + err(1, "pledge"); + + if (!(rule->options & NOLOG)) { + syslog(LOG_AUTHPRIV | LOG_INFO, + "%s ran command %s as %s from %s", + mypw->pw_name, cmdline, targpw->pw_name, cwd); + } + + envp = prepenv(rule, mypw, targpw); + + /* setusercontext set path for the next process, so reset it for us */ + if (rule->cmd) { + if (setenv("PATH", safepath, 1) == -1) + err(1, "failed to set PATH '%s'", safepath); + } else { + if (setenv("PATH", formerpath, 1) == -1) + err(1, "failed to set PATH '%s'", formerpath); + } + execvpe(cmd, argv, envp); +fail: + if (errno == ENOENT) + errx(1, "%s: command not found", cmd); + err(1, "%s", cmd); +} diff --git a/openbsd-pledge-unveil/openbsd-7.6.jpg b/openbsd-pledge-unveil/openbsd-7.6.jpg new file mode 100644 index 0000000..7d9cd50 Binary files /dev/null and b/openbsd-pledge-unveil/openbsd-7.6.jpg differ diff --git a/openbsd-pledge-unveil/openbsd-pledge-unveil.md b/openbsd-pledge-unveil/openbsd-pledge-unveil.md new file mode 100755 index 0000000..7e4ab76 --- /dev/null +++ b/openbsd-pledge-unveil/openbsd-pledge-unveil.md @@ -0,0 +1,389 @@ +#!/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 + +```demo +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](openbsd-7.6.jpg) + +------------------------------------- + +# Tangent: Release Artwork + +[Every OS release has artwork and music made by the community](https://www.openbsd.org/artwork.html) + +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 + +```c +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. + +```demo +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. + +```python +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: + +```python +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 + +```c +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 **(!!!)** + +```demo +less pledge.txt +``` + +---------------------------------- + +# Pledge Case Study: Echo + +The `echo` program in the OpenBSD codebase +(slightly modified) + +```c +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](https://github.com/openbsd/src/blob/master/bin/cat/cat.c) version of `cat`: + +```c +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: + +```c +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](openbsd-true.c) + +With the GNU Coure Utilities implementation used in Linux: + +![GNU Core Utilities - true.c](coreutils-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](https://www.alibabacloud.com/help/en/ecs/vulnerability-announcement-or-linux-sudo-permission-vulnerability) + diff --git a/openbsd-pledge-unveil/openbsd-true.c b/openbsd-pledge-unveil/openbsd-true.c new file mode 100644 index 0000000..90ecce6 --- /dev/null +++ b/openbsd-pledge-unveil/openbsd-true.c @@ -0,0 +1,9 @@ +/* $OpenBSD: true.c,v 1.1 2015/11/11 19:05:28 deraadt Exp $ */ + +/* Public domain - Theo de Raadt */ + + int +main(int argc, char *argv[]) +{ + return (0); +} diff --git a/openbsd-pledge-unveil/os-family-tree.png b/openbsd-pledge-unveil/os-family-tree.png new file mode 100644 index 0000000..db1b5f3 Binary files /dev/null and b/openbsd-pledge-unveil/os-family-tree.png differ diff --git a/openbsd-pledge-unveil/pledge.txt b/openbsd-pledge-unveil/pledge.txt new file mode 100644 index 0000000..52dd7da --- /dev/null +++ b/openbsd-pledge-unveil/pledge.txt @@ -0,0 +1,300 @@ +PLEDGE(2) System Calls Manual PLEDGE(2) + +NNAAMMEE + pplleeddggee - restrict system operations + +SSYYNNOOPPSSIISS + ##iinncclluuddee <> + + _i_n_t + pplleeddggee(_c_o_n_s_t _c_h_a_r _*_p_r_o_m_i_s_e_s, _c_o_n_s_t _c_h_a_r _*_e_x_e_c_p_r_o_m_i_s_e_s); + +DDEESSCCRRIIPPTTIIOONN + The pplleeddggee() system call forces the current process into a restricted- + service operating mode. A few subsets are available, roughly described + as computation, memory management, read-write operations on file + descriptors, opening of files, networking (and notably separate, DNS + resolution). In general, these modes were selected by studying the + operation of many programs using libc and other such interfaces, and + setting _p_r_o_m_i_s_e_s or _e_x_e_c_p_r_o_m_i_s_e_s. + + Use of pplleeddggee() in an application will require at least some study and + understanding of the interfaces called. Subsequent calls to pplleeddggee() can + reduce the abilities further, but abilities can never be regained. + + A process which attempts a restricted operation is killed with an + uncatchable SIGABRT, delivering a core file if possible. A process + currently running with pledge has state `p' in ps(1) output; a process + that was terminated due to a pledge violation is accounted by lastcomm(1) + with the `P' flag. + + A _p_r_o_m_i_s_e_s value of "" restricts the process to the _exit(2) system call. + This can be used for pure computation operating on memory shared with + another process. + + Passing NULL to _p_r_o_m_i_s_e_s or _e_x_e_c_p_r_o_m_i_s_e_s specifies to not change the + current value. + + Some system calls, when allowed, have restrictions applied to them: + + access(2): + May check for existence of _/_e_t_c_/_l_o_c_a_l_t_i_m_e. + + adjtime(2): + Read-only, for ntpd(8). + + chmod(2), fchmod(2), fchmodat(2), chown(2), lchown(2), fchown(2), + fchownat(2), mkfifo(2), and mknod(2): + Setuid/setgid/sticky bits are ignored. The user or group cannot be + changed on a file. + + ioctl(2): + Only the FIONREAD, FIONBIO, FIOCLEX, and FIONCLEX operations are + allowed by default. Various ioctl requests are allowed against + specific file descriptors based upon the requests aauuddiioo, bbppff, + ddiisskkllaabbeell, ddrrmm, iinneett, ppff, rroouuttee, wwrroouuttee, ttaappee, ttttyy, vviiddeeoo, and vvmmmm. + + mmap(2) and mprotect(2): + PROT_EXEC isn't allowed. + + open(2): + May open _/_e_t_c_/_l_o_c_a_l_t_i_m_e and any files below _/_u_s_r_/_s_h_a_r_e_/_z_o_n_e_i_n_f_o. + + profil(2): + Can only disable profiling. + + pplleeddggee(): + Can only reduce permissions for _p_r_o_m_i_s_e_s and _e_x_e_c_p_r_o_m_i_s_e_s. + + sysctl(2): + A small set of read-only operations are allowed, sufficient to + support: getdomainname(3), gethostname(3), getifaddrs(3), uname(3), + and system sensor readings. + + The _p_r_o_m_i_s_e_s argument is specified as a string, with space separated + keywords: + + ssttddiioo The following system calls are permitted. sendto(2) is + only permitted if its destination socket address is + NULL. As a result, all the expected functionalities of + libc stdio work. + + clock_getres(2), clock_gettime(2), close(2), + closefrom(2), dup(2), dup2(2), dup3(2), fchdir(2), + fcntl(2), fstat(2), fsync(2), ftruncate(2), + getdtablecount(2), getegid(2), getentropy(2), + geteuid(2), getgid(2), getgroups(2), getitimer(2), + getlogin(2), getpgid(2), getpgrp(2), getpid(2), + getppid(2), getresgid(2), getresuid(2), getrlimit(2), + getrtable(2), getsid(2), getthrid(2), gettimeofday(2), + getuid(2), issetugid(2), kevent(2), kqueue(2), + kqueue1(2), lseek(2), madvise(2), minherit(2), mmap(2), + mprotect(2), mquery(2), munmap(2), nanosleep(2), + pipe(2), pipe2(2), poll(2), pread(2), preadv(2), + profil(2), pwrite(2), pwritev(2), read(2), readv(2), + recvfrom(2), recvmsg(2), select(2), sendmsg(2), + sendsyslog(2), sendto(2), setitimer(2), shutdown(2), + sigaction(2), sigprocmask(2), sigreturn(2), + socketpair(2), umask(2), wait4(2), waitid(2), write(2), + writev(2) + + rrppaatthh A number of system calls are allowed if they only cause + read-only effects on the filesystem, or expose filenames + to programs: + + chdir(2), getcwd(3), getdents(2), openat(2), fstatat(2), + faccessat(2), readlinkat(2), lstat(2), chmod(2), + fchmod(2), fchmodat(2), chflags(2), chflagsat(2), + chown(2), fchown(2), fchownat(2), fstat(2), getfsstat(2) + + wwppaatthh A number of system calls are allowed and may cause + write-effects on the filesystem: + + getcwd(3), openat(2), fstatat(2), faccessat(2), + readlinkat(2), lstat(2), chmod(2), fchmod(2), + fchmodat(2), chflags(2), chflagsat(2), chown(2), + fchown(2), fchownat(2), fstat(2) + + ccppaatthh A number of system calls and sub-modes are allowed, + which may create new files or directories in the + filesystem: + + rename(2), renameat(2), link(2), linkat(2), symlink(2), + symlinkat(2), unlink(2), unlinkat(2), mkdir(2), + mkdirat(2), rmdir(2) + + ddppaatthh A number of system calls are allowed to create special + files: + + mkfifo(2), mknod(2) + + ttmmppppaatthh A number of system calls are allowed to do operations in + the _/_t_m_p directory, including create, read, or write: + + lstat(2), chmod(2), chflags(2), chown(2), unlink(2), + fstat(2) + + iinneett The following system calls are allowed to operate in the + AF_INET and AF_INET6 domains (though setsockopt(2) has + been substantially reduced in functionality): + + socket(2), listen(2), bind(2), connect(2), accept4(2), + accept(2), getpeername(2), getsockname(2), + setsockopt(2), getsockopt(2) + + mmccaasstt In combination with iinneett give back functionality to + setsockopt(2) for operating on multicast sockets. + + ffaattttrr The following system calls are allowed to make explicit + changes to fields in _s_t_r_u_c_t _s_t_a_t relating to a file: + + utimes(2), futimes(2), utimensat(2), futimens(2), + chmod(2), fchmod(2), fchmodat(2), chflags(2), + chflagsat(2), chown(2), fchownat(2), lchown(2), + fchown(2), utimes(2) + + cchhoowwnn The chown(2) family is allowed to change the user or + group on a file. + + fflloocckk File locking via fcntl(2), flock(2), lockf(3), and + open(2) is allowed. No distinction is made between + shared and exclusive locks. This promise is required + for unlock as well as lock. + + uunniixx The following system calls are allowed to operate in the + AF_UNIX domain: + + socket(2), listen(2), bind(2), connect(2), accept4(2), + accept(2), getpeername(2), getsockname(2), + setsockopt(2), getsockopt(2) + + ddnnss Subsequent to a successful open(2) of _/_e_t_c_/_r_e_s_o_l_v_._c_o_n_f, + a few system calls become able to allow DNS network + transactions: + + sendto(2), recvfrom(2), socket(2), connect(2) + + ggeettppww This allows read-only opening of files in _/_e_t_c for the + getpwnam(3), getgrnam(3), getgrouplist(3), and + initgroups(3) family of functions, including lookups via + the yp(8) protocol for YP and LDAP databases. + + sseennddffdd Allows sending of file descriptors using sendmsg(2). + File descriptors referring to directories may not be + passed. + + rreeccvvffdd Allows receiving of file descriptors using recvmsg(2). + File descriptors referring to directories may not be + passed. + + ttaappee Allow MTIOCGET and MTIOCTOP operations against tape + drives. + + ttttyy In addition to allowing read-write operations on + _/_d_e_v_/_t_t_y, this opens up a variety of ioctl(2) requests + used by tty devices. If ttttyy is accompanied with rrppaatthh, + revoke(2) is permitted. Otherwise only the following + ioctl(2) requests are permitted: + + TIOCSPGRP, TIOCGETA, TIOCGPGRP, TIOCGWINSZ, TIOCSWINSZ, + TIOCSBRK, TIOCCDTR, TIOCSETA, TIOCSETAW, TIOCSETAF, + TIOCUCNTL + + pprroocc Allows the following process relationship operations: + + fork(2), vfork(2), kill(2), getpriority(2), + setpriority(2), setrlimit(2), setpgid(2), setsid(2) + + eexxeecc Allows a process to call execve(2). Coupled with the + pprroocc promise, this allows a process to fork and execute + another program. If _e_x_e_c_p_r_o_m_i_s_e_s has been previously + set the new program begins with those promises, unless + setuid/setgid bits are set in which case execution is + blocked with EACCES. Otherwise the new program starts + running without pledge active, and hopefully makes a new + pledge soon. + + pprroott__eexxeecc Allows the use of PROT_EXEC with mmap(2) and + mprotect(2). + + sseettttiimmee Allows the setting of system time, via the + settimeofday(2), adjtime(2), and adjfreq(2) system + calls. + + ppss Allows enough sysctl(2) interfaces to allow inspection + of processes operating on the system using programs like + ps(1). + + vvmmiinnffoo Allows enough sysctl(2) interfaces to allow inspection + of the system's virtual memory by programs like top(1) + and vmstat(8). + + iidd Allows the following system calls which can change the + rights of a process: + + setuid(2), seteuid(2), setreuid(2), setresuid(2), + setgid(2), setegid(2), setregid(2), setresgid(2), + setgroups(2), setlogin(2), setrlimit(2), getpriority(2), + setpriority(2), setrtable(2) + + ppff Allows a subset of ioctl(2) operations on the pf(4) + device: + + DIOCADDRULE, DIOCGETSTATUS, DIOCNATLOOK, DIOCRADDTABLES, + DIOCRCLRADDRS, DIOCRCLRTABLES, DIOCRCLRTSTATS, + DIOCRGETTSTATS, DIOCRSETADDRS, DIOCXBEGIN, DIOCXCOMMIT + + rroouuttee Allow inspection of the routing table. + + wwrroouuttee Allow changes to the routing table. + + aauuddiioo Allows a subset of ioctl(2) operations on audio(4) + devices (see sio_open(3) for more information): + + AUDIO_GETPOS, AUDIO_GETPAR, AUDIO_SETPAR, AUDIO_START, + AUDIO_STOP, AUDIO_MIXER_DEVINFO, AUDIO_MIXER_READ, + AUDIO_MIXER_WRITE + + vviiddeeoo Allows a subset of ioctl(2) operations on video(4) + devices: + + VIDIOC_DQBUF, VIDIOC_ENUM_FMT, + VIDIOC_ENUM_FRAMEINTERVALS, VIDIOC_ENUM_FRAMESIZES, + VIDIOC_G_CTRL, VIDIOC_G_PARM, VIDIOC_QBUF, + VIDIOC_QUERYBUF, VIDIOC_QUERYCAP, VIDIOC_QUERYCTRL, + VIDIOC_S_CTRL, VIDIOC_S_FMT, VIDIOC_S_PARM, + VIDIOC_STREAMOFF, VIDIOC_STREAMON, VIDIOC_TRY_FMT, + VIDIOC_REQBUFS + + bbppff Allow BIOCGSTATS operation for statistics collection + from a bpf(4) device. + + uunnvveeiill Allow unveil(2) to be called. + + eerrrroorr Rather than killing the process upon violation, indicate + error with ENOSYS. + + Also when pplleeddggee() is called with higher _p_r_o_m_i_s_e_s or + _e_x_e_c_p_r_o_m_i_s_e_s, those changes will be ignored and return + success. This is useful when a parent enforces + _e_x_e_c_p_r_o_m_i_s_e_s but an execve'd child has a different idea. + +RREETTUURRNN VVAALLUUEESS + Upon successful completion, the value 0 is returned; otherwise the + value -1 is returned and the global variable _e_r_r_n_o is set to indicate the + error. + +EERRRROORRSS + pplleeddggee() will fail if: + + [EFAULT] _p_r_o_m_i_s_e_s or _e_x_e_c_p_r_o_m_i_s_e_s points outside the process's + allocated address space. + + [EINVAL] _p_r_o_m_i_s_e_s is malformed or contains invalid keywords. + + [EPERM] This process is attempting to increase permissions. + +HHIISSTTOORRYY + The pplleeddggee() system call first appeared in OpenBSD 5.9. + +OpenBSD 7.6 September 17, 2024 OpenBSD 7.6 diff --git a/openbsd-pledge-unveil/unveil.txt b/openbsd-pledge-unveil/unveil.txt new file mode 100644 index 0000000..f940ab3 --- /dev/null +++ b/openbsd-pledge-unveil/unveil.txt @@ -0,0 +1,81 @@ +UNVEIL(2) System Calls Manual UNVEIL(2) + +NNAAMMEE + uunnvveeiill - unveil parts of a restricted filesystem view + +SSYYNNOOPPSSIISS + ##iinncclluuddee <> + + _i_n_t + uunnvveeiill(_c_o_n_s_t _c_h_a_r _*_p_a_t_h, _c_o_n_s_t _c_h_a_r _*_p_e_r_m_i_s_s_i_o_n_s); + +DDEESSCCRRIIPPTTIIOONN + The first call to uunnvveeiill() that specifies a _p_a_t_h removes visibility of + the entire filesystem from all other filesystem-related system calls + (such as open(2), chmod(2) and rename(2)), except for the specified _p_a_t_h + and _p_e_r_m_i_s_s_i_o_n_s. + + The uunnvveeiill() system call remains capable of traversing to any _p_a_t_h in the + filesystem, so additional calls can set permissions at other points in + the filesystem hierarchy. + + After establishing a collection of _p_a_t_h and _p_e_r_m_i_s_s_i_o_n_s rules, future + calls to uunnvveeiill() can be disabled by passing two NULL arguments. + Alternatively, pledge(2) may be used to remove the "unveil" promise. + + The _p_e_r_m_i_s_s_i_o_n_s argument points to a string consisting of zero or more of + the following characters: + + rr Make _p_a_t_h available for read operations, corresponding to the + pledge(2) promise "rpath". + ww Make _p_a_t_h available for write operations, corresponding to + the pledge(2) promise "wpath". + xx Make _p_a_t_h available for execute operations, corresponding to + the pledge(2) promise "exec". + cc Allow _p_a_t_h to be created and removed, corresponding to the + pledge(2) promise "cpath". + + A _p_a_t_h that is a directory will enable all filesystem access underneath + _p_a_t_h using _p_e_r_m_i_s_s_i_o_n_s if and only if no more specific matching uunnvveeiill() + exists at a lower level. Directories are remembered at the time of a + call to uunnvveeiill(). This means that a directory that is removed and + recreated after a call to uunnvveeiill() will appear to not exist. + + Non-directory paths are remembered by name within their containing + directory, and so may be created, removed, or re-created after a call to + uunnvveeiill() and still appear to exist. + + Attempts to access paths not allowed by uunnvveeiill() will result in an error + of EACCES when the _p_e_r_m_i_s_s_i_o_n_s argument does not match the attempted + operation. ENOENT is returned for paths for which no uunnvveeiill() + permissions qualify. After a process has terminated, lastcomm(1) will + mark it with the `U' flag if file access was prevented by uunnvveeiill(). + + uunnvveeiill() use can be tricky because programs misbehave badly when their + files unexpectedly disappear. In many cases it is easier to unveil the + directories in which an application makes use of files. + +RREETTUURRNN VVAALLUUEESS + Upon successful completion, the value 0 is returned; otherwise the + value -1 is returned and the global variable _e_r_r_n_o is set to indicate the + error. + +EERRRROORRSS + [E2BIG] The addition of _p_a_t_h would exceed the per-process + limit for unveiled paths. + + [EFAULT] _p_a_t_h or _p_e_r_m_i_s_s_i_o_n_s points outside the process's + allocated address space. + + [ENOENT] A directory in _p_a_t_h did not exist. + + [EINVAL] An invalid value of _p_e_r_m_i_s_s_i_o_n_s was used. + + [EPERM] An attempt to increase permissions was made, or the + _p_a_t_h was not accessible, or uunnvveeiill() was called after + locking. + +HHIISSTTOORRYY + The uunnvveeiill() system call first appeared in OpenBSD 6.4. + +OpenBSD 7.6 September 6, 2021 OpenBSD 7.6