Skip to content

GHSA-phxh-hjqv-39c9

CVE Information

When preserving metadata (-A/--acls, -X/--xattrs, or fake-super ACL-as-xattr), the receiver applied the received ACL/xattrs to each entry by path, via acl_set_file()/setxattr() (and the libacl walk re-resolves every component). A local user who controls a path component races the entry an unprivileged user just received -- a regular file, dir or FIFO -- into a symlink->outside AFTER the receiver created it but BEFORE the metadata apply, so the ACL set follows the flipped leaf (or a flipped parent) and writes an attacker-chosen ACL onto a victim inode OUTSIDE the destination tree. The attacker controls the ACL bytes (they are carried in the source entry), so this grants the attacker rwx on a root-owned file -- a local privilege escalation, not just a write. A raced FIFO could also block the apply (DoS).

Fix: hold an O_RDONLY|O_NOFOLLOW (O_NONBLOCK) fd pinning each entry's inode and apply all metadata to the pinned inode rather than re-resolving the path: xattrs via the f-variant calls on the held fd; ACLs via lib/acl.c's fd path (xacl_*_fd) -- or setxattrat(AT_SYMLINK_NOFOLLOW) on the dirfd+leaf for a socket/device -- covering both the access and default ACL; fake-super ACL-as-xattr writes/deletes likewise go through the held fd. On Linux 6.13+ this uses the *xattrat syscalls (preferring a patched libacl's *_at bindings when present); on older Linux the /proc/self/fd compat path pins the same inode. The packed ACL bytes are unchanged, so the result matches the old acl_set_file() exactly.

Tests: acl-symlink-race (RED on the unfixed path-based apply, GREEN pinned), acls-unpinnable (ACL still applies correctly on the path-based fallback).

Additional surfaces folded in (same class -- a confined receiver's xattr/ACL/metadata write resolved by pathname): the receiver's own xattr/ACL copy-out (the copy-xattrs case), and cross-tree operator-path metadata writes on --backup-dir / alt-dest / fake-super targets, likewise resolved by path, so a raced parent-component symlink could redirect the write outside the confined tree (a fake-super residual of this same issue). Fix: never path-resolve a confined receiver's xattr/ACL/metadata write -- route it through a held/pinned O_NOFOLLOW fd like the metadata apply above. Added test: copy-xattrs-symlink-race.

Note: the path-based fallback (and thus the unavoidable parent-symlink race) is reached only where neither primitive is available -- the BSDs, Solaris and macOS (no *xattrat syscalls and no /proc/self/fd magic symlinks), plus a Linux instance with no usable /proc. There the apply keeps the long-standing path-based calls to keep --acls functional rather than silently skipping it -- an accepted, documented residual (SECURITY.md), refusable via refuse options = acls.

Affected: rsync 3.4.3 and earlier; fixed in 3.5.0. (Precise introduced-in ranges being finalised.)