Re: chflags -RL and symlinks pointing outside the traversal: preserve behavior or add a Capsicum guardrail?

From: Mark Johnston <markj_at_freebsd.org>
Date: Wed, 19 Aug 2026 16:11:22 UTC
On Wed, Aug 19, 2026 at 09:40:35AM -0600, Alan Somers wrote:
> On Wed, Aug 19, 2026 at 8:37 AM Jitendra Bhati <jitendrabhati2026@gmail.com>
> wrote:
> 
> > Hi all,
> >
> > As part of GSoC 2026 I'm capsicumizing chflags(1), using the new
> > fts_openat(3) API:
> >
> > https://github.com/freebsd/freebsd-src/pull/2375
> >
> > This raises a backward-compatibility question that Alan Somers and I think
> > warrants wider input.
> >
> > In physical modes (-P, -h, and the default without -L) the traversal now
> > runs inside Capsicum capability mode: chflags pre-opens a directory
> > descriptor per argument, calls cap_enter(), and operates via:
> >
> > chflagsat(fts_parent->fts_dirfd, fts_name, ...);
> >
> > This confines chflags to the hierarchies named on the command line.
> >
> > The complication is -L (FTS_LOGICAL), where chflags follows symlinks. A
> > followed symlink can point outside the traversal root, e.g.:
> >
> > file
> > foo/bar/baz/link -> ../../../file
> >
> > Running:
> >
> > chflags -RL <flags> foo/bar
> >
> > today follows link and changes the flags on file, which lives outside
> > foo/bar. Capability mode would block that access, because file isn't
> > reachable from the descriptor opened for foo/bar.
> >
> > For now the PR preserves existing behavior: when -L is in effect it skips
> > cap_enter() and keeps the historical path-based call:
> >
> > chflagsat(AT_FDCWD, p->fts_accpath, ...);
> >
> > So -L is not capsicumized, but nothing regresses.
> >
> > The open question: is "following a symlink out of the named hierarchy"
> > behavior worth preserving, or is it exactly the kind of thing a
> > Capsicum-ized chflags should refuse? A tree that redirects chflags at
> > arbitrary files via a crafted symlink is arguably a threat we'd want to
> > guard against. But it's also a long-standing, documented -L semantic, and
> > changing it would be a POLA violation for anyone relying on it.
> >
> > Options:
> >
> > Keep -L as-is (path-based, not confined) - current PR behavior.
> > Capsicumize -L too, and treat out-of-hierarchy symlink targets as an error.
> > Capsicumize -L but keep out-of-hierarchy targets working by opening a
> > descriptor for the resolved target's parent (loses most of the confinement
> > benefit for -L).
> >
> > I lean toward (1) for this PR and revisiting -L separately, but I'd
> > appreciate opinions on whether the stricter behavior is worth the
> > compatibility break.
> >
> > Thanks,
> > Jitendra Bhati
> >
> 
> I vote for #2.  Capsicumize the utility, and treat out-of-hierarchy
> symlinks as an error.  I think that real users will almost never want to
> follow out of hierarchy symlinks.  Also, even if this is a POLA violation,
> it's extremely rare.  With grep, I couldn't find any example of chflags -RL
> in the base system.  Github code search turned up a few.  Some were MacOS
> Homebrew recipes that invoke chflags right after installing the package.
> Those uses surely would prefer not to follow out-of--hierarchy symlinks.
> The most interesting use I found was from quBSD, a jail/bhyve manager.  I
> don't know if that project really intends to follow out-of-hierarchy
> symlinks or not, but I've notified him.

If we go this route, I think there does need to be a chicken switch for
any users that are legitimately impacted and want an easy workaround.
I'm not sure what that would look like: an environment variable, sysctl,
new command-line option?  Should any such chicken switch be global,
i.e., should it apply to all capsicumized utilities which use fts?  I
presume you are planning to extend this to work with other fts-using
utilities which might benefit more from running in a sandbox, like grep.