mirror of
https://github.com/RsyncProject/rsync.git
synced 2026-09-14 22:28:57 -04:00
Two fixes from codex review of the no-AT_FDCWD port: - vfs/stat.c: the held-dirfd vfs_lstat branch fell back to fstatat(dirfd, path, st, 0) when AT_SYMLINK_NOFOLLOW is unavailable, which FOLLOWS the leaf and breaks lstat's no-follow contract. On a system with SUPPORT_LINKS but no AT_SYMLINK_NOFOLLOW, return ENOSYS instead (mirroring the held-fd vfs_lchown arm) so a symlink-sensitive caller fails loud rather than silently following; the !SUPPORT_LINKS arm keeps fstatat(...,0) since there is nothing to follow. The CI compile-check config also undefines AT_FDCWD so no held fd is produced there; this hardens the standalone "no AT_SYMLINK_NOFOLLOW" shape. - Makefile.in: the vfs-no-at-fdcwd.o compile loop wrote every object to $@, so a mid-loop failure left a fresh-timestamped $@ and a retry could skip the check. Compile to $@.tmp and mv to $@ only after the whole loop succeeds (rm the stale target up front); clean the .tmp too.
190 lines
5.3 KiB
C
190 lines
5.3 KiB
C
/*
|
|
* vfs/stat.c - stat/lstat/fstat wrappers (path, parent-resolved, held-dirfd).
|
|
*
|
|
* Moved verbatim out of syscall.c.
|
|
*
|
|
* Copyright (C) 1998-2022 Andrew Tridgell, Martin Pool, Wayne Davison
|
|
* Copyright (C) 2026 Wayne Davison, Andrew Tridgell
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*/
|
|
|
|
#include "rsync.h"
|
|
#include "ifuncs.h"
|
|
#include "vfs/vfs_internal.h"
|
|
|
|
static int vfs__stat_plain(const char *path, STRUCT_STAT *st)
|
|
{
|
|
RETURN_ERROR_IF_NULL(path);
|
|
#ifdef USE_STAT64_FUNCS
|
|
return stat64(path, st);
|
|
#else
|
|
return stat(path, st);
|
|
#endif
|
|
}
|
|
|
|
static int vfs__lstat_plain(const char *path, STRUCT_STAT *st)
|
|
{
|
|
RETURN_ERROR_IF_NULL(path);
|
|
#ifdef SUPPORT_LINKS
|
|
# ifdef USE_STAT64_FUNCS
|
|
return lstat64(path, st);
|
|
# else
|
|
return lstat(path, st);
|
|
# endif
|
|
#else
|
|
return vfs__stat_plain(path, st);
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
Symlink-race-safe variants of vfs_stat() / vfs_lstat() for receiver-
|
|
side use. See the comment on vfs__chmod_secure() for the threat model.
|
|
stat() and lstat() resolve parent components, so a parent-symlink
|
|
swap can make the receiver's stat see attributes of a victim file
|
|
outside the module -- which then drives later behaviour (e.g.
|
|
"this isn't a directory, delete it" -> attacker-controlled unlink
|
|
on something outside the module).
|
|
|
|
Defence: open the parent under vfs_resolve_open() and use
|
|
fstatat() with AT_SYMLINK_NOFOLLOW (lstat) or 0 (stat) against
|
|
that dirfd. Same fall-through gating as the other wrappers.
|
|
*/
|
|
static int do_xstat_at(const char *path, STRUCT_STAT *st, int at_flags, int (*fallback)(const char *, STRUCT_STAT *), int vfs_flags)
|
|
{
|
|
#ifdef AT_FDCWD
|
|
char dirpath[MAXPATHLEN];
|
|
const char *bname;
|
|
const char *slash;
|
|
int dfd, ret, e;
|
|
size_t dlen;
|
|
|
|
#if defined O_NOFOLLOW && defined O_DIRECTORY
|
|
if (vfs_flags & VFS_OPERATOR_PATH) {
|
|
if (vfs_symlink_optout_allowed())
|
|
return fallback(path, st);
|
|
dfd = vfs_owner_walk_parent(path, &bname, 1);
|
|
if (dfd < 0)
|
|
return -1;
|
|
ret = fstatat(dfd, bname, st, at_flags);
|
|
e = errno;
|
|
close(dfd);
|
|
errno = e;
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
if (!vfs_relpath_active())
|
|
return fallback(path, st);
|
|
|
|
if (!path || !*path || *path == '/')
|
|
return fallback(path, st);
|
|
|
|
slash = strrchr(path, '/');
|
|
if (!slash)
|
|
return fallback(path, st);
|
|
|
|
dlen = slash - path;
|
|
if (dlen >= sizeof dirpath) {
|
|
errno = ENAMETOOLONG;
|
|
return -1;
|
|
}
|
|
memcpy(dirpath, path, dlen);
|
|
dirpath[dlen] = '\0';
|
|
bname = slash + 1;
|
|
|
|
dfd = vfs_resolve_open(NULL, dirpath, O_RDONLY | O_DIRECTORY, 0);
|
|
if (dfd < 0)
|
|
return -1;
|
|
|
|
ret = fstatat(dfd, bname, st, at_flags);
|
|
e = errno;
|
|
close(dfd);
|
|
errno = e;
|
|
return ret;
|
|
#else
|
|
(void)at_flags; (void)vfs_flags;
|
|
return fallback(path, st);
|
|
#endif
|
|
}
|
|
|
|
/* Unified stat/lstat. dirfd == VFS_AT_FDCWD resolves `path` (VFS_ALLOW_SYMLINK =
|
|
* plain libc stat/lstat, default 0 = secure receiver resolve, VFS_OPERATOR_PATH =
|
|
* ownership walk); a real held dirfd fstatat()s a single validated component
|
|
* under it. vfs_stat follows the leaf, vfs_lstat does not. */
|
|
int vfs_stat(int dirfd, const char *path, STRUCT_STAT *st, int flags)
|
|
{
|
|
RETURN_ERROR_IF_NULL(path);
|
|
if (dirfd != VFS_AT_FDCWD) {
|
|
#ifdef AT_FDCWD
|
|
/* Held-fd: reject empty, multi-component (a '/' would resolve a path
|
|
* under the pinned dir) and ".." (escapes to the parent). "." is
|
|
* allowed: a read-only fstatat of the dir itself is legitimate
|
|
* (link_stat_at). */
|
|
if (!*path || strchr(path, '/')
|
|
|| (path[0] == '.' && path[1] == '.' && path[2] == '\0')) {
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
return fstatat(dirfd, path, st, 0);
|
|
#else
|
|
(void)dirfd; errno = ENOSYS; return -1;
|
|
#endif
|
|
}
|
|
if (flags & VFS_ALLOW_SYMLINK)
|
|
return vfs__stat_plain(path, st);
|
|
return do_xstat_at(path, st, 0, vfs__stat_plain, flags);
|
|
}
|
|
|
|
int vfs_lstat(int dirfd, const char *path, STRUCT_STAT *st, int flags)
|
|
{
|
|
RETURN_ERROR_IF_NULL(path);
|
|
if (dirfd != VFS_AT_FDCWD) {
|
|
#ifdef AT_FDCWD
|
|
/* Held-fd: reject empty, multi-component and ".."; "." is allowed
|
|
* (read-only fstatat of the dir itself -- link_stat_at). */
|
|
if (!*path || strchr(path, '/')
|
|
|| (path[0] == '.' && path[1] == '.' && path[2] == '\0')) {
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
# if defined SUPPORT_LINKS && defined AT_SYMLINK_NOFOLLOW
|
|
return fstatat(dirfd, path, st, AT_SYMLINK_NOFOLLOW);
|
|
# elif defined SUPPORT_LINKS
|
|
/* No AT_SYMLINK_NOFOLLOW: an fstatat via a held dirfd cannot honour
|
|
* lstat's no-follow contract, so fail loud rather than silently
|
|
* follow the leaf (mirrors the held-fd vfs_lchown ENOSYS arm); a
|
|
* symlink-sensitive caller must use the path-based form. */
|
|
(void)dirfd; errno = ENOSYS; return -1;
|
|
# else
|
|
/* No link support: nothing to follow, fstatat == lstat. */
|
|
return fstatat(dirfd, path, st, 0);
|
|
# endif
|
|
#else
|
|
(void)dirfd; errno = ENOSYS; return -1;
|
|
#endif
|
|
}
|
|
if (flags & VFS_ALLOW_SYMLINK)
|
|
return vfs__lstat_plain(path, st);
|
|
#if defined SUPPORT_LINKS && defined AT_FDCWD && defined AT_SYMLINK_NOFOLLOW
|
|
return do_xstat_at(path, st, AT_SYMLINK_NOFOLLOW, vfs__lstat_plain, flags);
|
|
#elif defined SUPPORT_LINKS
|
|
return vfs__lstat_plain(path, st);
|
|
#else
|
|
return do_xstat_at(path, st, 0, vfs__stat_plain, flags);
|
|
#endif
|
|
}
|
|
|
|
int vfs_fstat(int fd, STRUCT_STAT *st)
|
|
{
|
|
#ifdef USE_STAT64_FUNCS
|
|
return fstat64(fd, st);
|
|
#else
|
|
return fstat(fd, st);
|
|
#endif
|
|
}
|
|
|