24 Commits
Author SHA1 Message Date
Andrew Tridgell 4c142f671b vfs: adapt the merged-in base changes to the VFS layer
Linear-rebase counterpart of the conflict resolutions made when the
sec-fixes base was integrated (see the merge for reference):

- Port ddda7ba5's operator-path confinement of do_symlink_at and
  do_rmdir_at into the relocated VFS code.  vfs__symlink_secure gains
  the VFS_OPERATOR_PATH ownership-walk branch (parent confined via
  vfs_owner_walk_parent, shared leaf-creation preserved so fake-super
  emulation still applies); vfs__unlink_secure extends its existing
  operator branch to the rmdir/AT_REMOVEDIR case.  Callers pass the
  policy explicitly where the base set operator_path_resolve: the
  keep_backup symlink create (backup.c), the backup-tree rmdir in
  delete_item (delete.c, DEL_FOR_BACKUP), and handle_partial_dir's
  rmdir (util1.c).
- Port 1f8f89c2's robust_rename EXDEV-fallback confinement into
  vfs/robust.c: an absolute --temp-dir/--partial-dir operand routes the
  copy_file dest-write and the source-unlink through the ownership walk
  (VFS_OPERATOR_PATH), so a raced parent symlink can't redirect either
  out of the module.
- Drop the stale "no ownership-walk branch" notes in vfs/vfs.h and
  vfs/symlink.c now that symlink and rmdir carry the branch.

Tree is byte-identical to the validated merge result.
2026-08-13 10:20:13 +10:00
Andrew Tridgell 139c305814 delete: confine the backup-tree unlink via the operator ownership walk
A backup-tree delete (delete_item with DEL_FOR_BACKUP -- removing an existing
leaf under an absolute --backup-dir before the new backup is placed) fell through
del_unlink() to robust_unlink(fbuf, 0), which resolves the leaf's parent by path
with no ownership walk.  A local attacker who flips a backup-path parent to a
symlink in that window could redirect the unlink outside the backup tree.

The base confines this: make_backup() wraps make_backup_inner() in
operator_path_resolve, so the same unlink reaches do_unlink_at()'s owner-walk
branch.  The VFS refactor replaced the global with explicit vfs_flags but left
del_unlink() passing 0, dropping the confinement.  Thread VFS_OPERATOR_PATH into
del_unlink()'s path-based fallback when DEL_FOR_BACKUP is set (the exact cases
that ran under the base's operator wrap); a held-dirfd delete is already confined
and ignores it.  The rmdir path keeps no owner-walk branch, matching base.
2026-08-13 10:20:13 +10:00
Andrew Tridgell 1214c4a2f9 vfs: unify stat/lstat, chmod, lchown into dirfd+flags form
Collapse the remaining three-form ops into one call each, matching
mkdir/mknod/symlink/unlink:
  vfs_stat(int dirfd, path, st, flags)
  vfs_lstat(int dirfd, path, st, flags)
  vfs_chmod(int dirfd, path, mode, flags)
  vfs_lchown(int dirfd, path, owner, group, flags)
dirfd == VFS_AT_FDCWD resolves the path (VFS_ALLOW_SYMLINK = plain libc op,
default 0 = secure receiver resolve, VFS_OPERATOR_PATH = ownership walk for
stat); a real held dirfd operates on a single component under it.  The old
plain/_at/_atfd bodies become static helpers behind the dispatchers;
vfs_fstat stays (fd-based).  chmod/lchown keep their no-owner-walk behaviour
(VFS_OPERATOR_PATH resolves the same as the default secure walk).

Pure API-narrowing -- the operator policy was already explicit; no global is
involved.  This is behaviour-preserving (plain sites -> VFS_ALLOW_SYMLINK,
_at -> the flag they already carried, _atfd -> held-fd 0).

Held-fd validation differs by op: mkdir/mknod/symlink/unlink reject "."/".."
(creating/removing them is nonsensical), but stat/chmod/lchown ALLOW "."
(a read or metadata op on the dir itself is legitimate -- link_stat_at and
set_file_attrs do it for directory entries); only empty and multi-component
("/") names are rejected there.  Caught by the suite (chmod/metadata/
ownership-depth + the implied-"." dir mkdir path).

Full suite 190/50.
2026-08-13 10:20:13 +10:00
Andrew Tridgell e12eb04e12 vfs: unify the unlink/rmdir family + flag robust_unlink
Collapse vfs_unlink / vfs_unlink_at / vfs_unlink_atfd / vfs_rmdir /
vfs_rmdir_at into one vfs_unlink(int dirfd, const char *path, int flags).
VFS_REMOVEDIR selects rmdir/AT_REMOVEDIR; dirfd == VFS_AT_FDCWD resolves the
path, a real held dirfd removes a single validated component.  The secure
body reads the operator context from VFS_OPERATOR_PATH instead of the
global; the rmdir path keeps its pre-existing no-owner-walk behaviour (it
never read the global -- matched the old vfs_rmdir_at).

robust_unlink() gains a vfs_flags argument threaded to vfs_unlink, so the
operator policy reaches it explicitly: the backup-tree callers (make_backup
link_or_rename, generator in-place backup) and copy_file's dest unlink (when
copying a backup) pass VFS_OPERATOR_PATH; delete and robust_rename's
transfer-context calls pass 0.  Its ETXTBSY rename-retry still uses
vfs_rename_at (the rename primitive isn't flagged yet), which reads the
global -- still set by the surrounding backup blocks -- so behaviour is
unchanged.

Call sites classified: --remove-source-files fallback (sender) follows
symlinks (VFS_ALLOW_SYMLINK); partial-dir unlink/rmdir (handle_partial_dir)
and in-place-backup/partial unlinks operator; the rest secure or held-fd.
Full suite 190/50; delete/backup/partial/remove-source/daemon subset green.
2026-08-13 10:20:13 +10:00
Andrew Tridgell 07dd81b17b vfs: move the chmod family into vfs/chmod.c
Relocate do_chmod/do_chmod_at/do_chmod_atfd (and the leaf-safe
do_fchmodat_nofollow helper) out of syscall.c into vfs/chmod.c as the
vfs_* names, declared in vfs/vfs.h.  The HAVE_CHMOD / HAVE_LCHMOD /
HAVE_SETATTRLIST / SYS_fchmodat2 platform guards travel verbatim.
Function bodies unchanged; no behavior change.

(Portability-sensitive family; wants a fleettest with the rest of the
#ifdef-heavy moves.)
2026-08-13 10:20:12 +10:00
Andrew Tridgell 7f03474b2b vfs: move the unlink/rmdir family into vfs/unlink.c
Relocate do_unlink/do_unlink_at/do_unlink_atfd and do_rmdir/do_rmdir_at
out of syscall.c into vfs/unlink.c as the vfs_* names, declared in
vfs/vfs.h.  Function bodies unchanged; no behavior change.
2026-08-13 10:20:12 +10:00
Andrew Tridgell a5f52ee62e vfs: move the held-dirfd cache into vfs/dircache.c
Relocate the persistent ancestor-dirfd cache out of syscall.c into
vfs/dircache.c with the vfs_* public names:

  open_dir_secure    -> vfs_opendir
  get_dir_fd         -> vfs_get_dirfd
  held_dir_path_fd   -> vfs_path_dirfd
  held_dfd_for       -> vfs_cached_dirfd
  reset_dir_fd_cache -> vfs_dircache_reset

The dpc_* cache statics and dpc_dir_fd stay file-local (they fold into
the vfs struct in a later commit).  Function bodies are unchanged; the
callers in generator/receiver/sender/delete/util1/rsync are updated and
the five entry points declared in vfs/vfs.h.  This completes moving the
security core (resolver, owner-walk, dirfd cache) out of syscall.c.
No behavior change.
2026-08-13 10:20:12 +10:00
Andrew Tridgell 54965efca9 syscall/util1: race-safe path resolution via a held dirfd-stack resolver
The core symlink-race (TOCTOU) defense for the CVE-2026-29518 class: a portable
secure resolver that walks a path one component at a time holding an O_NOFOLLOW
dir fd per level (the dirfd-stack), plus the do_*_at() filesystem wrappers, the
held-directory fd cache, secure_relative_open[_at](), the operator-path ownership
walk (owner_walk_parent / open_no_attacker_symlinks, follow a uid0/euid symlink,
refuse a foreign one) and its module-root confinement (abspath_excluded_by_module).
util1.c routes change_dir / robust_rename / make_path / handle_partial_dir through
it; the resolver bounds its deep-path fd use against RLIMIT_NOFILE.  Also drops the
obsolete android.c openat2 path and confines delete.c via the held dirfd.
2026-07-20 14:05:30 +10:00
Andrew TridgellandClaude Opus 4.7 30656c5e35 syscall: add symlink-race-safe do_*_at() wrappers and harden secure_relative_open
Add the rest of the path-based syscall wrappers and migrate every
receiver-side caller:
  - do_lchown_at, do_rename_at, do_mkdir_at, do_symlink_at,
    do_mknod_at, do_link_at, do_unlink_at, do_rmdir_at,
    do_utimensat_at, do_stat_at, do_lstat_at

Same shape as do_chmod_at: open each parent under
secure_relative_open(), call the *at() variant against the dirfd,
fall through to the bare path-based syscall in non-daemon /
chrooted / absolute-path / no-parent cases. macOS's
setattrlist-based set_times tier is also routed through the
utimensat_at path on daemon-no-chroot.

Hardenings to secure_relative_open() itself:
  - confine basedir resolution under the same kernel mechanism
    used for relpath (basedirs from --copy-dest / --link-dest are
    sender-controllable in daemon mode)
  - reject any '..' component (bare '..', 'foo/..', 'subdir/..')
    so the per-component O_NOFOLLOW fallback can't escape
  - return the dirfd we built up from the per-component fallback
    when the caller passed O_DIRECTORY (otherwise every do_*_at
    failed with EINVAL on platforms without RESOLVE_BENEATH)

Adds testsuite/alt-dest-symlink-race.test and
testsuite/secure-relpath-validation.test (with t_secure_relpath
helper) as regression coverage for the new hardenings.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 10:01:22 +10:00
Andrew TridgellandClaude Opus 4.7 862fe4eeaf syscall+receiver: secure receiver-side do_chmod against symlink-race TOCTOU
CVE-2026-29518's fix routed the receiver's open() through
secure_relative_open(), but every other path-based syscall the
receiver runs on sender-controllable paths is vulnerable to the
same TOCTOU primitive. This commit closes the chmod variant.

Add do_chmod_at() that opens the parent of fname under
secure_relative_open() and uses fchmodat() against the resulting
dirfd. Gate the secure path on am_daemon && !am_chrooted (the same
gate use_secure_symlinks already uses for the receiver basis-file
open), so non-daemon callers and chrooted daemons keep the original
do_chmod() fast path.

Migrate the receiver-side do_chmod() call sites in delete.c,
generator.c, rsync.c, and xattrs.c.

Adds testsuite/chmod-symlink-race.test (with t_chmod_secure helper)
as regression coverage.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 10:01:22 +10:00
Wayne Davison 6c8ca91c73 Preparing for release of 3.3.0 [buildall] 2024-04-06 09:30:21 -07:00
Wayne Davison 079e74a30f Some year updates. 2024-04-06 09:22:29 -07:00
Wayne Davison 6f3c5eccee Fix old stats bug that counted devices as symlinks. 2023-05-16 22:44:54 -07:00
Wayne Davison e63ff70eae Some indentation fixes. 2020-06-13 19:15:02 -07:00
Wayne Davison 3e2e4b5a33 Tweak the copyright year. 2019-03-16 09:15:49 -07:00
Wayne Davison 473108ae6e Tweak copyright date. 2018-01-14 19:55:07 -08:00
Wayne Davison 453914e35b Update the copyright year. 2015-08-08 12:47:03 -07:00
Wayne Davison dfa5b49110 Bump the year to 2014. 2014-01-26 09:29:15 -08:00
Wayne Davison 83792c1cbf A delete_item() error should use FERROR_XFER.
Fixes bug 10024.
2013-12-01 15:58:17 -08:00
Wayne Davison 7e1a9c4d79 Update copyright year. 2013-01-19 11:05:53 -08:00
Matt McCutchen 57edc4808f Avoid changing file_extra_cnt during deletion.
The I/O code can receive incremental file-list chunks during deletion,
and their OPT_EXTRA fields would get corrupted when file_extra_cnt is
incremented.

Instead of temporarily enabling uid_ndx to find out whether the user
owns a file, have make_file() set a flag for that purpose.

Applied with a few minor tweaks by Wayne.  Fixes bug 7936.
2011-01-29 22:05:16 -08:00
Wayne Davison 7e6c8ad653 Don't try to backup a file being removed from the backup area. 2009-04-10 23:14:06 -07:00
Wayne Davison 21cddef2b4 Improved the backup code:
- Backups do not interfere with an atomic update (when possible).
- Backing up a file will remove a directory that is in the way
  and visa versa.
- Unify the backup-dir and non-backup-dir code in backup.c.
- Improved the backup tests a little bit.
2009-01-03 12:02:47 -08:00
Wayne Davison 974e18191c Make delete_item() public, moving it into delete.c. 2009-01-03 10:52:50 -08:00