From: Crystal Kolipe Subject: Re: Locking down openat(2) and friends with O_BELOW and F_BELOW To: Theo de Raadt Cc: tech@openbsd.org Date: Mon, 05 Oct 2026 17:05:50 -0000 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 #include #include #include #include #include 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); }