Commit Graph
2 Commits
Author SHA1 Message Date
Andrew Tridgell 8fc908ebaf lib/acl: close the pre-6.13 ACL parent-symlink race via /proc/self/fd
On a kernel without the Linux 6.13 *xattrat syscalls, xacl_*_at() returned ENOSYS
so acls.c fell back to the unconfined path-based sys_acl_set_file() -- the
long-standing parent-symlink race for --acls on every pre-6.13 Linux (the
receiver applies a received ACL to a leaf an attacker raced to a symlink ->
arbitrary-ACL LPE; acl-symlink-race only documented it as a residual there).

Add a /proc/self/fd compat to the dirfd dispatchers: when the *xattrat syscall is
absent (runtime ENOSYS or unbuilt), address the leaf as
/proc/self/fd/<dirfd>/<leaf> and use the l*xattr (no-follow-leaf) calls.  The
/proc/self/fd/<dirfd> magic symlink resolves to the pinned parent inode (a raced
parent symlink can't redirect it) and l*xattr does not follow a raced leaf
symlink, so the dirfd+leaf ACL ops are race-safe on every Linux kernel with
procfs -- no libacl dependency and no 6.13 requirement.  xacl_at_available() now
returns true when the syscalls OR the /proc compat are usable, so acls.c takes
the race-safe dirfd path instead of the path-based fallback (now only the BSDs /
a /proc-less namespace).  The held-fd path (fsetxattr) is unchanged.

Verified on ubuntu-2004 (kernel 5.4, HAVE_XATTRAT_SYSCALLS=0): "ACL_at": true and
acl-symlink-race PASSES while asserting (a vacuous pass before); no regression on
a 6.13+ host (native syscalls unchanged) or t_acl.
2026-06-15 15:24:42 +10:00
Andrew Tridgell d7f929a30e lib/acl.c: POSIX ACL get/set via fd/at xattr syscalls + unit test
POSIX ACLs are stored by the kernel as the system.posix_acl_{access,default}
xattrs.  lib/acl.c serializes that wire format and operates on it via
fgetxattr/fsetxattr on a held O_NOFOLLOW fd -- or getxattrat/setxattrat
(AT_SYMLINK_NOFOLLOW) on a dirfd+leaf -- giving a symlink-race-safe ACL
primitive that, unlike libacl's access-only acl_get_fd/acl_set_fd, also covers
the default ACL.

It is self-contained (no libacl, no rsync globals): it speaks a neutral
(tag, perm, id) entry array, so t_acl can compare it directly against the system
libacl as an oracle.  configure gains SUPPORT_ACL_FD (POSIX ACLs + the xattr
header, independent of the -X feature) and an optional HAVE_XATTRAT_SYSCALLS
probe (the *xattrat syscalls, Linux 6.13+); the fd path needs neither.

t_acl exercises every op rsync needs in both directions (set via lib -> read via
libacl and vice versa), round-trips, the default-ACL delete, errno
discrimination, and the NOFOLLOW leaf refusal.  It self-skips (77) without
SUPPORT_ACL_FD or on a filesystem lacking ACL support.
2026-06-15 15:24:42 +10:00