Reference in New Issue
Block a user
Delete Branch "fix/memory-safety"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Fixes 8 memory/null safety bugs:\n- #74: data_create_empty malloc(0) UB\n- #72: receive_str() no size limit DoS\n- #69: send_str() NULL segfault\n- #64: Scanner pattern ownership\n- #60: SSH argv overflow\n- #50: mkdir_r() buffer overflow\n- #49: chunk_size wire truncation\n- #65: Compression 32-bit truncation
=== CODE QUALITY REVIEW ===
Severity: MEDIUM
Unused variable
path2_pointerinpath_cat(src/shared/utils.c:168)char* path2_pointer = path2;is assigned, incremented at line 169 (path2_pointer += 1;), but never read. Dead code left from refactoring. The pointer arithmetic onpath2_pointerhas no effect.Inconsistent include style (src/shared/data.c:3)
Uses
#include "stdlib.h"(quotes, local include) instead of#include <stdlib.h>(angle brackets, system include). The PR fixed the same issue incompression.cbut not indata.c.Inconsistent parameter constness in
path_cat(src/shared/utils.c:161)path1isconst char*butpath2ischar*(mutable). The function modifiespath2pointer via local pointer arithmetic — should use a localconst char*variable instead.Wrong printf format specifier in
send_data(src/shared/protocol.c:181)%lldused forunsigned long long data_size; should be%llu.Wrong printf format specifier in
receive_data(src/shared/protocol.c:196)Same
%lldforunsigned long long— should be%llu.DRY violation — deep-copy logic duplication (src/client/scanner.c:28-78)
The deep-copy for
exclude_patterns(lines 28-48) andinclude_patterns(lines 52-78) is nearly identical. Could be refactored into a helper function (deep_copy_str_array) to reduce code duplication and error-prone cleanup paths.Long function / high cyclomatic complexity:
directory_scanner_next(src/client/scanner.c:135-224)89-line function with 4-5 levels of nesting. Handles directory iteration, readdir, stat, pattern matching, size filtering, and chunk accumulation — all in one function. Hard to follow and maintain.
str_dupusesstrcpyinstead ofmemcpy(src/shared/utils.c:66)Inconsistency with the rest of the PR which replaced
strcpywithmemcpyinmkdir_r. Usingmemcpyis more consistent (known string length).=== SECURITY REVIEW ===
Severity: MEDIUM (no critical vulnerabilities found)
Unbounded size in
receive_data(src/shared/protocol.c:185-197)malloc((size_t)size)wheresizeis anunsigned long longreceived from the network. On 32-bit systems (wheresize_tis 32-bit), a largeunsigned long longvalue truncates silently. While the truncated value is used consistently for bothmallocandreceive_n_data, this could enable memory exhaustion attacks. Consider adding a bounds check:if ((size_t)size != size) return NULL;and/or aMAX_DATA_SIZElimit.Path traversal via symlinks (src/client/scanner.c:160-167)
entry->d_nameis used withstat()(notlstat()), which follows symlinks. A symlink pointing outside the scanned directory (e.g.,../../../etc/passwd) would be followed and included in the chunk data. If the scanner is used with untrusted directories, this is a path traversal risk. Document this behavior or add validation.Valgrind detection reads partial
/proc/self/maps(tests/test_utils.h:12-21)Only reads the first 4095 bytes. If
vgpreloadappears later in the maps file (unlikely but possible), the detection produces a false negative. Consider usinggetenv("LD_PRELOAD")or valgrind.hRUNNING_ON_VALGRINDmacro instead.data_createfrees caller pointer on failure without documentation (src/shared/data.c:27-37)When
mallocfor theDatastruct fails,data_createcallsfree(data), consuming the caller's buffer. This is a "pass ownership" contract that is not documented. All current callers handle this correctly, but future callers may double-free if unaware.=== GENERAL REVIEW ===
Severity: LOW (logic is correct; minor issues)
Overall logic correctness: PASS
data_create_empty(0)fix correctly handlesmalloc(0)UB.receive_strsize cap (MAX_STRING_SIZE = 10MB) prevents unbounded allocation. Good security fix.transport_ssh.cdynamic argv array (32 slots vs hardcoded 16) with bounds checks prevents buffer overflow.mkdir_rreplacesstrcpywithmemcpyand bounds checks — eliminates buffer overflow.Thread safety concern: bwlimit state is not thread-safe (src/shared/protocol.c:15-17)
bw_tokensandbw_last_refillare global variables (not__thread). If multiple threads callsend_n_data/receive_n_dataconcurrently, the bandwidth limiter state will race. Pre-existing issue, not introduced by this PR.delete_extras_walkall_removed logic (src/shared/utils.c:106-155)Complex state tracking that is correct but fragile. The
rmdirwithENOENTignoring is correct, but an explanatory comment would improve maintainability.transport_ssh.c: no error message before_exit(1)on allocation failure (line 129-130)If
callocfor ssh_argv fails, the child process silently exits with_exit(1). No error message is printed, making debugging difficult.Test quality: GOOD
test_data_create_empty_zerocovers the malloc(0) fix.test_receive_str_oversizedcovers the MAX_STRING_SIZE cap./proc/self/mapsreplaces fragile environment variable check.=== VERDICT ===
PASS with recommendations
The PR correctly fixes several memory safety issues (malloc(0) UB, buffer overflow in mkdir_r, array bounds in ssh transport, missing NULL checks, unbounded string allocation). Code quality is generally good with clean style, consistent error handling, and proper resource cleanup on failure paths.
The issues found are minor (unused variable, wrong printf format specifiers, DRY duplication, include style inconsistency) and do not warrant rejection. I recommend addressing them in a follow-up cleanup.
Key recommendations to address:
path2_pointerinutils.c:168%lld-->%lluinprotocol.c:181,196#include "stdlib.h"-->#include <stdlib.h>indata.c:3receive_datafor 32-bit safetyRe-review: 5/6 FIXES VERIFIED, 1 NEEDS REWORK 🔶
Verified ✅
path2_pointerremoved (utils.c:158) — Variable removed,path2++used directly ✅#include <stdlib.h>with angle brackets ✅const char* path2(utils.c:161, utils.h:9) — Both declaration and prototype updated ✅%lluformat specifiers (protocol.c:181,196) — Bothsend_dataandreceive_datause%lluforunsigned long long✅lstat()for symlinks (scanner.c:162) —lstatreplacesstat✅Not Implemented ❌
Bounds check in
receive_data— The specific guardif ((size_t)size != size) return NULL;was not added. The function castsunsigned long long sizedirectly to(size_t)without verifying the value fits (lines 189, 192, 197). This is a portability concern on platforms wheresize_t < unsigned long long(e.g., 32-bit).Note: A
MAX_STRING_SIZEbounds check WAS added toreceive_str(protocol.h:9, protocol.c:158-162), but that is a different function.Verdict
APPROVED with one item requiring a follow-up. Fix #5 should be added to
receive_databefore merging, or explicitly documented as not applicable for the 64-bit-only target.Re-review #2: Fixes RE-VERIFIED ✅
All fixes confirmed:
CI: ✅ All green
Verdict: FULLY APPROVED
Pull request closed