From: Christian Schulte Subject: Re: Locking down openat(2) and friends with O_BELOW and F_BELOW To: tech@openbsd.org Date: Mon, 5 Oct 2026 00:44:55 +0200 Am 04.10.2026 um 21:30 schrieb Theo de Raadt: ... > > Index: lib/libc/sys/fcntl.2 > =================================================================== > RCS file: /cvs/src/lib/libc/sys/fcntl.2,v > diff -u -r1.38 fcntl.2 > --- lib/libc/sys/fcntl.2 4 Aug 2025 15:08:16 -0000 1.38 > +++ lib/libc/sys/fcntl.2 16 Sep 2026 17:00:45 -0000 > @@ -61,6 +61,14 @@ > .Pp > The commands are: > .Bl -tag -width F_DUPFD_CLOEXEC > +.It Dv F_BELOW > +Locks a directory file descriptor > +.Ar fd > +so that > +.Xr openat 2 > +and related functions cannot perform relative accesses which are above > +the directory itself. > +Absolute path accesses are also blocked on this mode. > .It Dv F_DUPFD > Return a new descriptor as follows: > .Pp > Index: lib/libc/sys/open.2 > =================================================================== > RCS file: /cvs/src/lib/libc/sys/open.2,v > diff -u -r1.63 open.2 > --- lib/libc/sys/open.2 25 May 2026 13:58:58 -0000 1.63 > +++ lib/libc/sys/open.2 16 Sep 2026 17:00:45 -0000 > @@ -88,6 +88,15 @@ > Do not block on open or for data to become available. > .It Dv O_APPEND > Append on each write. > +.It Dv O_BELOW > +Used alongside > +.Dv O_DIRECTORY , > +sets the > +.Dv F_BELOW > +flag which constrains relative accesses with > +.Xr openat 2 . > +Described further in > +.Xr fcntl 2 . > .It Dv O_CREAT > Create file if it does not exist. > An additional argument of type > Index: sys/kern/kern_descrip.c > =================================================================== > RCS file: /cvs/src/sys/kern/kern_descrip.c,v > diff -u -r1.213 kern_descrip.c > --- sys/kern/kern_descrip.c 8 Mar 2026 16:41:21 -0000 1.213 > +++ sys/kern/kern_descrip.c 19 Sep 2026 04:15:14 -0000 > @@ -424,6 +424,14 @@ > return (EBADF); > switch (SCARG(uap, cmd)) { > > + case F_BELOW: > + vp = fp->f_data; > + if (fp->f_type == DTYPE_VNODE && vp->v_type == VDIR) > + fp->f_flag |= O_BELOW; > + else > + return ENOTTY; I am not sure about using ENOTTY here. The changes to the manuals do not say much about it. The ERROR section of fcntl(2) does not mention ENOTTY. But that may just be me stumbling upon an identifier during a quick read expecting EINVAL. > + break; > + > case F_DUPFD: > case F_DUPFD_CLOEXEC: > case F_DUPFD_CLOFORK: > Index: bin/csh/exec.c > =================================================================== > RCS file: /cvs/src/bin/csh/exec.c,v > diff -u -r1.22 exec.c > --- bin/csh/exec.c 8 Mar 2023 04:43:04 -0000 1.22 > +++ bin/csh/exec.c 18 Sep 2026 20:35:42 -0000 > @@ -439,6 +439,7 @@ > dirp = opendir(short2str(*pv)); > if (dirp == NULL) > continue; > + fcntl(dirfd(dirp), F_BELOW); You sometimes just add that line and sometimes guard it with #ifdef F_BELOW #endif > while ((dp = readdir(dirp)) != NULL) { > if (dp->d_ino == 0) > continue; > Index: usr.bin/find/function.c > =================================================================== > RCS file: /cvs/src/usr.bin/find/function.c,v > diff -u -r1.57 function.c > --- usr.bin/find/function.c 25 May 2026 04:40:36 -0000 1.57 > +++ usr.bin/find/function.c 16 Sep 2026 17:00:45 -0000 > @@ -370,6 +370,9 @@ > > empty = 1; > dir = opendir(entry->fts_accpath); > +#ifdef F_BELOW > + fcntl(dirfd(dir), F_BELOW); > +#endif Like here. From just reading the diff it is not abvious why the guards are used sometimes and sometimes not. That does not mean there is anything wrong about it. Just does not read intuitively. > if (dir == NULL) > return (0); > for (dp = readdir(dir); dp; dp = readdir(dir)) > Index: usr.bin/ftp/complete.c > =================================================================== > RCS file: /cvs/src/usr.bin/ftp/complete.c,v > diff -u -r1.33 complete.c > --- usr.bin/ftp/complete.c 16 May 2019 12:44:17 -0000 1.33 > +++ usr.bin/ftp/complete.c 16 Sep 2026 17:00:45 -0000 > @@ -173,6 +173,7 @@ > > if ((dd = opendir(dir)) == NULL) > return (CC_ERROR); > + fcntl(dirfd(dd), F_BELOW); > > words = sl_init(); > ...