CI / lint (pull_request) Successful in 1m45s
CI / sanitizers (address) (pull_request) Skipped
CI / sanitizers (undefined) (pull_request) Skipped
CI / fuzz-build (pull_request) Skipped
CI / coverage (pull_request) Skipped
CI / valgrind (pull_request) Skipped
CI / build-and-test (pull_request) Successful in 45s
Address findings from the four-agent review of PR #298: - file_create: zero the new File.matched_bytes. It was uninitialized malloc memory, so the receiver could sum a garbage value into STATUS_STATS "Matched data" (nondeterministic --stats divergence and an uninitialized-heap disclosure on the wire). - send_append: load the source into memory before hashing/copying the prefix and tail. Files >64 MiB without compression (and --sendfile runs) are streamed without loading, so --append/--append-verify dereferenced a NULL data->data and crashed. - filter_file_append: clamp the rollback to the surviving rule count. A "clear" rule in a merge file frees every rule including the caller's; the old rollback rewound count to rules_before and resurrected freed pointers for a double free / UAF. Also roll back when set_rule_owner fails instead of leaving owner-less rules. - filter_rule_parse: reject the xattr-name filter modifier (x), which was parsed and silently reinterpreted as a filename rule (affecting what --delete protects). The p modifier stays accepted (existing grammar test). - Remove two dead functions: compression_default_algo and change_render_itemize_code. - tests: free ctx->would_delete in the two test_multiprocessing manual teardowns (ASan leak, 1648 bytes/run). - docs: correct the RSYNC_COMPAT/HANDOFF tally (156 rows: 106/27/23), downgrade --info/--debug to caveat with their silent categories, add %C-vs-xxh64 and --delete-delay count caveats, refresh stale xattr mode comments, and document -p special-bit (setuid/setgid/sticky) parity plus its mitigations.
114 lines
5.8 KiB
C
114 lines
5.8 KiB
C
#ifndef XATTR_H
|
|
#define XATTR_H
|
|
|
|
#include "file_attr.h"
|
|
#include <stdbool.h>
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
|
|
/*
|
|
* Portable extended-attribute (xattr) and POSIX-ACL preservation (Phase 4,
|
|
* protocol 2.13.0). --xattrs/-X and --acls/-A are implemented on top of the
|
|
* xattr machinery: the SENDER captures a bounded, namespace-whitelisted set of
|
|
* `name = value` pairs per file, transmits them in a per-file wire block, and
|
|
* the RECEIVER re-applies them fd-relative on the just-written file. Linux
|
|
* xattr syscalls are used; libacl is NOT required (ACLs travel as the
|
|
* system.posix_acl_access / system.posix_acl_default xattrs).
|
|
*
|
|
* Security model:
|
|
* * A client can never force a `security.*` / privileged xattr onto the
|
|
* destination: both capture (sender) and apply (receiver) are restricted to
|
|
* the unprivileged `user.*` namespace and the two POSIX ACL xattrs. The
|
|
* receiver independently re-validates every incoming name against this
|
|
* whitelist, so a malicious sender's `security.capability` payload is
|
|
* rejected, not applied.
|
|
* * Payloads are bounded (per-name length, per-value length, per-file count
|
|
* and total bytes) on BOTH ends to prevent OOM/memory abuse; an oversized
|
|
* or malformed frame is a clean protocol rejection, never an allocation
|
|
* blowup.
|
|
* * Application is confined to the exact destination file descriptor
|
|
* (fsetxattr on the just-written fd), never a caller-controlled path.
|
|
*/
|
|
|
|
/* Reserved key used by --fake-super to park the source's privileged ownership
|
|
* / mode / mtime on the destination file as an unprivileged user.* xattr, so a
|
|
* later privileged restore could re-apply them. Exact documented format:
|
|
* uid:gid:mode:mtime_sec:mtime_nsec (decimal, decimal, octal, dec, dec)
|
|
* e.g. "1000:1000:644:1765238400:0". */
|
|
#define FAKESUPER_XATTR "user.fastsync.stat"
|
|
|
|
/* --- bounds --- */
|
|
#define XATTR_NAME_MAX 255 /* xattr names are limited to 255 bytes */
|
|
#define XATTR_VALUE_MAX (1024 * 1024) /* per-value cap (1 MiB) */
|
|
#define XATTR_TOTAL_MAX (4 * 1024 * 1024) /* per-file total name+value bytes */
|
|
#define XATTR_MAX_COUNT 256
|
|
|
|
typedef struct {
|
|
char* name; /* owned, NUL-terminated */
|
|
unsigned char* value; /* owned, may hold embedded NULs */
|
|
size_t value_len;
|
|
} FileXattr;
|
|
|
|
typedef struct {
|
|
FileXattr* items;
|
|
int count;
|
|
} FileXattrList;
|
|
|
|
FileXattrList* xattr_list_new(void);
|
|
void xattr_list_free(FileXattrList* list);
|
|
/* Deep-copy `list` (NULL in, NULL out). Returns NULL on allocation failure. */
|
|
FileXattrList* xattr_list_clone(const FileXattrList* list);
|
|
/* Append one entry (deep copy). Returns false on allocation failure. */
|
|
bool xattr_list_append(FileXattrList* list, const char* name, const void* value, size_t value_len);
|
|
|
|
/* True when `name` is a well-formed xattr name AND belongs to a namespace this
|
|
* build is authorized to apply. `user.*` is always accepted for -X; the two
|
|
* POSIX ACL xattrs are accepted only when `preserve_acls` (--acls/-A) is set, so
|
|
* a plain -X run can never carry or apply an ACL the receiver did not ask for.
|
|
* Used for both capture and receiver-side validation. */
|
|
bool xattr_name_appliable(const char* name, bool preserve_acls);
|
|
|
|
/* Sender: read the whitelisted xattrs of `path` into a new list. The POSIX ACL
|
|
* names are captured only when `preserve_acls` (--acls/-A) is set, so a plain
|
|
* -X run never carries an ACL it was not asked to preserve; `user.*` is
|
|
* unaffected. Returns NULL when the path has no appliable xattrs (or the
|
|
* filesystem has no xattr support); an empty-but-valid list is never returned
|
|
* distinct from NULL. */
|
|
FileXattrList* xattr_capture_path(const char* path, bool preserve_acls);
|
|
|
|
/* Wire: bounded serialization. xattr_send returns false on write failure; an
|
|
* empty/NULL list transmits a zero-count block. xattr_receive returns NULL and
|
|
* sets *ok = 0 on any malformed / oversized / non-whitelisted entry. When
|
|
* `preserve_acls` is false, any POSIX ACL entries are consumed and DROPPED (so
|
|
* a -X transfer still succeeds and never applies an ACL it did not negotiate);
|
|
* a genuinely disallowed namespace is still rejected. */
|
|
bool xattr_send(int fd, const FileXattrList* list);
|
|
FileXattrList* xattr_receive(int fd, int* ok, bool preserve_acls);
|
|
|
|
/* Receiver: apply every entry fd-relative (fsetxattr) to the just-written file
|
|
* descriptor. A per-attribute failure (e.g. ACL set refused for non-root on a
|
|
* file the process does not own) is logged and skipped, never fatal. Returns
|
|
* true when apply was attempted (allowing callers to treat it as best-effort). */
|
|
bool xattr_apply_fd(int fd, const FileXattrList* list);
|
|
|
|
/* --fake-super: write the source uid/gid/mode/mtime record into the reserved
|
|
* FAKESUPER_XATTR on `fd`. Best-effort (logged, never fatal). Only meaningful
|
|
* when metadata was transmitted so the values exist. */
|
|
void fake_super_store_fd(int fd, uint32_t uid, uint32_t gid, uint32_t mode, int64_t mtime_sec,
|
|
int64_t mtime_nsec);
|
|
|
|
/* --fake-super replay: parse the FAKESUPER_XATTR record previously written on
|
|
* `fd` by fake_super_store_fd and re-apply mode/mtime fd-relative. The
|
|
* recorded uid/gid are deliberately NOT chowned for real: --fake-super only
|
|
* RECORDS ownership (the caller stores the resolved mapping via
|
|
* identity_resolve_storage_ids), it never performs a real chown. Best-effort:
|
|
* absence of the xattr or a malformed record is a silent no-op that never fails
|
|
* the transfer. The MODE leg is applied only when policy.perms||policy.
|
|
* executability and the MTIME leg only when policy.times, so the fake-super
|
|
* replay cannot bypass the per-attribute split; the mode follows the normal
|
|
* metadata path exactly (under --perms the source mode is copied verbatim,
|
|
* special and group/other write bits included).
|
|
* Returns true when the xattr was present and parsed. */
|
|
bool fake_super_restore_fd(int fd, FileAttrPolicy policy);
|
|
|
|
#endif |