Index | Thread | Search

From:
"Theo de Raadt" <deraadt@openbsd.org>
Subject:
Re: openat(2) is mostly useless, sadly
Cc:
tech@cvs.openbsd.org
Date:
Fri, 30 May 2025 09:51:39 -0600

Download raw body.

Thread
Theo de Raadt <deraadt@openbsd.org> wrote:

> The family of system calls related to openat(2) are mostly useless in
> practice, rarely used. When they are used it is often ineffectively or
> even with performance-reducing results.

Below, find part of the code for a regression test.  It builds a tree of
files, directories, and symlinks which looks like this, and then it compares
then access patterns between normal use, O_BELOW, F_BELOW (which should be
the same), and finally also unveil() which is subtly different.

./:
-rw-------  1 root  wobj  18 May 30 09:43 upper

testdir:
drwx------  3 root   wobj  512 May 30 09:46 ./
drwxrwx---  3 build  wobj  512 May 30 07:58 ../
lrwxr-xr-x  1 root   wobj   15 May 30 07:41 crazy-local-symlink@ -> subdir/../local
lrwxr-xr-x  1 root   wobj   26 May 30 07:41 crazy-local-symlink2@ -> subdir/../../testdir/local
-rw-------  1 root   wobj   18 May 30 09:43 local
drwx------  2 root   wobj  512 May 30 07:28 subdir/
lrwxr-xr-x  1 root   wobj    2 May 30 07:41 symlink-to-..@ -> ..
lrwxr-xr-x  1 root   wobj    5 May 30 07:41 symlink-to-home@ -> /home
lrwxr-xr-x  1 root   wobj    5 May 30 07:41 symlink-to-local@ -> local
lrwxr-xr-x  1 root   wobj    1 May 30 07:41 symlink-to-root@ -> /
lrwxr-xr-x  1 root   wobj    8 May 30 07:41 symlink-to-upper@ -> ../upper
lrwxr-xr-x  1 root   wobj   16 May 30 07:41 symlink2-to-local@ -> symlink-to-local
lrwxr-xr-x  1 root   wobj   15 May 30 07:41 symlink2-to-root@ -> symlink-to-root
lrwxr-xr-x  1 root   wobj   16 May 30 07:41 symlink2-to-upper@ -> symlink-to-upper

testdir/subdir:
total 8
drwx------  2 root  wobj  512 May 30 07:28 ./
drwx------  3 root  wobj  512 May 30 09:46 ../

without F_BELOW or O_BELOW
	0	local
	1	..
	0	.
	1	/
	1	/home
	1	/etc/services
	1	../upper
	0	symlink-to-local
	0	symlink2-to-local
	1	symlink-to-..
	1	symlink-to-root
	1	symlink2-to-root
	1	symlink-to-upper
	1	symlink2-to-upper
	1	symlink-to-home
	0	crazy-local-symlink
	1	crazy-local-symlink2
with fcntl F_BELOW
	0	local
	1	..: No such file or directory
	0	.
	1	/: No such file or directory
	1	/home: No such file or directory
	1	/etc/services: No such file or directory
	1	../upper: No such file or directory
	0	symlink-to-local
	0	symlink2-to-local
	1	symlink-to-..: No such file or directory
	1	symlink-to-root: No such file or directory
	1	symlink2-to-root: No such file or directory
	1	symlink-to-upper: No such file or directory
	1	symlink2-to-upper: No such file or directory
	1	symlink-to-home: No such file or directory
	0	crazy-local-symlink
	1	crazy-local-symlink2: No such file or directory
with open O_BELOW
	0	local
	1	..: No such file or directory
	0	.
	1	/: No such file or directory
	1	/home: No such file or directory
	1	/etc/services: No such file or directory
	1	../upper: No such file or directory
	0	symlink-to-local
	0	symlink2-to-local
	1	symlink-to-..: No such file or directory
	1	symlink-to-root: No such file or directory
	1	symlink2-to-root: No such file or directory
	1	symlink-to-upper: No such file or directory
	1	symlink2-to-upper: No such file or directory
	1	symlink-to-home: No such file or directory
	0	crazy-local-symlink
	1	crazy-local-symlink2: No such file or directory
with unveil
	0	local
	1	..: No such file or directory
	0	.
	1	/: No such file or directory
	1	/home: No such file or directory
	1	/etc/services: No such file or directory
	1	../upper: No such file or directory
	0	symlink-to-local
	0	symlink2-to-local
	1	symlink-to-..: No such file or directory
	1	symlink-to-root: No such file or directory
	1	symlink2-to-root: No such file or directory
	1	symlink-to-upper: No such file or directory
	1	symlink2-to-upper: No such file or directory
	1	symlink-to-home: No such file or directory
	0	crazy-local-symlink
	1	crazy-local-symlink2
		WORKED, BUT SHOULD HAVE FAILED

I may have missed some cases which need to be added.

This shows that with O_BELOW we are unable to look above '.', even using
symlinks.  We also cannot traverse above, to get below.  In particular,
symlink traversals are contained.  That is a strong part of this concept.
Many programs contain code to be careful with symbolic links escaping their
intended place, and this is very tricky.  O_BELOW will be kernel-assist
containment of that policy.  (I'm suggesting that noone should delete their
application specific containment code...).

With unveil(), we can traverse above the spot, to get below.  That's
because of how unveil works.  unveil can do paths starting with '/', or
all sorts of '..'  or symlink traversals, as long as in-the-end it lands
on an unveil-remembered-file-vnode or below an unveil-remembered-directory-vnode.

#include <sys/types.h>
#include <sys/types.h>
#include <sys/fcntl.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>

void	setup(void);
int	test(char *label, int dirfd, int test);

struct paths {
	char *path;
	int block;
};

struct paths paths[] = {
	{ "local", 0 },
	{ "..", 1 },
	{ ".", 0 },
	{ "/", 1 },
	{ "/home", 1 },
	{ "/etc/services", 1 },
	{ "../upper", 1 },
	{ "symlink-to-local", 0 },
	{ "symlink2-to-local", 0 },	// symlink to symlink to inside
	{ "symlink-to-..", 1 },
	{ "symlink-to-root", 1 },
	{ "symlink2-to-root", 1 },	// symlink to symlink to root
	{ "symlink-to-upper", 1 },
	{ "symlink2-to-upper", 1 },	// symlink to symlink to upper
	{ "symlink-to-home", 1 },
	{ "crazy-local-symlink", 0 },
	{ "crazy-local-symlink2", 1 },
};

/*
 * Create a heirarchy we will test against
 */
void
setup(void)
{
	int fd;

	fd = open("upper", O_CREAT|O_RDWR, 0600);
	if (fd > 0) {
		dprintf(fd, "contents of upper\n");
		close(fd);
	}
	mkdir("testdir", 0700);
	mkdir("testdir/subdir", 0700);
	chdir("testdir");

	fd = open("local", O_CREAT|O_RDWR, 0600);
	if (fd > 0) {
		dprintf(fd, "contents of local\n");
		close(fd);
	}
	symlink("local", "symlink-to-local");
	symlink("symlink-to-local", "symlink2-to-local");
	symlink("..", "symlink-to-..");
	symlink("/", "symlink-to-root");
	symlink("symlink-to-root", "symlink2-to-root");
	symlink("../upper", "symlink-to-upper");
	symlink("symlink-to-upper", "symlink2-to-upper");
	symlink("/home", "symlink-to-home");
	symlink("subdir/../local", "crazy-local-symlink");
	symlink("subdir/../../testdir/local", "crazy-local-symlink2");
}

int
test(char *label, int dirfd, int test)
{
	unsigned int i;
	int fd, err = 0;

	printf("%s\n", label);
	for (i = 0; i < sizeof (paths)/sizeof(paths[0]); i++) {
		fd = openat(dirfd, paths[i].path, O_RDONLY);
		if (fd == -1) {
			printf("\t%d\t%s: %s\n", paths[i].block, paths[i].path,
			    strerror(errno));
			if (test && paths[i].block == 0) {
				printf("\t\tFAILED, BUT SHOULD HAVE WORKED\n");
				err = 1;
			}
		} else {
			printf("\t%d\t%s\n", paths[i].block, paths[i].path);
			close(fd);
			if (test && paths[i].block == 1) {
				printf("\t\tWORKED, BUT SHOULD HAVE FAILED\n");
				err = 1;
			}
		}
	}
	if (err)
		printf("AT LEAST ONE ERROR\n");
	close(dirfd);
	return err;
}

int
main(int argc, char *argv[])
{
	int fd, err = 0;

	setup();

	fd = open(".", O_DIRECTORY|O_RDONLY);
	err += test("without F_BELOW or O_BELOW", fd, 0);
	close(fd);

	fd = open(".", O_DIRECTORY|O_RDONLY);
	fcntl(fd, F_BELOW, 0);
	err += test("with fcntl F_BELOW", fd, 1);
	close(fd);

	fd = open(".", O_DIRECTORY|O_RDONLY|O_BELOW);
	err += test("with open O_BELOW", fd, 1);
	close(fd);

	unveil(".", "rw");
	unveil(NULL, NULL);

	fd = open(".", O_DIRECTORY|O_RDONLY);
	err += test("with unveil", fd, 2);
	close(fd);

	// unveil() has corrupted the test environment, do not add
	// new tests after this

	if (err)
		exit(1);


}