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

From: Alan Somers <asomers_at_freebsd.org>
Date: Wed, 19 Aug 2026 15:40:35 UTC
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.

https://github.com/BawdyAnarchist/quBSD/blob/88621ea7693514e4342c81478cd1b883d22b04a5/usr/local/libexec/qubsd/qb-ivpn#L222


> ᐧ
>