Skip to content

syscall: Munge symlinks in do_symlink_at() when munge_symlinks is enabled - #1152

Open
seks99x wants to merge 1 commit into
RsyncProject:masterfrom
seks99x:fix-backup-symlink-munge
Open

seks99x wants to merge 1 commit into
RsyncProject:masterfrom
seks99x:fix-backup-symlink-munge

Conversation

@seks99x

@seks99x seks99x commented Nov 8, 2026 •

Copy link
Copy Markdown
Member

When make_backup_inner() in backup.c backs up a symlink, it constructs a file_struct via make_file() and attempts link_or_rename(). If link_or_rename() fails (such as during a parent-directory swap race) make_backup_inner() falls back to recreating the symlink on disk via do_symlink_at(). Because F_SYMLINK(file) holds the unmunged target and do_symlink_at() did not perform munging, its created with the daemons running UID. root-owned/trsuted symlink could occur on the disk/daemon-module which could break the trusted symlink idea.

Fix: Centralize Munging in do_symlink_at()
Rather than scattering SYMLINK_PREFIX checks across individual callers like make_backup_inner(), we need to centralize symlink munging directly at the syscall wrapper boundary in do_symlink_at():

  • Whenever munge_symlinks is active ( which is active by default in non-chroot daemon modules ), do_symlink_at() check whether the target lnk already begins with SYMLINK_PREFIX (/rsyncd-munged/).
  • If lnk is not already prefixed, the wrapper prepends SYMLINK_PREFIX before invoking symlinkat()(returning ENAMETOOLONG if the combined length exceeds MAXPATHLEN).
  • Links that were already munged by recv_file_entry() are not double-munged, while any internal code path that creates a symlink from an in-memory file_struct (such as make_backup_inner()) is guaranteed to never write an unmunged symlink to disk.

Tests

  • Added testsuite/backup-munge-symlinks_test.py to verify that backup symlinks created via the make_backup_inner() fallback under a parent-swap race are properly prefixed with /rsyncd-munged/.
  • Updated testsuite/filter-leak_test.py and testsuite/files-from-leak_test.py so their setup preconditions treat munged root-owned backup symlinks as mitigated rather than a test setup failure.

When make_backup_inner() falls back to recreating a symlink via
do_symlink_at() (such as during a parent-swap race when link_or_rename()
fails), it passed the unmunged link target read by make_file() directly
to symlinkat(). In a non-chrooted daemon with munge_symlinks enabled,
this created an unmunged root-owned backup symlink on disk.

Ensure do_symlink_at() prepend SYMLINK_PREFIX whenever
munge_symlinks is active and the target is not already prefixed, and add
a regression test.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant