From 6a4fa8065aee14c154af2e91f528797170c09db4 Mon Sep 17 00:00:00 2001 From: evgeny Date: Tue, 27 Oct 2026 03:30:51 +0100 Subject: [PATCH] Add targeted SQL failure injection to real signed feedback tests --- tests/Makefile.am | 3 ++ tests/test_chat_feedback.c | 76 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 75 insertions(+), 4 deletions(-) diff --git a/tests/Makefile.am b/tests/Makefile.am index a8bfda71..be2a36bb 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -896,6 +896,9 @@ test_merkle_sync_SOURCES = test_merkle_sync.c $(test_clock_sources) test_chat_feedback_SOURCES = test_chat_feedback.c test_chat_feedback_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/src/chat -I$(top_srcdir)/lib test_chat_feedback_LDADD = $(LIBUTUN) $(CRYPTO_LIBS) $(COMMON_LIBS) +test_chat_feedback_LDFLAGS = -Wl,--wrap=sqlite3_exec -Wl,--wrap=sqlite3_prepare_v2 -Wl,--wrap=sqlite3_bind_blob \ + -Wl,--wrap=sqlite3_bind_int64 -Wl,--wrap=sqlite3_bind_int -Wl,--wrap=sqlite3_step -Wl,--wrap=sqlite3_changes \ + -Wl,--wrap=sqlite3_finalize test_merkle_protocol_SOURCES = test_merkle_protocol.c test_merkle_protocol_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/src/chat -I$(top_srcdir)/lib test_merkle_protocol_LDADD = $(LIBUTUN) $(CRYPTO_LIBS) $(COMMON_LIBS) diff --git a/tests/test_chat_feedback.c b/tests/test_chat_feedback.c index d1539106..e5fdeb17 100644 --- a/tests/test_chat_feedback.c +++ b/tests/test_chat_feedback.c @@ -5,6 +5,74 @@ static const uint8_t owner_private[32] = {9}; static int events; +enum sql_fault { F_NONE, F_BEGIN, F_PREPARE, F_BIND, F_STEP, F_CHANGES, F_FINALIZE, F_COMMIT }; +enum sql_operation { F_GET, F_PUT }; +static enum sql_fault injected; +static enum sql_operation injected_operation; +static int injected_bind, failed_rollback, last_put; + +static int fault_statement(const char* sql) { + const char* prefix = injected_operation == F_GET ? "SELECT message_id,node_id,kind,ver,data,signature FROM feedback_1001 " : + "INSERT INTO feedback_1001(record_key,bucket,message_id,node_id,kind,ver,data,signature)"; + return sql && !strncmp(sql, prefix, strlen(prefix)); +} + +static int take_fault(enum sql_fault point, const char* sql, int index) { + if (injected != point || (sql && !fault_statement(sql)) || (point == F_BIND && index != injected_bind)) return 0; + injected = F_NONE; + DEBUG_WARN(DEBUG_CATEGORY_CHAT_SYNC, "feedback test: injected stage=%d operation=%d bind=%d", point, injected_operation, index); return 1; +} + +extern int __real_sqlite3_exec(sqlite3*, const char*, int (*)(void*, int, char**, char**), void*, char**); +int __wrap_sqlite3_exec(sqlite3* db, const char* sql, int (*cb)(void*, int, char**, char**), void* arg, char** error) { + if ((!strcmp(sql, "SAVEPOINT feedback_update") || strstr(sql, "BEGIN IMMEDIATE /* feedback")) && take_fault(F_BEGIN, NULL, 0)) + return SQLITE_BUSY; + if ((!strcmp(sql, "RELEASE feedback_update") || !strncmp(sql, "COMMIT /* feedback", 18)) && take_fault(F_COMMIT, NULL, 0)) + return SQLITE_BUSY; + if (failed_rollback && !strncmp(sql, "ROLLBACK", 8)) { + failed_rollback = 0; DEBUG_WARN(DEBUG_CATEGORY_CHAT_SYNC, "feedback test: injected rollback failure"); return SQLITE_IOERR; + } + return __real_sqlite3_exec(db, sql, cb, arg, error); +} + +extern int __real_sqlite3_prepare_v2(sqlite3*, const char*, int, sqlite3_stmt**, const char**); +int __wrap_sqlite3_prepare_v2(sqlite3* db, const char* sql, int length, sqlite3_stmt** st, const char** tail) { + if (take_fault(F_PREPARE, sql, 0)) { *st = NULL; return SQLITE_NOMEM; } + return __real_sqlite3_prepare_v2(db, sql, length, st, tail); +} + +extern int __real_sqlite3_bind_blob(sqlite3_stmt*, int, const void*, int, void (*)(void*)); +int __wrap_sqlite3_bind_blob(sqlite3_stmt* st, int index, const void* bytes, int length, void (*destroy)(void*)) { + if (take_fault(F_BIND, sqlite3_sql(st), index)) return SQLITE_NOMEM; + return __real_sqlite3_bind_blob(st, index, bytes, length, destroy); +} +extern int __real_sqlite3_bind_int64(sqlite3_stmt*, int, sqlite3_int64); +int __wrap_sqlite3_bind_int64(sqlite3_stmt* st, int index, sqlite3_int64 value) { + if (take_fault(F_BIND, sqlite3_sql(st), index)) return SQLITE_NOMEM; + return __real_sqlite3_bind_int64(st, index, value); +} +extern int __real_sqlite3_bind_int(sqlite3_stmt*, int, int); +int __wrap_sqlite3_bind_int(sqlite3_stmt* st, int index, int value) { + if (take_fault(F_BIND, sqlite3_sql(st), index)) return SQLITE_NOMEM; + return __real_sqlite3_bind_int(st, index, value); +} +extern int __real_sqlite3_step(sqlite3_stmt*); +int __wrap_sqlite3_step(sqlite3_stmt* st) { + const char* sql = sqlite3_sql(st); last_put = !strncmp(sql, "INSERT INTO feedback_1001(", 26); + if (take_fault(F_STEP, sql, 0)) return SQLITE_IOERR; + return __real_sqlite3_step(st); +} +extern int __real_sqlite3_changes(sqlite3*); +int __wrap_sqlite3_changes(sqlite3* db) { + if (last_put && take_fault(F_CHANGES, NULL, 0)) return 0; + return __real_sqlite3_changes(db); +} +extern int __real_sqlite3_finalize(sqlite3_stmt*); +int __wrap_sqlite3_finalize(sqlite3_stmt* st) { + int fail = st && take_fault(F_FINALIZE, sqlite3_sql(st), 0); + int rc = __real_sqlite3_finalize(st); return fail ? SQLITE_IOERR : rc; +} + /* Событие должно видеть опубликованную транзакцию. */ static void on_event(struct UTUN_INSTANCE* inst, int type, const uint8_t* data, int len) { (void)data; (void)len; @@ -17,13 +85,13 @@ static void public_key(const uint8_t priv[32], uint8_t pub[32]) { } /* Экземпляры используют один uasync данного потока, но разные БД и идентичности. */ -static void setup(struct UTUN_INSTANCE* inst, struct UASYNC* ua) { +static void setup(struct UTUN_INSTANCE* inst, struct UASYNC* ua, const char* path) { memset(inst, 0, sizeof(*inst)); inst->ua = ua; assert(sc_generate_keypair(&inst->my_keys) == SC_OK); inst->node_id = sc_derive_node_id_from_pubkey(inst->my_keys.public_key); memcpy(inst->my_ed25519_privkey, inst->my_keys.private_key, 32); public_key(inst->my_ed25519_privkey, inst->my_ed25519_pubkey); - assert(sqlite3_open(":memory:", &inst->topo_sqlite_db) == SQLITE_OK); + assert(sqlite3_open(path ? path : ":memory:", &inst->topo_sqlite_db) == SQLITE_OK); assert(topo_node_sqlite_init(inst->topo_sqlite_db) == 0); uint8_t x[32] = {3}, ed[32], zero[64] = {0}; public_key(owner_private, ed); assert(topo_node_sqlite_channel_put(inst->topo_sqlite_db, "1001", "one", 1, x, NULL, ed, NULL, zero) == 0); @@ -94,7 +162,7 @@ static int veto_commit(void* arg) { int* remaining = arg; return (*remaining)-- /* Исключение, purge и ожидание signer не уничтожают последние состояния и отмены. */ static void membership_regressions(struct UASYNC* ua) { - struct UTUN_INSTANCE author, target, inviter; setup(&author, ua); setup(&target, ua); setup(&inviter, ua); + struct UTUN_INSTANCE author, target, inviter; setup(&author, ua, NULL); setup(&target, ua, NULL); setup(&inviter, ua, NULL); for (unsigned ch = 0; ch < 2; ch++) { const char* channel = ch ? "1002" : "1001"; add_member(&author, &author, channel); add_member(&target, &author, channel); add_member(&target, &target, channel); @@ -271,7 +339,7 @@ int main(int argc, char** argv) { if (argc == 2) return check_profile_authors(argv[1]); size_t baseline = u_get_allocated_count(); struct UASYNC* ua = uasync_create(); assert(ua); - struct UTUN_INSTANCE source, target; setup(&source, ua); setup(&target, ua); + struct UTUN_INSTANCE source, target; setup(&source, ua, NULL); setup(&target, ua, NULL); for (int ch = 0; ch < 2; ch++) { const char* channel = ch ? "1002" : "1001"; add_member(&source, &source, channel); add_member(&target, &source, channel);