fix(parity): receiver temp-dir confinement, server I/O floor, delete budget
Address review findings on feat/rsync-parity: - confine --temp-dir below the receive root (reject absolute/.. like backup-dir/partial-dir); keep EXDEV non-atomic fallback - floor server session I/O deadlines at SERVER_IO_TIMEOUT_SEC (60s) and install it on the socket layer at startup (slow-loris) - charge each --delete-missing-args directory removal once and clamp the extras-walk remaining budget so it can never underflow past --max-delete - normalize --compress-choice=auto to zstd client-side and accept it on receive so auto transfers no longer fail - map received --max-alloc=0 to MAX_SERVER_ALLOC (receive path only) - zero File.dest_state; include log-file-format in report_dest_info; add STATUS_DELETE_LIMIT name; recognize --skip-compress as a separate-value option; OOM-guard send_list_only root entry; drop the dead -M= branch; record the bare relative protected prefix for -R size-prunes in both scanners; refresh delete-manifest comment - pin the rsync tarball sha256 and bump integrator image to v11 Tests: temp-dir rejection/relative/cross-device, server timeout floor, delete-missing dir budget regression, compress-choice=auto e2e, max-alloc=0 receive mapping, dest_state, report_dest_info modes, skip-compress dash value, -M short forms, -R root size-prune mirror protection (rsync 3.4.1 confirmed).
This commit is contained in:
+22
-8
@@ -46,9 +46,11 @@ static void config_set_defaults(Config* config) {
|
||||
config->server_port_set = false;
|
||||
config->server_host_set = false;
|
||||
/* rsync defaults: --timeout=0 (I/O timeouts disabled) and --contimeout=60.
|
||||
* A value of 0 disables the deadline on both the socket layer
|
||||
* A value of 0 disables the client's own deadline on both the socket layer
|
||||
* (tcp_set_timeouts) and the protocol layer
|
||||
* (protocol_session_set_io_timeout); a positive value sets it. */
|
||||
* (protocol_session_set_io_timeout); a positive value sets it. A server
|
||||
* session floors the deadline at SERVER_IO_TIMEOUT_SEC so 0 can never hold a
|
||||
* connection open forever. */
|
||||
config->timeout = 0;
|
||||
config->contimeout = 60;
|
||||
config->quiet = false;
|
||||
@@ -207,7 +209,8 @@ static bool validate_received_config(const Config* config) {
|
||||
config->delta_block_size >= DELTA_BLOCK_SIZE_MIN &&
|
||||
config->delta_block_size <= DELTA_BLOCK_SIZE_MAX &&
|
||||
config->delta_max_file_size <= DELTA_MAX_FILE_SIZE && config->modify_window >= 0 &&
|
||||
config->max_delete >= -1 && config->skip_compress_count >= 0 &&
|
||||
config->max_delete >= -1 && config->max_alloc <= MAX_SERVER_ALLOC &&
|
||||
config->skip_compress_count >= 0 &&
|
||||
config->skip_compress_count <= MAX_SKIP_COMPRESS_SUFFIXES &&
|
||||
(!config->chmod_spec || !*config->chmod_spec ||
|
||||
chmod_apply(0, config->chmod_spec, &(mode_t){0})) &&
|
||||
@@ -788,13 +791,14 @@ void config_delete(Config* config) {
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/* --max-alloc: raw 64-bit value, clamped server-side and installed as the
|
||||
* session allocation ceiling. Zero means "no alloc limit" (rsync's
|
||||
* --max-alloc=0) and is passed through; a non-zero value is clamped to the
|
||||
* server's own ceiling. */
|
||||
* session allocation ceiling. A received 0 is rsync's "no alloc limit"; on the
|
||||
* receive path it is mapped to the server ceiling so a client can never disable
|
||||
* it (client-side 0 remains unlimited). Any value above the ceiling is clamped
|
||||
* to it. */
|
||||
static bool config_receive_max_alloc(int fd, unsigned long long* value) {
|
||||
if (!receive_n_data(fd, value, sizeof(*value)))
|
||||
return false;
|
||||
if (*value > MAX_SERVER_ALLOC)
|
||||
if (*value == 0 || *value > MAX_SERVER_ALLOC)
|
||||
*value = MAX_SERVER_ALLOC;
|
||||
protocol_session_set_max_alloc(NULL, *value);
|
||||
return true;
|
||||
@@ -1362,7 +1366,8 @@ Config* config_receive_with_validate(int file_descriptor, ConfigValidateFunc val
|
||||
!receive_output_options(file_descriptor, config, &budget))
|
||||
goto error;
|
||||
if (config->compress_choice[0] != '\0' && strcmp(config->compress_choice, "zstd") != 0 &&
|
||||
strcmp(config->compress_choice, "none") != 0) {
|
||||
strcmp(config->compress_choice, "none") != 0 &&
|
||||
strcmp(config->compress_choice, "auto") != 0) {
|
||||
char* escaped_choice = output_escape(config->compress_choice, config->eight_bit_output);
|
||||
log_message(LOG_LEVEL_ERROR, "Unsupported compression choice: %s",
|
||||
escaped_choice ? escaped_choice : "<allocation failed>");
|
||||
@@ -1373,6 +1378,15 @@ Config* config_receive_with_validate(int file_descriptor, ConfigValidateFunc val
|
||||
free(escaped_choice);
|
||||
goto error;
|
||||
}
|
||||
/* Defensive: an older/hostile client may still send "auto"; canonicalize it
|
||||
to zstd (its effective choice) so the stored value is always concrete. */
|
||||
if (strcmp(config->compress_choice, "auto") == 0) {
|
||||
char* canonical = str_dup("zstd");
|
||||
if (!canonical)
|
||||
goto error;
|
||||
free(config->compress_choice);
|
||||
config->compress_choice = canonical;
|
||||
}
|
||||
if (!validate_received_config(config)) {
|
||||
log_message(LOG_LEVEL_ERROR, "Invalid configuration received from client");
|
||||
send_error_detail(file_descriptor, "invalid configuration received from client");
|
||||
|
||||
Reference in New Issue
Block a user