fix: replace strcpy/strcat with bounded memory operations (#195)

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().
This commit is contained in:
2026-07-30 18:41:49 +02:00
parent e876055167
commit be9ec46ee2
3 changed files with 74 additions and 6 deletions
+15 -2
View File
@@ -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,
@@ -283,6 +286,9 @@ static int send_chunks_multithreaded(void* pipeline_context) {
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) {
+52
View File
@@ -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 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 {
+3
View File
@@ -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;