Merge branch 'feat/p7-copy-as' into feat/p7-privilege
# Conflicts: # RSYNC_COMPAT.md # src/shared/config.c # src/shared/config.h # src/shared/identity.c # tests/integration/test_preflight.py # tests/test_client_cli.c # tests/test_config.c
This commit is contained in:
+93
-6
@@ -1707,6 +1707,51 @@ static void test_config_super_mode_wire_roundtrip() {
|
||||
}
|
||||
}
|
||||
|
||||
/* --copy-as (P7 Wave E, protocol 2.18.0) travels as a trailing config-frame
|
||||
block: a presence int, then the two int32 ids when set. */
|
||||
static void test_config_copy_as_wire_roundtrip() {
|
||||
struct {
|
||||
bool set;
|
||||
int32_t uid;
|
||||
int32_t gid;
|
||||
} cases[] = {{false, 0, 0}, {true, 1000, 1001}};
|
||||
if (is_running_under_valgrind())
|
||||
return;
|
||||
for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) {
|
||||
int p[2];
|
||||
EXPECT_EQ_INT(socketpair(AF_UNIX, SOCK_STREAM, 0, p), 0);
|
||||
pid_t pid = fork();
|
||||
if (pid == 0) {
|
||||
close(p[1]);
|
||||
io_set_fds(p[0], p[0]);
|
||||
Config* recv = config_receive(p[0]);
|
||||
bool ok = recv != NULL && recv->copy_as_set == cases[i].set &&
|
||||
(!cases[i].set ||
|
||||
(recv->copy_as_uid == cases[i].uid && recv->copy_as_gid == cases[i].gid));
|
||||
config_delete(recv);
|
||||
close(p[0]);
|
||||
_exit(ok ? 0 : 1);
|
||||
} else {
|
||||
close(p[0]);
|
||||
io_set_fds(p[1], p[1]);
|
||||
Config* send_cfg = config_create();
|
||||
EXPECT_NOT_NULL(send_cfg);
|
||||
send_cfg->send_directory = str_dup("/src");
|
||||
send_cfg->receive_root_directory = str_dup("/dst");
|
||||
send_cfg->copy_as_set = cases[i].set;
|
||||
send_cfg->copy_as_uid = cases[i].uid;
|
||||
send_cfg->copy_as_gid = cases[i].gid;
|
||||
bool sent = config_send(p[1], send_cfg);
|
||||
int status;
|
||||
waitpid(pid, &status, 0);
|
||||
close(p[1]);
|
||||
config_delete(send_cfg);
|
||||
EXPECT_TRUE(sent);
|
||||
EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* An out-of-range super_mode value on the wire must be refused on receive
|
||||
(never silently clamped or accepted). */
|
||||
static void test_config_receive_rejects_invalid_super_mode() {
|
||||
@@ -1730,9 +1775,49 @@ static void test_config_receive_rejects_invalid_super_mode() {
|
||||
config_delete(c);
|
||||
}
|
||||
|
||||
/* A hostile peer must not smuggle a negative (sentinel) copy-as id into the
|
||||
ownership path: the receive side rejects it and the run fails the handshake. */
|
||||
static void test_config_receive_rejects_negative_copy_as() {
|
||||
if (is_running_under_valgrind())
|
||||
return;
|
||||
Config* send_cfg = config_create();
|
||||
EXPECT_NOT_NULL(send_cfg);
|
||||
send_cfg->send_directory = str_dup("/src");
|
||||
send_cfg->receive_root_directory = str_dup("/dst");
|
||||
send_cfg->copy_as_set = true;
|
||||
send_cfg->copy_as_uid = -1;
|
||||
send_cfg->copy_as_gid = 0;
|
||||
|
||||
int p[2];
|
||||
EXPECT_EQ_INT(socketpair(AF_UNIX, SOCK_STREAM, 0, p), 0);
|
||||
io_set_fds(p[0], p[1]);
|
||||
io_set_bwlimit(0);
|
||||
|
||||
pid_t pid = fork();
|
||||
if (pid == 0) {
|
||||
close(p[1]);
|
||||
io_set_fds(p[0], p[0]);
|
||||
Config* recv_cfg = config_receive(p[0]);
|
||||
config_delete(recv_cfg);
|
||||
close(p[0]);
|
||||
_exit(recv_cfg ? 1 : 0);
|
||||
} else {
|
||||
close(p[0]);
|
||||
io_set_fds(p[1], p[1]);
|
||||
bool sent = config_send(p[1], send_cfg);
|
||||
int status;
|
||||
waitpid(pid, &status, 0);
|
||||
close(p[1]);
|
||||
config_delete(send_cfg);
|
||||
EXPECT_FALSE(sent);
|
||||
EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0);
|
||||
}
|
||||
}
|
||||
|
||||
/* P7 Wave E: privilege_super_permitted() maps the super_mode tri-state. OFF
|
||||
forbids super-user activities even for root; ON permits them; AUTO follows
|
||||
the effective uid. */
|
||||
forbids super-user activities even for root; ON and AUTO permit the confined
|
||||
attempt (matching FastSync's historical best-effort behavior, where the kernel
|
||||
refuses an unprivileged attempt and the caller skips it). */
|
||||
static void test_privilege_super_permitted_modes() {
|
||||
Config* c = config_create();
|
||||
EXPECT_NOT_NULL(c);
|
||||
@@ -1744,13 +1829,13 @@ static void test_privilege_super_permitted_modes() {
|
||||
EXPECT_TRUE(privilege_super_permitted());
|
||||
c->super_mode = SUPER_MODE_AUTO;
|
||||
identity_set_active(c);
|
||||
EXPECT_EQ_INT(privilege_super_permitted() ? 1 : 0, geteuid() == 0 ? 1 : 0);
|
||||
EXPECT_TRUE(privilege_super_permitted());
|
||||
config_delete(c);
|
||||
|
||||
/* After clearing, the neutral default is AUTO (root-following), never a
|
||||
stale snapshot from a previous connection. */
|
||||
/* After clearing, the neutral default is AUTO (attempt), never a stale
|
||||
snapshot from a previous connection. */
|
||||
identity_clear_active();
|
||||
EXPECT_EQ_INT(privilege_super_permitted() ? 1 : 0, geteuid() == 0 ? 1 : 0);
|
||||
EXPECT_TRUE(privilege_super_permitted());
|
||||
}
|
||||
|
||||
void test_config() {
|
||||
@@ -1801,6 +1886,8 @@ void test_config() {
|
||||
test_config_receive_rejects_invalid_iconv_spec();
|
||||
test_config_super_mode_wire_roundtrip();
|
||||
test_config_receive_rejects_invalid_super_mode();
|
||||
test_config_copy_as_wire_roundtrip();
|
||||
test_config_receive_rejects_negative_copy_as();
|
||||
test_config_receive_with_validate_rejects();
|
||||
}
|
||||
test_privilege_super_permitted_modes();
|
||||
|
||||
Reference in New Issue
Block a user