mirror of
https://github.com/RsyncProject/rsync.git
synced 2026-10-09 18:41:13 -04:00
acls: route race-safe ACL ops through libacl *_at when available
When configure detects a patched libacl exporting acl_get_file_at / acl_set_file_at / acl_delete_def_file_at (new HAVE_LIBACL_AT; ACL_1.3), route the receiver's race-safe ACL get/set/delete through them instead of lib/acl.c's xacl_* -- held fd via AT_EMPTY_PATH, dirfd+leaf via AT_SYMLINK_NOFOLLOW, reusing the existing unpack_smb_acl/pack_smb_acl conversion. libacl's /proc/self/fd compat makes this race-safe on every Linux kernel. lib/acl.c stays as the fallback for a stock libacl (macro undefined -> zero change there); it already closes the same pre-6.13 parent-symlink-race gap via its own /proc/self/fd compat, so the two paths are equivalent in safety and prefer libacl only when its newer bindings are present. -VV reports ACL-libacl-at and keeps "ACL_at": true. A stock -lacl lacks these symbols, so HAVE_LIBACL_AT is undefined on real distros and the build is byte-identical to the lib/acl.c path until libacl ships ACL_1.3; build against the patched lib via CPPFLAGS/LDFLAGS pointing at its install prefix.
This commit is contained in:
1 parent
8fc908ebaf
commit
893d3f612e
5 files changed
+137
-1
No files matched your search
@@ -22,6 +22,9 @@
|
||||
#include "rsync.h"
|
||||
#include "lib/sysacls.h"
|
||||
#include "lib/acl.h"
|
||||
#ifdef HAVE_LIBACL_AT
|
||||
#include <fcntl.h> /* AT_EMPTY_PATH / AT_SYMLINK_NOFOLLOW */
|
||||
#endif
|
||||
|
||||
#ifdef SUPPORT_ACLS
|
||||
|
||||
@@ -470,7 +473,10 @@ static int find_matching_rsync_acl(const rsync_acl *racl, SMB_ACL_TYPE_T type,
|
||||
return *match;
|
||||
}
|
||||
|
||||
#ifdef SUPPORT_ACL_FD
|
||||
/* These two bridge lib/acl.c's neutral (tag,perm,id) entry array; with
|
||||
* HAVE_LIBACL_AT the libacl *_at path uses unpack_smb_acl/pack_smb_acl directly,
|
||||
* so they are unused there. */
|
||||
#if defined(SUPPORT_ACL_FD) && !defined(HAVE_LIBACL_AT)
|
||||
/* Convert a packed system ACL into the neutral (tag,perm,id) entry array that
|
||||
* lib/acl.c serializes. Reuses pack_smb_acl()+change_sacl_perms() output so
|
||||
* the bytes we write match exactly what acl_set_file() would have written.
|
||||
@@ -627,6 +633,33 @@ static int get_rsync_acl(int fd, int dirfd, const char *leaf, const char *fname,
|
||||
#endif
|
||||
|
||||
#ifdef SUPPORT_ACL_FD
|
||||
#ifdef HAVE_LIBACL_AT
|
||||
/* Read the ACL via the new libacl *_at calls; fd<0 && dirfd<0
|
||||
* (e.g. a synthetic dir) falls through to the path-based call below. */
|
||||
if (fd >= 0 || dirfd >= 0) {
|
||||
if (fd >= 0)
|
||||
sacl = sys_acl_get_file_at(fd, "", AT_EMPTY_PATH, type);
|
||||
else
|
||||
sacl = sys_acl_get_file_at(dirfd, leaf, AT_SYMLINK_NOFOLLOW, type);
|
||||
if (sacl != 0) {
|
||||
BOOL ok = unpack_smb_acl(sacl, racl);
|
||||
sys_acl_free_acl(sacl);
|
||||
if (!ok) {
|
||||
rsyserr(FERROR_XFER, errno, "get_acl: unpack_smb_acl(%s)", fname);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (no_acl_syscall_error(errno)) {
|
||||
if (type == SMB_ACL_TYPE_ACCESS)
|
||||
rsync_acl_fake_perms(racl, mode);
|
||||
return 0;
|
||||
}
|
||||
rsyserr(FERROR_XFER, errno, "get_acl: acl_get_file_at(%s, %s)",
|
||||
fname, str_acl_type(type));
|
||||
return -1;
|
||||
}
|
||||
#else
|
||||
/* Race-safe path: read the ACL through the held O_NOFOLLOW fd, or via
|
||||
* setxattrat(AT_SYMLINK_NOFOLLOW) on dirfd+leaf, instead of re-resolving
|
||||
* fname. Only for real-root ACLs (am_root >= 0; the fake-super branch
|
||||
@@ -670,6 +703,7 @@ static int get_rsync_acl(int fd, int dirfd, const char *leaf, const char *fname,
|
||||
* BSDs / a /proc-less namespace / an un-pinnable entry): read the real
|
||||
* destination ACL via the path-based call rather than a mode-only fake, so
|
||||
* --acls stays functional where the race-safe primitive is unavailable. */
|
||||
#endif /* HAVE_LIBACL_AT */
|
||||
#endif
|
||||
|
||||
if ((sacl = sys_acl_get_file(fname, type)) != 0) {
|
||||
@@ -1114,6 +1148,16 @@ static int set_rsync_acl(int fd, int dirfd, const char *leaf, const char *fname,
|
||||
else
|
||||
#endif
|
||||
#ifdef SUPPORT_ACL_FD
|
||||
#ifdef HAVE_LIBACL_AT
|
||||
/* Race-safe default-ACL delete via the new libacl *_at
|
||||
* calls (held fd via AT_EMPTY_PATH, dirfd+leaf via AT_SYMLINK_NOFOLLOW)
|
||||
* -- race-safe on every Linux kernel. fd<0 && dirfd<0 falls to path. */
|
||||
if (fd >= 0)
|
||||
rc = sys_acl_delete_def_file_at(fd, "", AT_EMPTY_PATH);
|
||||
else if (dirfd >= 0)
|
||||
rc = sys_acl_delete_def_file_at(dirfd, leaf, AT_SYMLINK_NOFOLLOW);
|
||||
else
|
||||
#else
|
||||
/* Race-safe default-ACL delete via the held fd or dirfd+leaf. Where
|
||||
* neither is available (xacl_at_available() is false -- the BSDs, a
|
||||
* /proc-less namespace, an un-pinnable entry; every Linux with procfs
|
||||
@@ -1125,6 +1169,7 @@ static int set_rsync_acl(int fd, int dirfd, const char *leaf, const char *fname,
|
||||
else if (dirfd >= 0 && xacl_at_available())
|
||||
rc = xacl_del_default_at(dirfd, leaf);
|
||||
else
|
||||
#endif /* HAVE_LIBACL_AT */
|
||||
#endif
|
||||
rc = sys_acl_delete_def_file(fname);
|
||||
if (rc < 0) {
|
||||
@@ -1172,6 +1217,28 @@ static int set_rsync_acl(int fd, int dirfd, const char *leaf, const char *fname,
|
||||
}
|
||||
#endif
|
||||
#ifdef SUPPORT_ACL_FD
|
||||
#ifdef HAVE_LIBACL_AT
|
||||
/* Apply the packed/perm-reconciled ACL (duo_item->sacl)
|
||||
* through the new libacl *_at calls -- held fd via AT_EMPTY_PATH,
|
||||
* dirfd+leaf via AT_SYMLINK_NOFOLLOW -- race-safe on every Linux kernel,
|
||||
* and byte-identical to the path-based sys_acl_set_file() below. */
|
||||
if (fd >= 0 || dirfd >= 0) {
|
||||
int rc;
|
||||
|
||||
if (fd >= 0)
|
||||
rc = sys_acl_set_file_at(fd, "", AT_EMPTY_PATH, type, duo_item->sacl);
|
||||
else
|
||||
rc = sys_acl_set_file_at(dirfd, leaf, AT_SYMLINK_NOFOLLOW, type, duo_item->sacl);
|
||||
if (rc < 0) {
|
||||
rsyserr(FERROR_XFER, errno, "set_acl: acl_set_file_at(%s, %s)",
|
||||
fname, str_acl_type(type));
|
||||
return -1;
|
||||
}
|
||||
if (type == SMB_ACL_TYPE_ACCESS)
|
||||
sxp->st.st_mode = cur_mode;
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
/* Race-safe write: serialize the packed (and perm-reconciled)
|
||||
* system ACL to the kernel xattr format and apply it through the
|
||||
* held fd or dirfd+leaf -- never re-resolving fname. This matches
|
||||
@@ -1206,6 +1273,7 @@ static int set_rsync_acl(int fd, int dirfd, const char *leaf, const char *fname,
|
||||
* carries the parent-symlink-race exposure on those remaining platforms;
|
||||
* it is the only way to honour --acls where no race-safe primitive
|
||||
* exists. */
|
||||
#endif /* HAVE_LIBACL_AT */
|
||||
#endif
|
||||
if (sys_acl_set_file(fname, type, duo_item->sacl) < 0) {
|
||||
rsyserr(FERROR_XFER, errno, "set_acl: sys_acl_set_file(%s, %s)",
|
||||
|
||||
@@ -1492,6 +1492,30 @@ struct xattr_args { uint64_t value; uint32_t size; uint32_t flags; };]],
|
||||
esac
|
||||
fi
|
||||
|
||||
#################################################
|
||||
# Detect a patched libacl providing the race-safe
|
||||
# *_at ACL entry points (acl_get_file_at/acl_set_file_at/acl_delete_def_file_at,
|
||||
# ACL_1.3, unreleased upstream). When present we route the race-safe ACL get/
|
||||
# set/delete through them on Linux -- race-safe on every kernel (6.13+ uses
|
||||
# *xattrat; older uses libacl's /proc/self/fd compat). A stock -lacl lacks these
|
||||
# symbols, so this stays undefined and the build falls back to lib/acl.c;
|
||||
# detection must therefore run against the patched lib (CPPFLAGS/LDFLAGS).
|
||||
AH_TEMPLATE([HAVE_LIBACL_AT],
|
||||
[Define to 1 if libacl provides acl_get_file_at/acl_set_file_at/acl_delete_def_file_at])
|
||||
if test x"$samba_cv_HAVE_POSIX_ACLS" = x"yes"; then
|
||||
case "$host_os" in
|
||||
*linux*)
|
||||
AC_CHECK_LIB(acl, acl_get_file_at, [rsync_have_libacl_at=yes], [rsync_have_libacl_at=no])
|
||||
if test x"$rsync_have_libacl_at" = x"yes"; then
|
||||
AC_CHECK_FUNCS([acl_set_file_at acl_delete_def_file_at], [], [rsync_have_libacl_at=no])
|
||||
fi
|
||||
if test x"$rsync_have_libacl_at" = x"yes"; then
|
||||
AC_DEFINE(HAVE_LIBACL_AT, 1)
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
if test x"$enable_acl_support" = x"no" || test x"$enable_xattr_support" = x"no" || test x"$enable_iconv" = x"no"; then
|
||||
AC_MSG_CHECKING([whether $CC supports -Wno-unused-parameter])
|
||||
OLD_CFLAGS="$CFLAGS"
|
||||
|
||||
@@ -180,6 +180,26 @@ int sys_acl_free_acl(SMB_ACL_T the_acl)
|
||||
return acl_free(the_acl);
|
||||
}
|
||||
|
||||
#ifdef HAVE_LIBACL_AT
|
||||
/* Dirfd/AT-flag ACL ops via the new libacl,
|
||||
* race-safe on every Linux kernel. at_flags is AT_SYMLINK_NOFOLLOW (dirfd+leaf)
|
||||
* or AT_EMPTY_PATH (operate on an open fd passed as dirfd, path ""). */
|
||||
SMB_ACL_T sys_acl_get_file_at(int dirfd, const char *path_p, int at_flags, SMB_ACL_TYPE_T type)
|
||||
{
|
||||
return acl_get_file_at(dirfd, path_p, at_flags, type);
|
||||
}
|
||||
|
||||
int sys_acl_set_file_at(int dirfd, const char *path_p, int at_flags, SMB_ACL_TYPE_T type, SMB_ACL_T theacl)
|
||||
{
|
||||
return acl_set_file_at(dirfd, path_p, at_flags, type, theacl);
|
||||
}
|
||||
|
||||
int sys_acl_delete_def_file_at(int dirfd, const char *path_p, int at_flags)
|
||||
{
|
||||
return acl_delete_def_file_at(dirfd, path_p, at_flags);
|
||||
}
|
||||
#endif /* HAVE_LIBACL_AT */
|
||||
|
||||
#elif defined(HAVE_TRU64_ACLS) /*--------------------------------------------*/
|
||||
/*
|
||||
* The interface to DEC/Compaq Tru64 UNIX ACLs
|
||||
|
||||
@@ -304,4 +304,10 @@ int sys_acl_delete_def_file(const char *name);
|
||||
int sys_acl_free_acl(SMB_ACL_T the_acl);
|
||||
int no_acl_syscall_error(int err);
|
||||
|
||||
#ifdef HAVE_LIBACL_AT
|
||||
SMB_ACL_T sys_acl_get_file_at(int dirfd, const char *path_p, int at_flags, SMB_ACL_TYPE_T type);
|
||||
int sys_acl_set_file_at(int dirfd, const char *path_p, int at_flags, SMB_ACL_TYPE_T type, SMB_ACL_T theacl);
|
||||
int sys_acl_delete_def_file_at(int dirfd, const char *path_p, int at_flags);
|
||||
#endif
|
||||
|
||||
#endif /* SUPPORT_ACLS */
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "git-version.h"
|
||||
#include "default-cvsignore.h"
|
||||
#include "itypes.h"
|
||||
#include "lib/acl.h"
|
||||
|
||||
extern struct name_num_obj valid_checksums, valid_compressions, valid_auth_checksums;
|
||||
|
||||
@@ -106,6 +107,23 @@ static void print_info_flags(enum logcode f)
|
||||
#endif
|
||||
"ACLs",
|
||||
|
||||
#ifdef SUPPORT_ACL_FD
|
||||
#ifdef HAVE_LIBACL_AT
|
||||
/* The patched libacl *_at calls apply received ACLs race-safely
|
||||
* on every Linux kernel (compat layer below 6.13). Emit ACL-at (true, so
|
||||
* the JSON "ACL_at": true lets acl-symlink-race assert) plus a
|
||||
* distinguishing libacl token. */
|
||||
"ACL-at",
|
||||
"ACL-libacl-at",
|
||||
#else
|
||||
/* Runtime: are the *xattrat syscalls usable, so a received ACL is
|
||||
* applied race-safely (dirfd+leaf, AT_SYMLINK_NOFOLLOW) even when the
|
||||
* leaf can't be pinned? False => ACLs are applied via the path on an
|
||||
* un-pinnable leaf, which carries the parent-symlink-race exposure. */
|
||||
xacl_at_available() ? "ACL-at" : "no ACL-at",
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef SUPPORT_XATTRS
|
||||
"no "
|
||||
#endif
|
||||
|
||||
Reference in new issue
Block a user