fix: security hardening across crypto, storage, and index#30
Conversation
|
| Filename | Overview |
|---|---|
| src/multimodal/bm25.c | Adds BM25_FWRITE macro for write-error detection and overflow guard in add_posting; macro correctly unlocks and closes on failure but leaves a partial file on disk (P1). |
| src/security/crypto.c | Replaces weak rand() fallback with getrandom//dev/urandom and hard-fails; partial key leakage on second RNG failure in crypto_generate_key is a minor concern. |
| src/security/auth.c | Mirrors crypto.c RNG hardening; return-value check propagated to callers correctly with lock released on failure. |
| src/storage/soa_storage.c | Adds stdint.h and overflow guards for capacity doubling and initial allocation; SIZE_MAX / dimension can divide by zero if dimension == 0. |
| src/storage/database.c | Adds overflow guard before compaction malloc; same dimension == 0 division-by-zero risk as soa_storage.c. |
| src/storage/memory_consolidation.c | Replaces strcat chains with explicit memcpy + position tracking; correct and safe. |
| src/multimodal/metadata_index.c | Adds overflow guard before capacity doubling in metadata_index_add; correct. |
| src/index/lsh.c | Adds overflow guard in bucket_add with a proper capacity > 0 check before the multiplication; correct. |
Reviews (1): Last reviewed commit: "fix: eliminate weak RNG, integer overflo..." | Re-trigger Greptile
| #define BM25_FWRITE(ptr, sz, n) \ | ||
| do { if (fwrite((ptr), (sz), (n), fp) != (n)) { \ | ||
| pthread_rwlock_unlock((pthread_rwlock_t *)&index->rwlock); \ | ||
| fclose(fp); return -1; } } while (0) |
There was a problem hiding this comment.
Partial file left on disk after write failure
When BM25_FWRITE detects a failed write, it closes the file and returns -1 but never unlinks it. A partial file at filepath is then left on disk; the next call to bm25_load will successfully open the file, read the corrupt header/body, and silently return bad state (or crash). The fix is to unlink the file before returning on error.
| #define BM25_FWRITE(ptr, sz, n) \ | |
| do { if (fwrite((ptr), (sz), (n), fp) != (n)) { \ | |
| pthread_rwlock_unlock((pthread_rwlock_t *)&index->rwlock); \ | |
| fclose(fp); return -1; } } while (0) | |
| #define BM25_FWRITE(ptr, sz, n) \ | |
| do { if (fwrite((ptr), (sz), (n), fp) != (n)) { \ | |
| pthread_rwlock_unlock((pthread_rwlock_t *)&index->rwlock); \ | |
| fclose(fp); remove(filepath); return -1; } } while (0) |
| storage->count = 0; | ||
| storage->capacity = (initial_capacity > 0) ? initial_capacity : 1024; | ||
|
|
||
| if (storage->capacity > SIZE_MAX / dimension / sizeof(float)) { |
There was a problem hiding this comment.
Division by zero when
dimension == 0
The new guard SIZE_MAX / dimension / sizeof(float) performs integer division with dimension before checking whether it is non-zero. If a caller passes dimension = 0, this is undefined behaviour. Add a dimension == 0 check before this guard (the same applies to the analogous check added in database.c).
| storage->count = 0; | |
| storage->capacity = (initial_capacity > 0) ? initial_capacity : 1024; | |
| if (storage->capacity > SIZE_MAX / dimension / sizeof(float)) { | |
| if (dimension == 0 || storage->capacity > SIZE_MAX / dimension / sizeof(float)) { | |
| free(storage); | |
| return NULL; | |
| } |
| return 0; | ||
| } | ||
|
|
||
| int crypto_generate_key(GV_CryptoKey *key) { |
There was a problem hiding this comment.
Partial key left in memory on second RNG failure
If generate_random_bytes(key->key, 32) succeeds but generate_random_bytes(key->iv, 16) fails, the function returns -1 with key->key populated with 32 bytes of live keying material and key->iv uninitialized. Callers that don't check the return value (or that log the struct on error) may inadvertently expose or misuse the partial key. Wiping the key before returning is safer:
if (generate_random_bytes(key->key, 32) != 0 ||
generate_random_bytes(key->iv, 16) != 0) {
crypto_wipe_key(key);
return -1;
}…NG fail, div-by-zero guard
03e316d to
1e5e928
Compare
Summary
rand()fallback withgetrandom//dev/urandominauth.candcrypto.c; fail hard instead of silently using insecure RNGcapacity * 2andcount * dimension * sizeof(float)insoa_storage.c,database.c,bm25.c,metadata_index.c,lsh.cfwritereturn values inbm25_saveviaBM25_FWRITEmacro; abort and clean up on partial writestrcatchains inmemory_consolidation.cwith explicitmemcpy+ length trackingTest plan
cmake -DBUILD_TESTS=ON -DENABLE_SANITIZERS=ON .. && ctestpasses with ASAN+UBSANcppcheck --enable=warning,performance,portability src/reports no new errors/dev/urandomis unavailable instead of producing predictable keys