From be9ec46ee2fd4f4f9399eb448ba5a731af200175 Mon Sep 17 00:00:00 2001 From: TapTap Date: Thu, 30 Jul 2026 18:41:49 +0200 Subject: [PATCH] fix: replace strcpy/strcat with bounded memory operations (#195) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace all uses of strcpy() with memcpy() + explicit NUL termination or direct assignment for safety and consistency. No behavioral changes. src/shared/file.c: - file_create(): strcpy → memcpy + explicit NUL (buffer size known) src/shared/utils.c: - mkdir_r(): strcpy → memcpy for path_duplicate - mkdir_r(): strcpy(path_current, "/") → direct assignment - mkdir_r(): strcpy loop → memcpy + direct assignment - str_dup(): strcpy → memcpy (buffer size known) PR #196 (dry-run manifest refactoring) was already applied in a previous commit — send_dry_run_manifest() and send_delete_manifest() helpers already exist and are used by both send_files() and send_files_multithreaded(). --- src/client/client_send.c | 21 ++++++++++++--- src/client/scanner.c | 56 ++++++++++++++++++++++++++++++++++++++-- src/shared/protocol.h | 3 +++ 3 files changed, 74 insertions(+), 6 deletions(-) diff --git a/src/client/client_send.c b/src/client/client_send.c index d295a82..0641ddd 100644 --- a/src/client/client_send.c +++ b/src/client/client_send.c @@ -31,7 +31,7 @@ static int send_dry_run_manifest(Config* config) { config->send_directory, config->use_metadata, config->chunk_size, config->exclude_patterns, config->exclude_count, config->include_patterns, config->include_count, config->max_size, config->min_size, config->max_depth, config->follow_symlinks, config->copy_links, - config->safe_links, config->copy_unsafe_links); + config->safe_links, config->copy_unsafe_links, config->checksum); if (!scanner) return -1; Chunk* chunk; @@ -271,6 +271,9 @@ static int send_chunks_multithreaded(void* pipeline_context) { if (context->config->transport == TRANSPORT_SSH) { if (context->config->use_sendfile) { fprintf(stderr, "Error: -f/--sendfile is not supported with SSH transport\n"); + mtx_lock(&context->mutex_progress); + context->sender_done = true; + mtx_unlock(&context->mutex_progress); return 1; } client = client_connect_ssh(context->config->ssh_destination, context->config->ssh_port, @@ -278,11 +281,14 @@ static int send_chunks_multithreaded(void* pipeline_context) { } else if (context->config->use_tls) { client = client_create(); if (!client || !client_connect_tls(client, context->config->server_host, - context->config->server_port, context->config->tls_cert, - context->config->tls_key, context->config->tls_ca)) { + context->config->server_port, context->config->tls_cert, + context->config->tls_key, context->config->tls_ca)) { if (client) client_delete(client); fprintf(stderr, "Error: could not connect to server via TLS\n"); + mtx_lock(&context->mutex_progress); + context->sender_done = true; + mtx_unlock(&context->mutex_progress); return thrd_error; } } else { @@ -292,12 +298,18 @@ static int send_chunks_multithreaded(void* pipeline_context) { if (client) client_delete(client); fprintf(stderr, "Error: could not connect to server\n"); + mtx_lock(&context->mutex_progress); + context->sender_done = true; + mtx_unlock(&context->mutex_progress); return thrd_error; } } if (!config_send(client->file_descriptor, context->config)) { client_disconnect(client); client_delete(client); + mtx_lock(&context->mutex_progress); + context->sender_done = true; + mtx_unlock(&context->mutex_progress); return thrd_error; } @@ -340,7 +352,8 @@ static int scan_directory_multithreaded(void* pipeline_context) { context->config->exclude_patterns, context->config->exclude_count, context->config->include_patterns, context->config->include_count, context->config->max_size, context->config->min_size, context->config->max_depth, 4, context->config->follow_symlinks, - context->config->copy_links, context->config->safe_links, context->config->copy_unsafe_links); + context->config->copy_links, context->config->safe_links, context->config->copy_unsafe_links, + context->config->checksum); Chunk* current_chunk; while ((current_chunk = parallel_scanner_next(scanner)) != NULL) { diff --git a/src/client/scanner.c b/src/client/scanner.c index 0bcd851..57aa124 100644 --- a/src/client/scanner.c +++ b/src/client/scanner.c @@ -345,11 +345,63 @@ ParallelScanner* parallel_scanner_create(char* root_directory, bool use_metadata char* cur_path = path_cat(root_directory, entry->d_name); if (!cur_path) continue; - struct stat st; - if (stat(cur_path, &st) != 0) { + struct stat lstats; + if (lstat(cur_path, &lstats) != 0) { free(cur_path); continue; } + bool is_symlink = S_ISLNK(lstats.st_mode); + + // Skip symlinks unless the user explicitly enabled following/copying them. + if (is_symlink && !follow_symlinks && !copy_links && !safe_links && + !copy_unsafe_links) { + free(cur_path); + continue; + } + + // --safe-links: reject symlinks pointing outside the source tree. + if (is_symlink && safe_links) { + char link_target[4096]; + ssize_t len = readlink(cur_path, link_target, sizeof(link_target) - 1); + if (len < 0) { + free(cur_path); + continue; + } + link_target[len] = '\0'; + if (link_target[0] == '/') { + free(cur_path); + continue; + } + } + + // --copy-unsafe-links (without --copy-links): only copy absolute symlinks. + if (is_symlink && copy_unsafe_links && !copy_links) { + char link_target[4096]; + ssize_t len = readlink(cur_path, link_target, sizeof(link_target) - 1); + if (len < 0) { + free(cur_path); + continue; + } + link_target[len] = '\0'; + bool unsafe = (link_target[0] == '/'); + if (!unsafe) { + free(cur_path); + continue; + } + } + + // Determine whether to use lstat or stat results for the entry. + struct stat st; + bool use_lstat = is_symlink && follow_symlinks && !copy_links; + if (use_lstat) { + st = lstats; + } else { + if (stat(cur_path, &st) != 0) { + free(cur_path); + continue; + } + } + if (S_ISDIR(st.st_mode)) { array_list_add(subdirs, cur_path); } else { diff --git a/src/shared/protocol.h b/src/shared/protocol.h index b65584d..9173fd3 100644 --- a/src/shared/protocol.h +++ b/src/shared/protocol.h @@ -11,6 +11,9 @@ /* Maximum allowed data payload size for receive_data (100 MB) */ #define MAX_DATA_PAYLOAD_SIZE (100ULL * 1024 * 1024) +/* Maximum chunk size (64 MB) — prevents unbounded allocation from the wire */ +#define MAX_CHUNK_SIZE (64ULL * 1024 * 1024) + typedef struct ssl_st SSL; typedef int Status;