- H3: a daemon module without 'client owner = yes' now also has super-user device activity forced off (char/block mknod, --write-devices), so a root daemon can no longer be made to create/write raw devices under AUTO. The entries are skipped, preserving ordinary -a pushes. - H1/H2: propagate a failed required --copy-as chown from symlink metadata restore and implicitly-created parent directories, so the entry (and run) reports failure instead of a wrong-owner success. - Docs/help/headers updated for A2/A3 and the device clamp; startup warning spells out the client-owner risk. - Tests: daemon device clamp (skipped without opt-in, created with opt-in), updated --super/--fake-super expectations.
101 lines
4.9 KiB
C
101 lines
4.9 KiB
C
#ifndef XATTR_H
|
|
#define XATTR_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);
|
|
/* 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.* or the two POSIX ACL xattrs). Used for
|
|
* both capture and receiver-side validation. */
|
|
bool xattr_name_appliable(const char* name);
|
|
|
|
/* Sender: read the whitelisted xattrs of `path` into a new list. 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);
|
|
|
|
/* 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. */
|
|
bool xattr_send(int fd, const FileXattrList* list);
|
|
FileXattrList* xattr_receive(int fd, int* ok);
|
|
|
|
/* 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 uid/gid/mode/mtime fd-relative.
|
|
* Best-effort: absence of the xattr or a malformed record is a silent no-op
|
|
* that never fails the transfer. The OWNER leg is applied only when an explicit
|
|
* ownership identity policy is active (numeric-ids/chown/usermap/groupmap/
|
|
* copy-as), when super-user activities are permitted, and when --copy-as is not
|
|
* authoritative; a non-root EPERM/EACCES is skipped silently, matching
|
|
* FastSync's identity philosophy. The mode is sanitized exactly like the normal
|
|
* metadata path (group/other write bits never granted). Returns true when the
|
|
* xattr was present and parsed. */
|
|
bool fake_super_restore_fd(int fd);
|
|
|
|
#endif |