From 5b5c7b2a7e2dc6704df2904129e22fee36248fb1 Mon Sep 17 00:00:00 2001 From: TapTap Date: Mon, 20 Jul 2026 21:26:08 +0200 Subject: [PATCH] =?UTF-8?q?fix:=20security=20issues=20=E2=80=94=20path=20t?= =?UTF-8?q?raversal,=20TLS=20hostname,=20SUID,=20OOM,=20stack=20overflow?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/shared/config.c | 19 +++++++++++++++++++ src/shared/file.c | 31 ++++++++++++++++++++++++++++++- src/shared/metadata.c | 2 +- src/shared/protocol.c | 6 ++++++ src/shared/transport_tcp.c | 6 ++++-- src/shared/transport_tls.c | 8 ++++++++ 6 files changed, 68 insertions(+), 4 deletions(-) diff --git a/src/shared/config.c b/src/shared/config.c index 352d609..b474768 100644 --- a/src/shared/config.c +++ b/src/shared/config.c @@ -4,6 +4,7 @@ #include "protocol.h" #include "utils.h" #include +#include #include #include #include @@ -247,10 +248,20 @@ Config* config_receive(int file_descriptor) { config->follow_symlinks = false; config->partial = false; +#define MAX_PATTERN_COUNT 10000 + // Receive exclude patterns int ec; if (!receive_int(file_descriptor, &ec)) goto error; + if (ec > MAX_PATTERN_COUNT) { + log_message(LOG_LEVEL_ERROR, "Exclude pattern count %d exceeds maximum %d", ec, MAX_PATTERN_COUNT); + goto error; + } + if ((size_t)ec > SIZE_MAX / sizeof(char*)) { + log_message(LOG_LEVEL_ERROR, "Exclude pattern count %d would cause integer overflow", ec); + goto error; + } config->exclude_count = ec; if (ec > 0) { config->exclude_patterns = malloc((size_t)ec * sizeof(char*)); @@ -275,6 +286,14 @@ Config* config_receive(int file_descriptor) { int ic; if (!receive_int(file_descriptor, &ic)) goto error; + if (ic > MAX_PATTERN_COUNT) { + log_message(LOG_LEVEL_ERROR, "Include pattern count %d exceeds maximum %d", ic, MAX_PATTERN_COUNT); + goto error; + } + if ((size_t)ic > SIZE_MAX / sizeof(char*)) { + log_message(LOG_LEVEL_ERROR, "Include pattern count %d would cause integer overflow", ic); + goto error; + } config->include_count = ic; if (ic > 0) { config->include_patterns = malloc((size_t)ic * sizeof(char*)); diff --git a/src/shared/file.c b/src/shared/file.c index 0cbd878..883c58c 100644 --- a/src/shared/file.c +++ b/src/shared/file.c @@ -126,7 +126,11 @@ static bool file_send_streaming(File* file, int file_descriptor) { return false; } - char buf[STREAM_CHUNK_SIZE]; + char* buf = malloc(STREAM_CHUNK_SIZE); + if (!buf) { + fclose(fp); + return false; + } unsigned long long remaining = total_size; while (remaining > 0) { size_t to_read = (size_t)((remaining < STREAM_CHUNK_SIZE) ? remaining : STREAM_CHUNK_SIZE); @@ -135,15 +139,18 @@ static bool file_send_streaming(File* file, int file_descriptor) { if (ferror(fp)) { perror("Read error during streaming"); } + free(buf); fclose(fp); return false; } if (!send_n_data(file_descriptor, buf, nread)) { + free(buf); fclose(fp); return false; } remaining -= (unsigned long long)nread; } + free(buf); fclose(fp); return true; } @@ -194,9 +201,19 @@ bool file_send_single_calls(File* file, int file_descriptor, bool use_metadata, bool file_save_to_disk(const char* root_directory, File* file) { if (file->type == FILE_TYPE_SYMLINK && file->link_target) { + // Validate link_target — reject absolute paths or traversal + if (file->link_target[0] == '/' || strstr(file->link_target, "..") != NULL) { + log_message(LOG_LEVEL_ERROR, "Path traversal blocked in symlink target: %s", file->link_target); + return false; + } char* disk_path = path_cat((char*)root_directory, file->path); if (disk_path == NULL) return false; + if (strstr(disk_path, "..") != NULL) { + log_message(LOG_LEVEL_ERROR, "Path traversal blocked: %s", disk_path); + free(disk_path); + return false; + } unlink(disk_path); bool ok = (symlink(file->link_target, disk_path) == 0); if (ok && file->metadata) @@ -208,6 +225,11 @@ bool file_save_to_disk(const char* root_directory, File* file) { char* disk_path = path_cat((char*)root_directory, file->path); if (disk_path == NULL) return false; + if (strstr(disk_path, "..") != NULL) { + log_message(LOG_LEVEL_ERROR, "Path traversal blocked: %s", disk_path); + free(disk_path); + return false; + } bool ok = to_disk(disk_path, file->data->data, file->data->size); if (ok) file_restore_metadata(disk_path, file->metadata); @@ -423,6 +445,13 @@ File* receive_incremental_check(int fd, const Config* config, bool* skipped) { } char* full_path = path_cat(config->receive_root_directory, check_path); + if (full_path && strstr(full_path, "..") != NULL) { + log_message(LOG_LEVEL_ERROR, "Path traversal blocked: %s", full_path); + free(full_path); + free(check_path); + send_status(fd, STATUS_ERROR); + return NULL; + } struct stat st; bool has_old_file = (full_path && stat(full_path, &st) == 0); unsigned long long old_size = has_old_file ? (unsigned long long)st.st_size : 0; diff --git a/src/shared/metadata.c b/src/shared/metadata.c index a7126dd..fb27ae1 100644 --- a/src/shared/metadata.c +++ b/src/shared/metadata.c @@ -150,7 +150,7 @@ FileMetadata* metadata_receive(int file_descriptor, int* ok) { void file_restore_metadata(const char* path, FileMetadata* metadata) { if (metadata == NULL) return; - if (chmod(path, metadata->mode & 07777) != 0) + if (chmod(path, metadata->mode & 07777 & ~(S_ISUID | S_ISGID)) != 0) log_message(LOG_LEVEL_WARNING, "Failed to chmod %s: %s", path, strerror(errno)); if (chown(path, metadata->uid, metadata->gid) != 0) log_message(LOG_LEVEL_WARNING, "Failed to chown %s: %s", path, strerror(errno)); diff --git a/src/shared/protocol.c b/src/shared/protocol.c index deaf84d..eb7eb9f 100644 --- a/src/shared/protocol.c +++ b/src/shared/protocol.c @@ -197,10 +197,16 @@ bool send_data(int file_descriptor, const Data* data) { return true; } +#define MAX_DATA_SIZE (1024ULL * 1024 * 1024) // 1 GB + Data* receive_data(int file_descriptor) { unsigned long long size = 0; if (!receive_n_data(file_descriptor, &size, sizeof(unsigned long long))) return NULL; + if (size > MAX_DATA_SIZE) { + log_message(LOG_LEVEL_ERROR, "receive_data: size %llu exceeds maximum", size); + return NULL; + } void* data = malloc((size_t)size); if (data == NULL) return NULL; diff --git a/src/shared/transport_tcp.c b/src/shared/transport_tcp.c index 1d4761d..0b27a55 100644 --- a/src/shared/transport_tcp.c +++ b/src/shared/transport_tcp.c @@ -161,8 +161,10 @@ static void accept_loop(Server* server, void (*child_fn)(int, void*), void* chil socklen_t client_len = sizeof(client_addr); int fd = accept(server->file_descriptor, (struct sockaddr*)&client_addr, &client_len); if (fd < 0) { - if (errno == EINTR) - break; + if (errno == EINTR) { + if (g_tcp_cleanup_requested) break; + continue; + } perror("Could not accept the connection"); continue; } diff --git a/src/shared/transport_tls.c b/src/shared/transport_tls.c index 27cba91..95bfd53 100644 --- a/src/shared/transport_tls.c +++ b/src/shared/transport_tls.c @@ -168,6 +168,14 @@ bool client_connect_tls(Client* client, char* host, int port, const char* cert_p client->ssl_ctx = NULL; return false; } + + // Set SNI and enable hostname verification + SSL_set_tlsext_host_name(ssl, host); + X509_VERIFY_PARAM *param = SSL_get0_param(ssl); + if (param) { + X509_VERIFY_PARAM_set1_host(param, host, 0); + } + client->ssl = ssl; io_set_ssl(ssl); return true;