Skip to content

Comments

Fix 35 bugs and bump version to 0.9.111#9

Open
marcobambini wants to merge 3 commits intomainfrom
fixes-21022026
Open

Fix 35 bugs and bump version to 0.9.111#9
marcobambini wants to merge 3 commits intomainfrom
fixes-21022026

Conversation

@marcobambini
Copy link
Member

Summary

  • Comprehensive audit identified and fixed 35 bugs (1 CRITICAL, 7 HIGH, 18 MEDIUM, 9 LOW) across the entire CloudSync codebase
  • Added 3 new targeted unit tests
  • Bumped version to 0.9.111
  • All 84 SQLite tests and 26 PostgreSQL tests pass with 0 failures and 0 memory leaks

Critical & High severity fixes

Severity File Fix
CRITICAL sql_postgresql.c EXCLUDED.col_version → table reference in UPSERT — broke CRDT merge logic in PostgreSQL
HIGH cloudsync.c Guard NULL db_version_stmt in cloudsync_dbversion_rerun
HIGH cloudsync_sqlite.c Split DEFAULT_FLAGS into pure/volatile — cloudsync_uuid() returned identical values in same query
HIGH cloudsync_sqlite.c Fix realloc inconsistency in aggregate append (new_values resized, old_values not)
HIGH cloudsync_postgresql.c PG_RETURN_INT32(rc)PG_RETURN_BOOL(rc == DBRES_OK) type mismatch
HIGH cloudsync_postgresql.c Copy blob before databasevm_reset + fix PG_RETURN_CSTRINGPG_RETURN_BYTEA_P (use-after-free)

Medium severity fixes

  • cloudsync.c: NULL stmt guard in dbvm_execute, (bool)dbvm_count> 0, NULL column text check in refill_metatable, early returns routed through goto cleanup in payload_apply, return falsegoto abort_add_table, uninitialized *persistent in cloudsync_colvalue_stmt, wrong NULL check in payload_get
  • cloudsync_sqlite.c: Move count++ after NULL checks, add payload_free before early returns, clean up partial database_value_dup on OOM
  • database_sqlite.c: Dynamic trigger WHEN buffers (replace 4096-byte stack), NULL mprintf check in col_names[]
  • sql_sqlite.c / dbutils.c: %s%q for SQL escaping in settings insert
  • utils.c: Integer overflow in blob_compare size subtraction
  • network.c: Trailing semicolon in savepoint name
  • database_postgresql.c: sql_escape_identifier for CREATE INDEX, sql_escape_literal in trigger WHEN
  • cloudsync_postgresql.c: palloc0 in aggregate context, SPI_freetuptable in all paths

Low severity fixes

  • NULL check on database_column_blob in merge_did_cid_win
  • Partial failure cleanup in table_add_to_context_cb
  • Remove unused pragma_checked field
  • strcmp instead of pointer comparison in cloudsync_set_schema
  • Consistent PRId64 format for schema hash
  • SPI_getvalue instead of DatumGetName for type safety
  • Remove dead else branch in database_pk_rowid
  • Fix DEBUG_DBFUNCTION wrong variable name (2 instances)

New tests

  • do_test_blob_compare_large_sizes — verifies overflow fix
  • do_test_deterministic_flags — verifies UUID non-determinism
  • do_test_schema_hash_consistency — verifies int64 hash roundtrip

Test plan

  • make clean && make unittest — 84/84 SQLite tests pass, 0 memory leaks
  • make postgres-docker-run-test — 26/26 PostgreSQL tests pass, 0 failures

🤖 Generated with Claude Code

marcobambini and others added 2 commits February 21, 2026 16:28
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>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@marcobambini marcobambini requested a review from andinux February 21, 2026 15:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant