You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Fix 35 bugs across CloudSync SQLite/PostgreSQL sync extension
Comprehensive audit identified and fixed 35 bugs (1 CRITICAL, 7 HIGH,
18 MEDIUM, 9 LOW) across the entire codebase. All 84 SQLite tests and
26 PostgreSQL tests pass with 0 failures and 0 memory leaks.
## src/cloudsync.c (13 fixes)
- [HIGH] Guard NULL db_version_stmt in cloudsync_dbversion_rerun —
set db_version = CLOUDSYNC_MIN_DB_VERSION and return 0 when stmt
is NULL, preventing NULL dereference after schema rebuild failure
- [MEDIUM] Add early return for NULL stmt in dbvm_execute to prevent
crash when called with uninitialized statement pointer
- [MEDIUM] Change (bool)dbvm_count() to (dbvm_count() > 0) in
table_pk_exists — prevents negative return values being cast to
true, giving false positive "pk exists" results
- [MEDIUM] Add NULL check on database_column_text result in
cloudsync_refill_metatable before calling strlen — prevents crash
on corrupted or empty column data
- [MEDIUM] Route early returns in cloudsync_payload_apply through
goto cleanup so CLEANUP callback and vm finalize always run —
prevents resource leaks and callback contract violation
- [MEDIUM] Change return false to goto abort_add_table when
ROWIDONLY rejected — ensures table_free runs on the partially
allocated table, preventing memory leak
- [MEDIUM] Initialize *persistent = false at top of
cloudsync_colvalue_stmt — prevents use of uninitialized value
when table_lookup returns NULL
- [LOW] Add NULL check on database_column_blob in merge_did_cid_win
— prevents memcmp with NULL pointer on corrupted cloudsync table
- [LOW] Handle partial failure in table_add_to_context_cb — clean
up col_name, col_merge_stmt, col_value_stmt at index on error
instead of leaving dangling pointers
- [LOW] Remove unused pragma_checked field from cloudsync_context
- [LOW] Change pointer comparison to strcmp in cloudsync_set_schema
— pointer equality missed cases where different string pointers
had identical content
- [LOW] Fix cloudsync_payload_get NULL check: blob == NULL (always
false for char** arg) changed to *blob == NULL
- [LOW] Pass extra meta_ref args to SQL_CLOUDSYNC_UPSERT_COL_INIT_OR_BUMP_VERSION
mprintf call to match updated PostgreSQL format string
## src/sqlite/cloudsync_sqlite.c (5 fixes)
- [HIGH] Split DEFAULT_FLAGS into FLAGS_PURE (SQLITE_UTF8 |
SQLITE_INNOCUOUS | SQLITE_DETERMINISTIC) and FLAGS_VOLATILE
(SQLITE_UTF8). Pure functions: cloudsync_version, cloudsync_pk_encode,
cloudsync_pk_decode. All others volatile — fixes cloudsync_uuid()
returning identical values within the same query when SQLite cached
deterministic results
- [HIGH] Fix realloc inconsistency in dbsync_update_payload_append:
on second realloc failure, state was inconsistent (new_values
resized, old_values not, capacity not updated). Both reallocs now
checked before updating pointers and capacity
- [MEDIUM] Move payload->count++ after all database_value_dup NULL
checks in dbsync_update_payload_append — prevents count increment
when allocation failed, which would cause use-after-free on cleanup
- [MEDIUM] Add dbsync_update_payload_free(payload) before 3 early
returns in dbsync_update_final — prevents memory leak of entire
aggregate payload on error paths
- [MEDIUM] Clean up partial database_value_dup allocations on OOM in
dbsync_update_payload_append — free dup'd values at current index
when count is not incremented to prevent leak
## src/sqlite/database_sqlite.c (6 fixes)
- [MEDIUM] Replace fixed 4096-byte trigger WHEN clause buffers with
dynamic cloudsync_memory_mprintf — prevents silent truncation for
tables with long filter expressions
- [MEDIUM] Check cloudsync_memory_mprintf return for NULL before
storing in col_names[] in database_build_trigger_when — prevents
strlen(NULL) crash in filter_is_column under OOM
- [LOW] Use consistent PRId64 format with (int64_t) cast for schema
hash in database_check_schema_hash and database_update_schema_hash
— prevents format string mismatch on platforms where uint64_t and
int64_t have different printf specifiers
- [LOW] Fix DEBUG_DBFUNCTION using undeclared variable 'table'
instead of 'table_name' in database_create_metatable (line 568)
and database_create_triggers (line 782) — compile error when
debug macros enabled
- [LOW] Remove dead else branch in database_pk_rowid — unreachable
code after sqlite3_prepare_v2 success check
## src/sqlite/sql_sqlite.c (1 fix)
- [MEDIUM] Change %s to %q in SQL_INSERT_SETTINGS_STR_FORMAT —
prevents SQL injection via malformed setting key/value strings
## src/dbutils.c (1 fix)
- [MEDIUM] Change snprintf to cloudsync_memory_mprintf for settings
insert using the new %q format — ensures proper SQL escaping
## src/utils.c (1 fix)
- [MEDIUM] Fix integer overflow in cloudsync_blob_compare:
(int)(size1 - size2) overflows for large size_t values, changed
to (size1 > size2) ? 1 : -1
## src/network.c (1 fix)
- [MEDIUM] Remove trailing semicolon from savepoint name
"cloudsync_logout_savepoint;" — semicolon in name caused
savepoint/release mismatch
## src/postgresql/sql_postgresql.c (1 fix)
- [CRITICAL] Replace EXCLUDED.col_version with %s.col_version
(table reference) in SQL_CLOUDSYNC_UPSERT_COL_INIT_OR_BUMP_VERSION
— PostgreSQL EXCLUDED refers to the proposed INSERT row (always
col_version=1), not the existing row. This caused col_version to
never increment correctly on conflict, breaking CRDT merge logic
## src/postgresql/cloudsync_postgresql.c (4 fixes)
- [HIGH] Change PG_RETURN_INT32(rc) to PG_RETURN_BOOL(rc == DBRES_OK)
in pg_cloudsync_terminate — SQL declaration returns BOOLEAN but code
returned raw integer, causing protocol mismatch
- [HIGH] Copy blob data with palloc+memcpy before databasevm_reset in
cloudsync_col_value, and fix PG_RETURN_CSTRING to PG_RETURN_BYTEA_P
— reset invalidates SPI tuple memory, causing use-after-free; wrong
return type caused type mismatch with SQL declaration
- [MEDIUM] Use palloc0 instead of cloudsync_memory_alloc+memset in
aggregate context — palloc0 is lifetime-safe in PG aggregate memory
context; cloudsync_memory_alloc uses wrong allocator
- [MEDIUM] Free SPI_tuptable in all paths of get_column_oid — prevents
SPI tuple table leak on early return
## src/postgresql/database_postgresql.c (3 fixes)
- [MEDIUM] Use sql_escape_identifier for table_name/schema in CREATE
INDEX — prevents SQL injection via specially crafted table names
- [MEDIUM] Use sql_escape_literal for table_name in trigger WHEN
clause — prevents SQL injection in trigger condition
- [LOW] Use SPI_getvalue instead of DatumGetName for type safety in
database_pk_names — DatumGetName assumes Name type which may not
match the actual column type from information_schema
## test/unit.c (3 new tests)
- do_test_blob_compare_large_sizes: verifies overflow fix for large
size_t values in cloudsync_blob_compare
- do_test_deterministic_flags: verifies cloudsync_uuid() returns
different values in same query (non-deterministic flag working)
- do_test_schema_hash_consistency: verifies int64 hash format
roundtrip through cloudsync_schema_versions table
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
0 commit comments