Add targeted SQL failure injection to real signed feedback tests

This commit is contained in:
evgeny committed 2026-10-27 03:30:51 +01:00
1 parent 1e5fc2d335
commit 6a4fa8065a
2 files changed
+75 -4

No files matched your search

+3
View File
@@ -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)
+72 -4
View File
@@ -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);