fix(server): gate --force by --allow-delete and secure root super default
C2: --force is deletion authority (an incoming regular file may remove a non-empty destination directory tree, and --delete-missing-args may remove a non-empty directory mirror), but it was not masked by the operator --allow-delete policy. The handler now clears config->force_delete unless --allow-delete was given, exactly like --delete and --delete-missing-args. C3: a standalone TCP / --stdio server running as root defaulted to SUPER_MODE_AUTO, so an untrusted client --devices/--write-devices/ --super could make it create device nodes, write raw devices, or apply client-chosen ownership. A privileged standalone receiver now forces SUPER_MODE_OFF unless the operator opts in with the new server-only --allow-super flag. Non-root receivers are unchanged, and the daemon path keeps its per-module `client owner = yes` gate. --allow-super is rejected with --no-super or --daemon. C6: tls_client_identity_allowed now rejects a CN whose reported length reached the buffer bound, so a truncated over-long CN cannot be matched by a required --client-cn prefix. Tests: an integration regression proving --force cannot replace a destination directory without --allow-delete; standalone-default tests for --copy-as refusal and (root-only) skipped device creation; a CLI unit test for the new flag. The integration shared_server fixture opts in with --allow-super so the existing root-only ownership/device/copy-as tests continue to exercise the opted-in configuration. README and RSYNC_COMPAT document the flag and the force/delete gating.
This commit is contained in:
+39
-2
@@ -37,6 +37,12 @@ static bool allow_unauthenticated;
|
||||
* root), so no super-user activity is attempted and any client --copy-as is
|
||||
* refused. Set once in main before the accept loop / stdio handler. */
|
||||
static bool server_no_super;
|
||||
/* --allow-super: standalone/--stdio opt-in that preserves the historical
|
||||
* permissive super mode for a root receiver. When false, a privileged
|
||||
* standalone receiver forces SUPER_MODE_OFF for every connection (C3), so a
|
||||
* client cannot make it create device nodes / write raw devices / apply
|
||||
* client-chosen ownership. */
|
||||
static bool server_allow_super;
|
||||
static const char* required_client_cn;
|
||||
/* --iconv CONVERT_SPEC the server was itself started with (borrowed argv
|
||||
* pointer). Its LOCAL half may override the local charset the client assumed;
|
||||
@@ -191,8 +197,12 @@ static bool tls_client_identity_allowed(SSL* ssl) {
|
||||
int length = X509_NAME_get_text_by_NID(X509_get_subject_name(certificate), NID_commonName,
|
||||
common_name, sizeof(common_name));
|
||||
size_t required_length = strlen(required_client_cn);
|
||||
bool allowed = length >= 0 && (size_t)length == required_length &&
|
||||
required_length < sizeof(common_name) &&
|
||||
/* X509_NAME_get_text_by_NID truncates an over-long CN to the buffer size; a
|
||||
* returned length at the buffer bound means the CN was silently shortened, so
|
||||
* a required-name prefix could be matched by a longer CN with extra suffix.
|
||||
* Reject any result that reached the bound. */
|
||||
bool allowed = length >= 0 && (size_t)length < sizeof(common_name) - 1 &&
|
||||
(size_t)length == required_length && required_length < sizeof(common_name) &&
|
||||
credentials_secure_equal(common_name, required_client_cn, required_length);
|
||||
X509_free(certificate);
|
||||
return allowed;
|
||||
@@ -592,6 +602,20 @@ static const char* server_module_gate(const Config* config, void* context) {
|
||||
if (gate_ctx)
|
||||
gate_ctx->super_mode_override = SUPER_MODE_OFF;
|
||||
}
|
||||
/* C3: a privileged (root) STANDALONE/--stdio receiver defaults to
|
||||
* SUPER_MODE_OFF. Without this a client --devices/--write-devices/--super
|
||||
* would let a root server create arbitrary device nodes and write raw devices,
|
||||
* and client-chosen ownership (--numeric-ids/--chown/--usermap/--groupmap)
|
||||
* would be applied, with no operator opt-in. The operator must pass
|
||||
* --allow-super to restore the historical permissive behavior; an
|
||||
* unprivileged receiver is unaffected (the kernel refuses the confined
|
||||
* attempts) and the daemon path keeps its per-module `client owner = yes`
|
||||
* gate. */
|
||||
if (g_daemon_conf == NULL && geteuid() == 0 && !server_allow_super) {
|
||||
effective.super_mode = SUPER_MODE_OFF;
|
||||
if (gate_ctx)
|
||||
gate_ctx->super_mode_override = SUPER_MODE_OFF;
|
||||
}
|
||||
/* --copy-as (P7 Wave E, protocol 2.18.0): FastSync's safe subset forces the
|
||||
ownership of every written entry to the requested ids, which needs a
|
||||
privileged (root) receiver. An unprivileged receiver REFUSES the whole
|
||||
@@ -751,6 +775,13 @@ void handler(int file_descriptor) {
|
||||
goto done;
|
||||
}
|
||||
config->use_delete = config->use_delete && allow_delete;
|
||||
/* --force (receiver-side) is deletion authority too: it lets an incoming
|
||||
* regular file recursively remove a non-empty destination directory tree, and
|
||||
* lets --delete-missing-args remove a non-empty directory mirror. Without
|
||||
* the operator's --allow-delete it must be inert, exactly like --delete and
|
||||
* --delete-missing-args, so a client cannot use --force to bypass the delete
|
||||
* policy. */
|
||||
config->force_delete = config->force_delete && allow_delete;
|
||||
/* --iconv (protocol 2.16.0): install the receiver-side wire->local conversion
|
||||
now that the client's full CONVERT_SPEC has been received and validated,
|
||||
before any received file name is decoded. The server's own --iconv (if
|
||||
@@ -1010,6 +1041,11 @@ static void print_server_usage(void) {
|
||||
printf(" --no-super Operator veto: never attempt super-user activities\n");
|
||||
printf(" (ownership, device nodes) even as root, and refuse\n");
|
||||
printf(" any client --copy-as/--super request\n");
|
||||
printf(" --allow-super Standalone/--stdio only: keep super-user activities\n");
|
||||
printf(" enabled for a root receiver. Without it a root\n");
|
||||
printf(" standalone server forces SUPER_MODE_OFF, so client\n");
|
||||
printf(" --devices/--write-devices/--super and ownership\n");
|
||||
printf(" requests are refused/skipped. No effect when not root\n");
|
||||
printf(" --iconv=LOCAL[,REMOTE] Declare this server's LOCAL charset for file-name\n");
|
||||
printf(" conversion: received names are translated to this\n");
|
||||
printf(" charset (the wire charset still comes from the\n");
|
||||
@@ -1134,6 +1170,7 @@ int main(int argc, char* argv[]) {
|
||||
trust_sender = opts.trust_sender;
|
||||
allow_unauthenticated = opts.allow_unauthenticated;
|
||||
server_no_super = opts.no_super;
|
||||
server_allow_super = opts.allow_super;
|
||||
server_iconv_spec = opts.iconv_spec;
|
||||
signal(SIGINT, cleanup);
|
||||
signal(SIGTERM, cleanup);
|
||||
|
||||
@@ -179,6 +179,8 @@ int server_cli_parse(int argc, char* argv[], ServerCliOptions* opts, char* err,
|
||||
opts->trust_sender = true;
|
||||
} else if (arg_is(argv[i], "--no-super")) {
|
||||
opts->no_super = true;
|
||||
} else if (arg_is(argv[i], "--allow-super")) {
|
||||
opts->allow_super = true;
|
||||
} else if (arg_is(argv[i], "--allow-unauthenticated")) {
|
||||
opts->allow_unauthenticated = true;
|
||||
} else if (arg_has_value(argv[i], "--iconv", &inline_value)) {
|
||||
@@ -259,6 +261,16 @@ int server_cli_parse(int argc, char* argv[], ServerCliOptions* opts, char* err,
|
||||
set_error(err, err_size, "--hash-credentials cannot be combined with --daemon or --stdio");
|
||||
return -1;
|
||||
}
|
||||
if (opts->allow_super && opts->no_super) {
|
||||
set_error(err, err_size, "--allow-super and --no-super are mutually exclusive");
|
||||
return -1;
|
||||
}
|
||||
if (opts->allow_super && opts->daemon_mode) {
|
||||
set_error(err, err_size,
|
||||
"--allow-super is for a standalone/--stdio server; daemon modules opt in per "
|
||||
"module with 'client owner = yes'");
|
||||
return -1;
|
||||
}
|
||||
if (opts->hash_iterations_set && opts->hash_credentials_file == NULL) {
|
||||
set_error(err, err_size, "--iterations requires --hash-credentials");
|
||||
return -1;
|
||||
|
||||
@@ -45,6 +45,14 @@ typedef struct ServerCliOptions {
|
||||
* device-node creation) even when running as root. Applies to --stdio and
|
||||
* --daemon alike; also makes the server refuse any client --copy-as. */
|
||||
bool no_super; /* --no-super */
|
||||
/* --allow-super: standalone/--stdio only opt-in that keeps the historical
|
||||
* permissive behavior for a PRIVILEGED (root) receiver. Without it a root
|
||||
* standalone server forces SUPER_MODE_OFF, so a client --devices /
|
||||
* --write-devices / --super / ownership request cannot make it create device
|
||||
* nodes, write raw devices, or apply client-chosen ownership. Non-root
|
||||
* receivers are unaffected (the kernel refuses the confined attempts). The
|
||||
* daemon path instead uses the per-module `client owner = yes` opt-in. */
|
||||
bool allow_super; /* --allow-super */
|
||||
/* --iconv=CONVERT_SPEC: the server's own LOCAL charset declaration. The
|
||||
* client's full spec rides the wire config frame anyway; when the server is
|
||||
* started with its own --iconv, its LOCAL half overrides the local charset
|
||||
|
||||
Reference in New Issue
Block a user