Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,8 @@ endif()
# in the CRT / provided by MinGW's built-in runtime.
if(NOT WIN32)
target_link_libraries(GigaVector PRIVATE m pthread)
else()
target_link_libraries(GigaVector PRIVATE bcrypt)
endif()

# Set library properties
Expand Down
1 change: 1 addition & 0 deletions src/index/lsh.c
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ static uint32_t hash_vector(const float *data, size_t dimension, float **hyperpl

static int bucket_add(GV_LSHBucket *bucket, size_t index) {
if (bucket->count >= bucket->capacity) {
if (bucket->capacity > SIZE_MAX / 2 || (bucket->capacity > 0 && bucket->capacity * 2 > SIZE_MAX / sizeof(size_t))) return -1;
size_t new_capacity = bucket->capacity == 0 ? 8 : bucket->capacity * 2;
size_t *new_indices = (size_t *)realloc(bucket->indices, new_capacity * sizeof(size_t));
if (new_indices == NULL) {
Expand Down
38 changes: 23 additions & 15 deletions src/multimodal/bm25.c
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ static int add_posting(GV_PostingList *pl, size_t doc_id, size_t term_freq) {
}

if (pl->count >= pl->capacity) {
if (pl->capacity > SIZE_MAX / 2 || pl->capacity * 2 > SIZE_MAX / sizeof(GV_Posting)) return -1;
size_t new_capacity = pl->capacity * 2;
GV_Posting *new_postings = realloc(pl->postings, new_capacity * sizeof(GV_Posting));
if (!new_postings) return -1;
Expand Down Expand Up @@ -614,45 +615,52 @@ int bm25_save(const GV_BM25Index *index, const char *filepath) {

pthread_rwlock_rdlock((pthread_rwlock_t *)&index->rwlock);

#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)

const char magic[] = "GV_BM25";
fwrite(magic, 1, 7, fp);
BM25_FWRITE(magic, 1, 7);

uint32_t version = 1;
fwrite(&version, sizeof(version), 1, fp);
BM25_FWRITE(&version, sizeof(version), 1);

fwrite(&index->config.k1, sizeof(index->config.k1), 1, fp);
fwrite(&index->config.b, sizeof(index->config.b), 1, fp);
BM25_FWRITE(&index->config.k1, sizeof(index->config.k1), 1);
BM25_FWRITE(&index->config.b, sizeof(index->config.b), 1);

fwrite(&index->total_documents, sizeof(index->total_documents), 1, fp);
fwrite(&index->total_terms, sizeof(index->total_terms), 1, fp);
fwrite(&index->total_doc_length, sizeof(index->total_doc_length), 1, fp);
BM25_FWRITE(&index->total_documents, sizeof(index->total_documents), 1);
BM25_FWRITE(&index->total_terms, sizeof(index->total_terms), 1);
BM25_FWRITE(&index->total_doc_length, sizeof(index->total_doc_length), 1);

for (size_t i = 0; i < DOC_HASH_BUCKETS; i++) {
GV_DocInfo *di = index->doc_buckets[i];
while (di) {
fwrite(&di->doc_id, sizeof(di->doc_id), 1, fp);
fwrite(&di->doc_length, sizeof(di->doc_length), 1, fp);
BM25_FWRITE(&di->doc_id, sizeof(di->doc_id), 1);
BM25_FWRITE(&di->doc_length, sizeof(di->doc_length), 1);
di = di->next;
}
}

size_t sentinel = (size_t)-1;
fwrite(&sentinel, sizeof(sentinel), 1, fp);
BM25_FWRITE(&sentinel, sizeof(sentinel), 1);

for (size_t i = 0; i < TERM_HASH_BUCKETS; i++) {
GV_PostingList *pl = index->term_buckets[i];
while (pl) {
size_t term_len = strlen(pl->term);
fwrite(&term_len, sizeof(term_len), 1, fp);
fwrite(pl->term, 1, term_len, fp);
fwrite(&pl->count, sizeof(pl->count), 1, fp);
fwrite(pl->postings, sizeof(GV_Posting), pl->count, fp);
BM25_FWRITE(&term_len, sizeof(term_len), 1);
BM25_FWRITE(pl->term, 1, term_len);
BM25_FWRITE(&pl->count, sizeof(pl->count), 1);
if (pl->count > 0) BM25_FWRITE(pl->postings, sizeof(GV_Posting), pl->count);
pl = pl->next;
}
}

size_t zero = 0;
fwrite(&zero, sizeof(zero), 1, fp);
BM25_FWRITE(&zero, sizeof(zero), 1);

#undef BM25_FWRITE

pthread_rwlock_unlock((pthread_rwlock_t *)&index->rwlock);
fclose(fp);
Expand Down
1 change: 1 addition & 0 deletions src/multimodal/metadata_index.c
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ int metadata_index_add(GV_MetadataIndex *index, const char *key, const char *val
}

if (entry->count >= entry->capacity) {
if (entry->capacity > SIZE_MAX / 2 || entry->capacity * 2 > SIZE_MAX / sizeof(size_t)) return -1;
size_t new_capacity = entry->capacity * 2;
size_t *new_indices = (size_t *)realloc(entry->vector_indices, new_capacity * sizeof(size_t));
if (new_indices == NULL) {
Expand Down
106 changes: 46 additions & 60 deletions src/security/auth.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,12 @@
#include <stdio.h>
#include <time.h>
#include <pthread.h>
#if defined(__linux__)
#ifdef __linux__
#include <sys/random.h>
#elif defined(_WIN32)
#include <windows.h>
#include <bcrypt.h>
#pragma comment(lib, "bcrypt.lib")
#endif

/* Internal Structures */
Expand Down Expand Up @@ -185,20 +189,24 @@ void auth_to_hex(const unsigned char *hash, size_t hash_len, char *hex_out) {

/* Random Generation */

static void generate_random_bytes(unsigned char *buf, size_t len) {
#if defined(__linux__)
if (getrandom(buf, len, 0) == (ssize_t)len) return;
static int generate_random_bytes(unsigned char *buf, size_t len) {
#if defined(_WIN32)
NTSTATUS st = BCryptGenRandom(NULL, (PUCHAR)buf, (ULONG)len, BCRYPT_USE_SYSTEM_PREFERRED_RNG);
if (BCRYPT_SUCCESS(st)) return 0;
#elif defined(__linux__)
ssize_t r = getrandom(buf, len, 0);
if (r >= 0 && (size_t)r == len) return 0;
#endif
#if !defined(_WIN32)
FILE *fp = fopen("/dev/urandom", "rb");
if (fp) {
size_t nread = fread(buf, 1, len, fp);
size_t n = fread(buf, 1, len, fp);
fclose(fp);
if (nread == len) return;
}
fprintf(stderr, "GigaVector: WARNING: falling back to weak PRNG for key generation\n");
for (size_t i = 0; i < len; i++) {
buf[i] = (unsigned char)(rand() & 0xff);
if (n == len) return 0;
}
#endif
fprintf(stderr, "GigaVector auth: FATAL: could not obtain cryptographic randomness\n");
return -1;
}

/* Configuration */
Expand Down Expand Up @@ -248,15 +256,6 @@ void auth_destroy(GV_AuthManager *auth) {

/* API Key Management */

static int cmp_api_key_by_hash(const void *a, const void *b) {
return strcmp(((const APIKeyEntry *)a)->key_hash,
((const APIKeyEntry *)b)->key_hash);
}

static void auth_sort_keys(GV_AuthManager *auth) {
qsort(auth->keys, auth->key_count, sizeof(APIKeyEntry), cmp_api_key_by_hash);
}

int auth_generate_api_key(GV_AuthManager *auth, const char *description,
uint64_t expires_at, char *key_out, char *key_id_out) {
if (!auth || !key_out || !key_id_out) return -1;
Expand All @@ -271,8 +270,11 @@ int auth_generate_api_key(GV_AuthManager *auth, const char *description,
/* Generate random key ID and key */
unsigned char key_id_bytes[KEY_ID_LEN];
unsigned char key_bytes[KEY_LEN];
generate_random_bytes(key_id_bytes, KEY_ID_LEN);
generate_random_bytes(key_bytes, KEY_LEN);
if (generate_random_bytes(key_id_bytes, KEY_ID_LEN) != 0 ||
generate_random_bytes(key_bytes, KEY_LEN) != 0) {
pthread_rwlock_unlock(&auth->rwlock);
return -1;
}

/* Convert to hex */
auth_to_hex(key_id_bytes, KEY_ID_LEN, key_id_out);
Expand All @@ -292,7 +294,6 @@ int auth_generate_api_key(GV_AuthManager *auth, const char *description,
entry->enabled = 1;

auth->key_count++;
auth_sort_keys(auth);

pthread_rwlock_unlock(&auth->rwlock);
return 0;
Expand Down Expand Up @@ -411,37 +412,32 @@ GV_AuthResult auth_verify_api_key(GV_AuthManager *auth, const char *api_key,

uint64_t now = (uint64_t)time(NULL);

APIKeyEntry key;
memset(&key, 0, sizeof(key));
strncpy(key.key_hash, hash_hex, sizeof(key.key_hash) - 1);
key.key_hash[sizeof(key.key_hash) - 1] = '\0';
APIKeyEntry *found_entry = (APIKeyEntry *)bsearch(&key, auth->keys, auth->key_count,
sizeof(APIKeyEntry), cmp_api_key_by_hash);

if (!found_entry) {
pthread_rwlock_unlock(&auth->rwlock);
return GV_AUTH_INVALID_KEY;
}
for (size_t i = 0; i < auth->key_count; i++) {
if (strcmp(auth->keys[i].key_hash, hash_hex) == 0) {
if (!auth->keys[i].enabled) {
pthread_rwlock_unlock(&auth->rwlock);
return GV_AUTH_INVALID_KEY;
}
if (auth->keys[i].expires_at > 0 && auth->keys[i].expires_at < now) {
pthread_rwlock_unlock(&auth->rwlock);
return GV_AUTH_EXPIRED;
}

if (!found_entry->enabled) {
pthread_rwlock_unlock(&auth->rwlock);
return GV_AUTH_INVALID_KEY;
}
if (found_entry->expires_at > 0 && found_entry->expires_at < now) {
pthread_rwlock_unlock(&auth->rwlock);
return GV_AUTH_EXPIRED;
}
if (identity) {
memset(identity, 0, sizeof(*identity));
identity->key_id = gv_dup_cstr(auth->keys[i].key_id);
identity->subject = gv_dup_cstr(auth->keys[i].key_id);
identity->auth_time = now;
identity->expires_at = auth->keys[i].expires_at;
}

if (identity) {
memset(identity, 0, sizeof(*identity));
identity->key_id = gv_dup_cstr(found_entry->key_id);
identity->subject = gv_dup_cstr(found_entry->key_id);
identity->auth_time = now;
identity->expires_at = found_entry->expires_at;
pthread_rwlock_unlock(&auth->rwlock);
return GV_AUTH_SUCCESS;
}
}

pthread_rwlock_unlock(&auth->rwlock);
return GV_AUTH_SUCCESS;
return GV_AUTH_INVALID_KEY;
}

/* Base64 URL decoding for JWT verification */
Expand Down Expand Up @@ -706,24 +702,14 @@ int auth_generate_jwt(GV_AuthManager *auth, const char *subject,
char header_b64[128];
base64url_encode(header, strlen(header), header_b64);

/* Build payload — escape the subject so it can't break JSON structure */
/* Build payload */
uint64_t now = (uint64_t)time(NULL);
uint64_t exp = now + expires_in;

char escaped_subject[256];
size_t ei = 0;
for (size_t si = 0; subject[si]; si++) {
size_t need = (subject[si] == '"' || subject[si] == '\\') ? 2 : 1;
if (ei + need + 1 > sizeof(escaped_subject)) return -1;
if (need == 2) escaped_subject[ei++] = '\\';
escaped_subject[ei++] = subject[si];
}
escaped_subject[ei] = '\0';

char payload[512];
snprintf(payload, sizeof(payload),
"{\"sub\":\"%s\",\"iat\":%llu,\"exp\":%llu}",
escaped_subject, (unsigned long long)now, (unsigned long long)exp);
subject, (unsigned long long)now, (unsigned long long)exp);

char payload_b64[512];
base64url_encode(payload, strlen(payload), payload_b64);
Expand Down
43 changes: 29 additions & 14 deletions src/security/crypto.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#ifdef __linux__
#include <sys/random.h>
#elif defined(_WIN32)
#include <windows.h>
#include <bcrypt.h>
#pragma comment(lib, "bcrypt.lib")
#endif

/* Internal Structures */

Expand Down Expand Up @@ -240,17 +247,24 @@ static void aes256_decrypt_block(const unsigned char in[16], unsigned char out[1

/* Random Generation */

static void generate_random_bytes(unsigned char *buf, size_t len) {
static int generate_random_bytes(unsigned char *buf, size_t len) {
#if defined(_WIN32)
NTSTATUS st = BCryptGenRandom(NULL, (PUCHAR)buf, (ULONG)len, BCRYPT_USE_SYSTEM_PREFERRED_RNG);
if (BCRYPT_SUCCESS(st)) return 0;
#elif defined(__linux__)
ssize_t r = getrandom(buf, len, 0);
if (r >= 0 && (size_t)r == len) return 0;
#endif
#if !defined(_WIN32)
FILE *fp = fopen("/dev/urandom", "rb");
if (fp) {
size_t read = fread(buf, 1, len, fp);
size_t n = fread(buf, 1, len, fp);
fclose(fp);
if (read == len) return;
}
/* Fallback to weak random (not secure!) */
for (size_t i = 0; i < len; i++) {
buf[i] = (unsigned char)(rand() & 0xff);
if (n == len) return 0;
}
#endif
fprintf(stderr, "GigaVector crypto: FATAL: could not obtain cryptographic randomness\n");
return -1;
}

/* Configuration */
Expand Down Expand Up @@ -325,28 +339,29 @@ int crypto_derive_key(GV_CryptoContext *ctx, const char *password,
memcpy(dk, T, 32);

memcpy(key->key, dk, 32);
generate_random_bytes(key->iv, 16);
if (generate_random_bytes(key->iv, 16) != 0) return -1;

return 0;
}

int crypto_generate_key(GV_CryptoKey *key) {
Comment on lines 344 to 347
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 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;
}

if (!key) return -1;
generate_random_bytes(key->key, 32);
generate_random_bytes(key->iv, 16);
if (generate_random_bytes(key->key, 32) != 0) return -1;
if (generate_random_bytes(key->iv, 16) != 0) {
crypto_wipe_key(key);
return -1;
}
return 0;
}

int crypto_generate_iv(unsigned char *iv) {
if (!iv) return -1;
generate_random_bytes(iv, 16);
return 0;
return generate_random_bytes(iv, 16);
}

int crypto_generate_salt(unsigned char *salt, size_t salt_len) {
if (!salt || salt_len == 0) return -1;
generate_random_bytes(salt, salt_len);
return 0;
return generate_random_bytes(salt, salt_len);
}

void crypto_wipe_key(GV_CryptoKey *key) {
Expand Down
1 change: 1 addition & 0 deletions src/storage/database.c
Original file line number Diff line number Diff line change
Expand Up @@ -3448,6 +3448,7 @@ static int db_compact_soa_storage(GV_Database *db) {
}

size_t new_count = storage->count - deleted_count;
if (dimension == 0 || new_count > SIZE_MAX / dimension / sizeof(float)) return -1;
float *new_data = (float *)malloc(new_count * dimension * sizeof(float));
GV_Metadata **new_metadata = (GV_Metadata **)calloc(new_count, sizeof(GV_Metadata *));
int *new_deleted = (int *)calloc(new_count, sizeof(int));
Expand Down
14 changes: 10 additions & 4 deletions src/storage/memory_consolidation.c
Original file line number Diff line number Diff line change
Expand Up @@ -160,16 +160,22 @@ char *memory_merge(GV_MemoryLayer *layer, const char *memory_id_1,
return NULL;
}

merged_content[0] = '\0';
size_t pos = 0;
if (mem1.content) {
strcat(merged_content, mem1.content);
size_t l = strlen(mem1.content);
memcpy(merged_content + pos, mem1.content, l);
pos += l;
}
if (mem2.content) {
if (mem1.content) {
strcat(merged_content, ". ");
memcpy(merged_content + pos, ". ", 2);
pos += 2;
}
strcat(merged_content, mem2.content);
size_t l = strlen(mem2.content);
memcpy(merged_content + pos, mem2.content, l);
pos += l;
}
merged_content[pos] = '\0';

float *merged_embedding = (float *)malloc(layer->db->dimension * sizeof(float));
if (merged_embedding == NULL) {
Expand Down
Loading
Loading