fix: security issues — path traversal, TLS hostname, SUID, OOM, stack overflow
CI / lint (pull_request) Failing after 3s
CI / build-and-test (pull_request) Has been skipped
CI / sanitizers (address) (pull_request) Has been skipped
CI / sanitizers (undefined) (pull_request) Has been skipped
CI / fuzz-build (pull_request) Has been skipped
CI / coverage (pull_request) Has been skipped
CI / valgrind (pull_request) Has been skipped

This commit is contained in:
2026-07-20 21:24:47 +02:00
parent 9dd925a87d
commit 224e7e8599
6 changed files with 68 additions and 4 deletions
+19
View File
@@ -4,6 +4,7 @@
#include "protocol.h"
#include "utils.h"
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -246,10 +247,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*));
@@ -274,6 +285,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*));
+30 -1
View File
@@ -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;
+1 -1
View File
@@ -149,7 +149,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));
+6
View File
@@ -198,10 +198,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;
+4 -2
View File
@@ -164,8 +164,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;
}
+8
View File
@@ -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;