Add OpenBSD/pledge/unveil

This commit is contained in:
Bruce Hill 2025-03-06 16:43:02 -05:00
parent 9e67dcebd6
commit 7174643ec4
8 changed files with 1357 additions and 0 deletions

View File

@ -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 <https://www.gnu.org/licenses/>. */
#include <config.h>
#include <stdio.h>
#include <sys/types.h>
#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;
}

View File

@ -0,0 +1,498 @@
/* $OpenBSD: doas.c,v 1.99 2024/02/15 18:57:58 tedu Exp $ */
/*
* Copyright (c) 2015 Ted Unangst <tedu@openbsd.org>
*
* 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 <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <limits.h>
#include <login_cap.h>
#include <bsd_auth.h>
#include <readpassphrase.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <err.h>
#include <unistd.h>
#include <pwd.h>
#include <grp.h>
#include <syslog.h>
#include <errno.h>
#include <fcntl.h>
#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);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 MiB

View File

@ -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)

View File

@ -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);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

View File

@ -0,0 +1,300 @@
PLEDGE(2) System Calls Manual PLEDGE(2)
NNAAMMEE
pplleeddggee - restrict system operations
SSYYNNOOPPSSIISS
##iinncclluuddee <<uunniissttdd..hh>>
_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

View File

@ -0,0 +1,81 @@
UNVEIL(2) System Calls Manual UNVEIL(2)
NNAAMMEE
uunnvveeiill - unveil parts of a restricted filesystem view
SSYYNNOOPPSSIISS
##iinncclluuddee <<uunniissttdd..hh>>
_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