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:
2026-09-14 16:09:44 +02:00
parent 80c1ff321c
commit 9da5a0a9ed
8 changed files with 180 additions and 12 deletions
+23
View File
@@ -32,6 +32,28 @@ static void test_server_cli_defaults() {
EXPECT_FALSE(opts.allow_delete);
EXPECT_FALSE(opts.allow_unauthenticated);
EXPECT_FALSE(opts.no_super);
EXPECT_FALSE(opts.allow_super);
server_cli_options_free(&opts);
}
/* C3: --allow-super is the standalone/--stdio opt-in for a privileged receiver;
* it never combines with --no-super, and daemon modules use their own per-module
* `client owner = yes` opt-in instead. */
static void test_server_cli_allow_super() {
const char* args[] = {"fastsync-server", "--allow-super", "--destination-root", "/srv"};
ServerCliOptions opts;
EXPECT_EQ_INT(parse_ok(args, 4, &opts), 0);
EXPECT_TRUE(opts.allow_super);
server_cli_options_free(&opts);
char err[256];
const char* a1[] = {"s", "--allow-super", "--no-super"};
EXPECT_EQ_INT(server_cli_parse(3, (char**)a1, &opts, err, sizeof(err)), -1);
EXPECT_TRUE(strstr(err, "mutually exclusive") != NULL);
const char* a2[] = {"s", "--daemon", "--config=/tmp/x.conf", "--allow-super"};
EXPECT_EQ_INT(server_cli_parse(4, (char**)a2, &opts, err, sizeof(err)), -1);
EXPECT_TRUE(strstr(err, "client owner") != NULL);
server_cli_options_free(&opts);
}
@@ -224,5 +246,6 @@ void test_server_cli() {
test_server_cli_password_and_early_input();
test_server_cli_password_requires_daemon();
test_server_cli_no_super();
test_server_cli_allow_super();
test_server_cli_help();
}