Index | Thread | Search

From:
Crystal Kolipe <kolipe.c@exoticsilicon.com>
Subject:
Re: Locking down openat(2) and friends with O_BELOW and F_BELOW
To:
Theo de Raadt <deraadt@openbsd.org>
Cc:
tech@openbsd.org
Date:
Mon, 05 Oct 2026 17:05:50 -0000

Download raw body.

Thread
On Mon, Oct 05, 2026 at 07:54:02AM -0600, Theo de Raadt wrote:
> Updated version of the diff.

I tested this, (first and second versions of the patch), with a few local
programs that use openat()...

Although it mostly works as expected, (and I like this feature), by passing a
contrived path containing './../.', it seems to be possible to cause openat()
to open a similarly named file in the wrong directory.

I've only had time to test on a machine with a lot of other local kernel
patches, so it's plausible that something local has introduced this bug, but I
won't get chance to test on stock -current until at least tomorrow so I
thought I'd just share what I've got now.

Reproducer below, but basically:

1. Open a directory fd, ('fdd'), using regular open().
2. Open a second directory fd, ('fdds'), using openat() on 'fdd'.
3. Open a file using that second directory fd, ('fdds').

Note: It seems that by design the second directory fd does not 'inherit' the
O_BELOW flag automatically, but this is _not_ the issue at hand here.

When step 2 is passed the path ./../. the behaviour changes depending on
whether O_BELOW is set or not on the first directory fd.

Obviously './../.' should be equivalent to '../.', and by extension,
equivalent to '..'.

What seems to happen is that with O_BELOW set on the first directory fd,
('fdd'), is that the current directory is successfully opened instead of the
directory above, ('..'), and no error is reported.


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

int open_contrived_path(int fdd)
{
char * buf;
int fdds;
int fd;

buf = malloc(18);

/*
 * Open secondary directory "./../.", which should be the 'upper' level
 * directory if it opens at all, (or the open should fail).
 */

if ((fdds = openat(fdd, "./../.", O_RDONLY)) == -1) {
	printf ("Error - Openat() to secondary dir using ./../. returned: %d\n", errno);
	return (1);
	}

if ((fd = openat(fdds, "file_a", O_RDONLY)) == -1) {
	printf ("Error - Openat() to file_a returned: %d\n", errno);
	return (1);
	}

if (read(fd, buf, 17) != 17) {
	printf ("Error - read() returned: %d\n", errno);
	return (1);
	}

*(buf + 17) = 0;
printf ("Read from ./.././file_a:\n%s\n", buf);
free (buf);
return (0);
}

int init()
{
/*
 * Create test hierarchy:
 *
 * file_a
 * dir_a/file_a
 */
char * buf;
int fd;

buf = malloc(18);

mkdir ("dir_a", 0755);
unlink ("file_a");
unlink ("dir_a/file_a");

fd = open ("file_a", O_WRONLY | O_CREAT, 0644);
write (fd, "upper level file\n", 17);
close (fd);

fd = open ("dir_a/file_a", O_WRONLY | O_CREAT, 0644);
write (fd, "lower level file\n", 17);
close (fd);
return (0);
}

int main(void)
{
int fdd;
int fdds;
int fd;
int res;
struct stat st;

init();

/*
 * Open directory dir_a/ for future use with openat():
 */

if ((fdd = open("dir_a", O_RDONLY | O_DIRECTORY)) == -1) {
	printf ("Error - Open directory dir_a/: %d\n", errno);
	return (1);
	}

open_contrived_path(fdd);

/*
 * Now set O_BELOW on the directory fd, and repeat the above...
 */

if (fcntl (fdd, F_BELOW) == -1) {
	printf ("Error - fcntl() returned: %d\n", errno);
	return (1);
	}

open_contrived_path(fdd);

return (0);
}