Merge branch 'feat/transport-g2' into feat/transport-xattr
This commit is contained in:
@@ -926,12 +926,14 @@ static FileSaveResult file_save_symlink_to_disk(const FileSavePlan* plan, bool*
|
||||
/* -X/-A: apply the symlink's OWN xattrs with a no-follow primitive. The
|
||||
confined parent directory is the anchor and the final component is applied
|
||||
with lsetxattr, so the referent is never touched. Best-effort: on Linux
|
||||
the VFS refuses xattrs on symlinks, so this is normally a no-op. */
|
||||
if (ok && config && config->use_xattrs && file->xattrs) {
|
||||
the VFS refuses xattrs on symlinks, so this is normally a no-op. Hoist the
|
||||
empty-list check so the common Linux case (NULL/empty xattrs) does not pay
|
||||
an open/close of the parent per symlink. */
|
||||
if (ok && config && config->use_xattrs && file->xattrs && file->xattrs->count > 0) {
|
||||
char* leaf = NULL;
|
||||
int parent_fd = file_open_secure_parent(link_path, &leaf, false);
|
||||
if (parent_fd >= 0) {
|
||||
xattr_apply_path_nofollow(parent_fd, leaf, file->xattrs);
|
||||
xattr_apply_path_nofollow(parent_fd, leaf, file->xattrs, config->preserve_acls);
|
||||
close(parent_fd);
|
||||
}
|
||||
free(leaf);
|
||||
|
||||
+15
-5
@@ -383,8 +383,17 @@ bool xattr_apply_fd(int fd, const FileXattrList* list) {
|
||||
* referent. fsetxattr cannot be used (no *at xattr syscall exists, and the
|
||||
* kernel rejects xattr syscalls on an O_PATH descriptor), so the already-open,
|
||||
* confinement-checked parent directory is addressed through /proc/self/fd and
|
||||
* the final component is applied with lsetxattr, which does not follow it. */
|
||||
bool xattr_apply_path_nofollow(int parent_fd, const char* leaf, const FileXattrList* list) {
|
||||
* the final component is applied with lsetxattr, which does not follow it.
|
||||
*
|
||||
* The list is trusted to come from xattr_receive() (already whitelisted), but
|
||||
* every name is re-validated here so this path-based primitive is confined on
|
||||
* its own -- this is the only apply primitive that addresses a path, and the
|
||||
* header promises a whitelisted apply. The apply is best-effort: if /proc is
|
||||
* not mounted (the anchor cannot be formed) or the kernel refuses the set, the
|
||||
* failure is skipped and never fails the transfer. See xattr.h for the bounded
|
||||
* residual TOCTOU between link creation and lsetxattr. */
|
||||
bool xattr_apply_path_nofollow(int parent_fd, const char* leaf, const FileXattrList* list,
|
||||
bool preserve_acls) {
|
||||
if (parent_fd < 0 || !leaf || leaf[0] == '\0' || strchr(leaf, '/') != NULL || !list)
|
||||
return false;
|
||||
if (list->count == 0)
|
||||
@@ -403,9 +412,10 @@ bool xattr_apply_path_nofollow(int parent_fd, const char* leaf, const FileXattrL
|
||||
int first_errno = 0;
|
||||
for (int i = 0; i < list->count; i++) {
|
||||
const FileXattr* xa = &list->items[i];
|
||||
/* Defense in depth: even a hand-crafted list can never apply the reserved
|
||||
--fake-super key (only fake_super_store_fd may write it). */
|
||||
if (strcmp(xa->name, FAKESUPER_XATTR) == 0)
|
||||
/* Defense in depth: re-validate against the receiver's full whitelist, so a
|
||||
hand-crafted list can never apply a privileged namespace or the reserved
|
||||
--fake-super key through this path-based primitive. */
|
||||
if (!xattr_name_appliable(xa->name, preserve_acls))
|
||||
continue;
|
||||
if (lsetxattr(path, xa->name, xa->value, xa->value_len, 0) != 0) {
|
||||
if (!warned) {
|
||||
|
||||
+24
-8
@@ -110,16 +110,32 @@ bool xattr_apply_fd(int fd, const FileXattrList* list);
|
||||
|
||||
/* Receiver: apply every entry to the symlink named by (parent_fd, leaf) WITHOUT
|
||||
* following it, via lsetxattr() on the confined path
|
||||
* "/proc/self/fd/<parent_fd>/<leaf>". A symlink cannot be targeted by the
|
||||
* fd-relative fsetxattr() path: there is no *at() xattr syscall and the kernel
|
||||
* rejects xattr syscalls on an O_PATH descriptor, so the already-opened,
|
||||
* "/proc/self/fd/<parent_fd>/<leaf>". Every incoming name is independently
|
||||
* re-validated against xattr_name_appliable() with `preserve_acls`, exactly like
|
||||
* xattr_apply_fd(): a non-whitelisted namespace (including the reserved
|
||||
* --fake-super key) is skipped, so this primitive stays confined even if handed
|
||||
* a hand-crafted list. A symlink cannot be targeted by the fd-relative
|
||||
* fsetxattr() path: there is no *at() xattr syscall and the kernel rejects
|
||||
* xattr syscalls on an O_PATH descriptor, so the already-opened,
|
||||
* confinement-checked parent directory is the anchor and only the final
|
||||
* component is the (no-follow) link. `leaf` must be a single path component.
|
||||
* Best-effort exactly like xattr_apply_fd(): a per-attribute failure (on Linux
|
||||
* every set on a symlink fails with EPERM) is logged once and skipped, never
|
||||
* fatal. Returns false only for an invalid anchor/list; true when an apply was
|
||||
* attempted. The reserved --fake-super key is never applied. */
|
||||
bool xattr_apply_path_nofollow(int parent_fd, const char* leaf, const FileXattrList* list);
|
||||
*
|
||||
* Portability: the "/proc/self/fd/<parent_fd>" anchor requires a mounted /proc.
|
||||
* Where /proc is unavailable (or the fd cannot be addressed that way) the
|
||||
* lsetxattr simply fails and is skipped -- the apply is best-effort exactly like
|
||||
* xattr_apply_fd(), so no error is propagated and the transfer continues. A
|
||||
* per-attribute failure (on Linux every set on a symlink fails with EPERM) is
|
||||
* logged once and skipped, never fatal. Returns false only for an invalid
|
||||
* anchor/list; true when an apply was attempted.
|
||||
*
|
||||
* Residual TOCTOU: `leaf` is a caller-supplied name resolved by path in the
|
||||
* parent, so a local writer could replace the just-created symlink between its
|
||||
* creation and lsetxattr(). This is bounded: it requires write access to the
|
||||
* confinement-checked destination directory (already trusted), can only install
|
||||
* a whitelisted user namespace or POSIX-ACL name, and never follows the link (a
|
||||
* replacement symlink is still applied to as the final, no-follow component). */
|
||||
bool xattr_apply_path_nofollow(int parent_fd, const char* leaf, const FileXattrList* list,
|
||||
bool preserve_acls);
|
||||
|
||||
/* --fake-super: write the source uid/gid/mode/rdev record into the reserved
|
||||
* FAKESUPER_XATTR on `fd`, using rsync 3.4.1's exact grammar (see the key
|
||||
|
||||
Reference in New Issue
Block a user