Download raw body.
Locking down openat(2) and friends with O_BELOW and F_BELOW
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);
}
Locking down openat(2) and friends with O_BELOW and F_BELOW