mirror of
https://github.com/RsyncProject/rsync.git
synced 2026-11-05 11:33:23 -05:00
syscall: broaden the symlink-race resolver to every non-chroot receiver
secure_relpath_active() now hardens path resolution for ALL non-chrooted
receivers/generators, not just the non-chrooted daemon module:
!am_chrooted && (am_daemon || !am_sender) (+ daemon inner-module clause)
so a root-run or cross-user local / remote-shell transfer resolves
destination paths under the held-dirfd resolver, closing the parent-
component symlink-race / confused-deputy on the receiver side. A chroot
is its own confinement (excluded); the sender is excluded so it still
follows -L/--copy-links symlinks out of the tree.
Also confine do_rename_at()/do_link_at() each side independently: an
absolute (operator-supplied) source must not disable confinement of a
relative destination, and an absolute operator-trusted path (e.g. an
absolutized --link-dest basis) uses AT_FDCWD with the full path.
Turns green: nondaemon-symlink-race, relative-mkpath-symlink,
relative-mkpath-dir-symlink, rename-mixed-parent-transfer,
symlink-dest-backupdir. No regression (itemize/xattrs-hlink stay green
via the absolutized alt-dest basis).
(cherry picked from commit 29ddd45e3d)
This commit is contained in:
1 parent
7dd9301b8d
commit
2be62f6010
1 file changed
+40
-24
@@ -43,6 +43,8 @@
|
||||
extern int dry_run;
|
||||
extern int am_root;
|
||||
extern int am_sender;
|
||||
extern int am_daemon;
|
||||
extern int am_chrooted;
|
||||
extern int read_only;
|
||||
extern int list_only;
|
||||
extern int inplace;
|
||||
@@ -53,6 +55,14 @@ extern int open_noatime;
|
||||
extern int copy_links;
|
||||
extern int copy_unsafe_links;
|
||||
|
||||
int secure_relpath_active(void)
|
||||
{
|
||||
extern unsigned int module_dirlen;
|
||||
if (am_daemon && am_chrooted && module_dirlen)
|
||||
return 1;
|
||||
return !am_chrooted && (am_daemon || !am_sender);
|
||||
}
|
||||
|
||||
|
||||
#ifndef S_BLKSIZE
|
||||
# if defined hpux || defined __hpux__ || defined __hpux
|
||||
@@ -118,7 +128,7 @@ int do_unlink_at(const char *path)
|
||||
if (dry_run) return 0;
|
||||
RETURN_ERROR_IF_RO_OR_LO;
|
||||
|
||||
if (!am_daemon || am_chrooted)
|
||||
if (!secure_relpath_active())
|
||||
return unlink(path);
|
||||
|
||||
if (!path || !*path || *path == '/')
|
||||
@@ -208,7 +218,7 @@ int do_symlink_at(const char *lnk, const char *path)
|
||||
if (dry_run) return 0;
|
||||
RETURN_ERROR_IF_RO_OR_LO;
|
||||
|
||||
if (!am_daemon || am_chrooted)
|
||||
if (!secure_relpath_active())
|
||||
return do_symlink(lnk, path);
|
||||
|
||||
if (!path || !*path || *path == '/')
|
||||
@@ -338,11 +348,10 @@ int do_link_at(const char *old_path, const char *new_path)
|
||||
if (dry_run) return 0;
|
||||
RETURN_ERROR_IF_RO_OR_LO;
|
||||
|
||||
if (!am_daemon || am_chrooted)
|
||||
if (!secure_relpath_active())
|
||||
return do_link(old_path, new_path);
|
||||
|
||||
if (!old_path || !*old_path || *old_path == '/'
|
||||
|| !new_path || !*new_path || *new_path == '/')
|
||||
if (!old_path || !*old_path || !new_path || !*new_path)
|
||||
return do_link(old_path, new_path);
|
||||
|
||||
old_slash = strrchr(old_path, '/');
|
||||
@@ -354,7 +363,9 @@ int do_link_at(const char *old_path, const char *new_path)
|
||||
* resolution -- otherwise a parent symlink (e.g. --link-dest=cd
|
||||
* where cd -> /outside) lets the kernel-level linkat(AT_FDCWD,
|
||||
* "cd/target.txt", ...) escape the module. */
|
||||
if (old_slash) {
|
||||
if (*old_path == '/') {
|
||||
old_bname = old_path;
|
||||
} else if (old_slash) {
|
||||
old_dlen = old_slash - old_path;
|
||||
if (old_dlen >= sizeof old_dirpath) { errno = ENAMETOOLONG; return -1; }
|
||||
memcpy(old_dirpath, old_path, old_dlen);
|
||||
@@ -368,7 +379,9 @@ int do_link_at(const char *old_path, const char *new_path)
|
||||
old_bname = old_path;
|
||||
}
|
||||
|
||||
if (new_slash) {
|
||||
if (*new_path == '/') {
|
||||
new_bname = new_path;
|
||||
} else if (new_slash) {
|
||||
new_dlen = new_slash - new_path;
|
||||
if (new_dlen >= sizeof new_dirpath) {
|
||||
e = ENAMETOOLONG;
|
||||
@@ -447,7 +460,7 @@ int do_lchown_at(const char *fname, uid_t owner, gid_t group)
|
||||
if (dry_run) return 0;
|
||||
RETURN_ERROR_IF_RO_OR_LO;
|
||||
|
||||
if (!am_daemon || am_chrooted)
|
||||
if (!secure_relpath_active())
|
||||
return do_lchown(fname, owner, group);
|
||||
|
||||
if (!fname || !*fname || *fname == '/')
|
||||
@@ -567,7 +580,7 @@ int do_mknod_at(const char *pathname, mode_t mode, dev_t dev)
|
||||
if (dry_run) return 0;
|
||||
RETURN_ERROR_IF_RO_OR_LO;
|
||||
|
||||
if (!am_daemon || am_chrooted)
|
||||
if (!secure_relpath_active())
|
||||
return do_mknod(pathname, mode, dev);
|
||||
|
||||
#if !defined MKNOD_CREATES_SOCKETS && defined HAVE_SYS_UN_H
|
||||
@@ -663,7 +676,7 @@ int do_rmdir_at(const char *pathname)
|
||||
if (dry_run) return 0;
|
||||
RETURN_ERROR_IF_RO_OR_LO;
|
||||
|
||||
if (!am_daemon || am_chrooted)
|
||||
if (!secure_relpath_active())
|
||||
return rmdir(pathname);
|
||||
|
||||
if (!pathname || !*pathname || *pathname == '/')
|
||||
@@ -743,7 +756,7 @@ int do_open_at(const char *pathname, int flags, mode_t mode)
|
||||
RETURN_ERROR_IF_RO_OR_LO;
|
||||
}
|
||||
|
||||
if (!am_daemon || am_chrooted)
|
||||
if (!secure_relpath_active())
|
||||
return do_open(pathname, flags, mode);
|
||||
|
||||
if (!pathname || !*pathname || *pathname == '/')
|
||||
@@ -873,7 +886,7 @@ int do_chmod_at(const char *fname, mode_t mode)
|
||||
* symlink they planted can only redirect to files they could
|
||||
* already access. Everywhere else, fall through to plain
|
||||
* do_chmod() to avoid the dirfd-open overhead on every call. */
|
||||
if (!am_daemon || am_chrooted)
|
||||
if (!secure_relpath_active())
|
||||
return do_chmod(fname, mode);
|
||||
|
||||
if (!fname || !*fname || *fname == '/' || S_ISLNK(mode))
|
||||
@@ -945,17 +958,18 @@ int do_rename_at(const char *old_path, const char *new_path)
|
||||
if (dry_run) return 0;
|
||||
RETURN_ERROR_IF_RO_OR_LO;
|
||||
|
||||
if (!am_daemon || am_chrooted)
|
||||
if (!secure_relpath_active())
|
||||
return do_rename(old_path, new_path);
|
||||
|
||||
if (!old_path || !*old_path || *old_path == '/'
|
||||
|| !new_path || !*new_path || *new_path == '/')
|
||||
if (!old_path || !*old_path || !new_path || !*new_path)
|
||||
return do_rename(old_path, new_path);
|
||||
|
||||
old_slash = strrchr(old_path, '/');
|
||||
new_slash = strrchr(new_path, '/');
|
||||
|
||||
if (old_slash) {
|
||||
if (*old_path == '/') {
|
||||
old_bname = old_path;
|
||||
} else if (old_slash) {
|
||||
old_dlen = old_slash - old_path;
|
||||
if (old_dlen >= sizeof old_dirpath) {
|
||||
errno = ENAMETOOLONG;
|
||||
@@ -972,7 +986,9 @@ int do_rename_at(const char *old_path, const char *new_path)
|
||||
old_bname = old_path;
|
||||
}
|
||||
|
||||
if (new_slash) {
|
||||
if (*new_path == '/') {
|
||||
new_bname = new_path;
|
||||
} else if (new_slash) {
|
||||
new_dlen = new_slash - new_path;
|
||||
if (new_dlen >= sizeof new_dirpath) {
|
||||
e = ENAMETOOLONG;
|
||||
@@ -1082,7 +1098,7 @@ int do_mkdir_at(char *path, mode_t mode)
|
||||
RETURN_ERROR_IF_RO_OR_LO;
|
||||
trim_trailing_slashes(path);
|
||||
|
||||
if (!am_daemon || am_chrooted)
|
||||
if (!secure_relpath_active())
|
||||
return mkdir(path, mode);
|
||||
|
||||
if (!path || !*path || *path == '/')
|
||||
@@ -1191,7 +1207,7 @@ static int do_xstat_at(const char *path, STRUCT_STAT *st, int at_flags, int (*fa
|
||||
int dfd, ret, e;
|
||||
size_t dlen;
|
||||
|
||||
if (!am_daemon || am_chrooted)
|
||||
if (!secure_relpath_active())
|
||||
return fallback(path, st);
|
||||
|
||||
if (!path || !*path || *path == '/')
|
||||
@@ -1274,7 +1290,7 @@ int do_setattrlist_times(const char *path, STRUCT_STAT *stp)
|
||||
* macOS-specific attribute set this function would have used
|
||||
* (ATTR_CMN_MODTIME / ATTR_CMN_ACCTIME) is the same set
|
||||
* utimensat() handles, so no functionality is lost. */
|
||||
if (am_daemon && !am_chrooted) {
|
||||
if (secure_relpath_active()) {
|
||||
errno = ENOSYS;
|
||||
return -1;
|
||||
}
|
||||
@@ -1309,7 +1325,7 @@ int do_setattrlist_crtime(const char *path, time_t crtime)
|
||||
* crtime preservation is silently dropped for that file (the
|
||||
* caller treats this as "crtime not updated"). The transfer
|
||||
* itself continues normally. */
|
||||
if (am_daemon && !am_chrooted) {
|
||||
if (secure_relpath_active()) {
|
||||
errno = ENOSYS;
|
||||
return -1;
|
||||
}
|
||||
@@ -1340,7 +1356,7 @@ time_t get_create_time(const char *path, STRUCT_STAT *stp)
|
||||
* outside the module. The caller's "no crtime available"
|
||||
* path returns 0; the file gets a fresh crtime instead of
|
||||
* preserving the source's. */
|
||||
if (am_daemon && !am_chrooted)
|
||||
if (secure_relpath_active())
|
||||
return 0;
|
||||
memset(&attrList, 0, sizeof attrList);
|
||||
attrList.bitmapcount = ATTR_BIT_MAP_COUNT;
|
||||
@@ -1437,7 +1453,7 @@ int do_utimensat_at(const char *path, STRUCT_STAT *stp)
|
||||
if (dry_run) return 0;
|
||||
RETURN_ERROR_IF_RO_OR_LO;
|
||||
|
||||
if (!am_daemon || am_chrooted)
|
||||
if (!secure_relpath_active())
|
||||
return do_utimensat(path, stp);
|
||||
|
||||
if (!path || !*path || *path == '/')
|
||||
@@ -2346,7 +2362,7 @@ int open_dir_secure(const char *dirname)
|
||||
/* Authority gate, identical to the do_*_at() wrappers. When hardened
|
||||
* resolution isn't in effect, return -1 with errno cleared so the caller
|
||||
* uses the full-path wrappers. */
|
||||
if (!am_daemon || am_chrooted) {
|
||||
if (!secure_relpath_active()) {
|
||||
errno = 0;
|
||||
return -1;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user