fix(p5-remote-option): wire server --trust-sender, align save-layer gates, add hostile-sender test

This commit is contained in:
2026-09-09 14:23:59 +02:00
parent 5e79d7d76b
commit ad228db915
5 changed files with 218 additions and 10 deletions
+12 -5
View File
@@ -22,6 +22,7 @@
static char* authorized_root;
static int authorized_root_fd = -1;
static bool allow_delete;
static bool trust_sender;
static bool allow_unauthenticated;
static const char* required_client_cn;
@@ -221,11 +222,14 @@ void handler(int file_descriptor) {
across the per-connection forked processes). */
file_set_keep_dirlinks(config->keep_dirlinks);
/* --trust-sender is a LOCAL receiver policy: it never crosses the wire (so a
wire peer can never enable it), the receiving process applies it here from
its own config. Set before any multithreaded receiver/writer threads are
spawned so the fd-walk reads a stable value during the whole transfer, and
never bleeds across the per-connection forked processes. Off by default. */
file_set_trust_sender(config->trust_sender);
wire peer can never enable it). The standalone server only honours it when
its own CLI was started with --trust-sender (the client forwards that switch
into the remote argv via --remote-option=--trust-sender; the server then
parses it here and applies the policy below). Set before any multithreaded
receiver/writer threads are spawned so the fd-walk reads a stable value
during the whole transfer, and never bleeds across the per-connection
forked processes. Off by default. */
file_set_trust_sender(trust_sender);
if (config->use_multithreading) {
Queue* q = queue_create(100, file_destroy);
if (q == NULL) {
@@ -348,6 +352,7 @@ static void print_server_usage(void) {
printf(" --client-cn <name> Required TLS client certificate CN\n");
printf(" --destination-root <path> Authorized destination root (default: .)\n");
printf(" --allow-delete Permit manifest deletion\n");
printf(" --trust-sender Trust the remote sender's file list\n");
printf(" --allow-unauthenticated Allow plaintext/anonymous network clients\n");
printf(" -v, --verbose Enable debug logging\n");
printf(" --help Show this help\n");
@@ -384,6 +389,8 @@ int main(int argc, char* argv[]) {
destination_root = argv[++i];
} else if (strcmp(argv[i], "--allow-delete") == 0) {
allow_delete = true;
} else if (strcmp(argv[i], "--trust-sender") == 0) {
trust_sender = true;
} else if (strcmp(argv[i], "--allow-unauthenticated") == 0) {
allow_unauthenticated = true;
} else if (strcmp(argv[i], "-p") == 0 && i + 1 < argc) {
+10 -5
View File
@@ -329,8 +329,12 @@ bool file_special_rdev_valid(int32_t major, int32_t minor, mode_t mode) {
*/
static FileSaveResult file_save_special_to_disk(const char* root_directory, const File* file,
const Config* config) {
/* The empty-path and structural checks stay unconditional; the redundant
".." list-path re-check is skipped under --trust-sender exactly like the
receive layer (confinement is deferred to the secure parent walk below,
which is never disabled). */
if (!root_directory || !file || !file->path || file->path[0] == '\0' ||
has_path_traversal(file->path) || !file->metadata)
(!file_get_trust_sender() && has_path_traversal(file->path)) || !file->metadata)
return FILE_SAVE_ERROR;
mode_t mode = file->metadata->mode;
@@ -461,7 +465,7 @@ static FileSaveResult file_save_special_to_disk(const char* root_directory, cons
* entry is skipped), never aborts. */
static FileSaveResult file_save_write_device(const char* root_directory, const File* file) {
if (!root_directory || !file || !file->path || file->path[0] == '\0' ||
has_path_traversal(file->path))
(!file_get_trust_sender() && has_path_traversal(file->path)))
return FILE_SAVE_ERROR;
if (!file->data)
return FILE_SAVE_ERROR;
@@ -538,7 +542,7 @@ FileSaveResult file_save_to_disk_full(const char* root_directory, const File* fi
char *backup_path = NULL, *parent_copy = NULL;
if (!file || !file->path || !file->data || (file->data->size != 0 && !file->data->data) ||
has_path_traversal(file->path) ||
(!file_get_trust_sender() && has_path_traversal(file->path)) ||
(backup_enabled &&
(!backup_suffix || backup_suffix[0] == '\0' || strchr(backup_suffix, '/') != NULL ||
strcmp(backup_suffix, ".") == 0 || strcmp(backup_suffix, "..") == 0))) {
@@ -560,7 +564,7 @@ FileSaveResult file_save_to_disk_full(const char* root_directory, const File* fi
immediately (they are never staged by --delay-updates, matching rsync,
where directory creation is not delayed). */
if (file->is_dir) {
if (file->path[0] == '\0' || has_path_traversal(file->path)) {
if (file->path[0] == '\0' || (!file_get_trust_sender() && has_path_traversal(file->path))) {
log_message(LOG_LEVEL_ERROR, "Invalid directory path received");
return FILE_SAVE_ERROR;
}
@@ -577,7 +581,8 @@ FileSaveResult file_save_to_disk_full(const char* root_directory, const File* fi
threads start, so it is stable throughout this walk.) */
if (file->is_symlink) {
if (!file->symlink_target || file->path[0] == '\0' || has_path_traversal(file->path)) {
if (!file->symlink_target || file->path[0] == '\0' ||
(!file_get_trust_sender() && has_path_traversal(file->path))) {
log_message(LOG_LEVEL_ERROR, "Invalid symlink entry received");
return FILE_SAVE_ERROR;
}