diff --git a/tests/Makefile.am b/tests/Makefile.am index 42292ad2..b0b987f9 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -340,7 +340,10 @@ test_chat_json_LDFLAGS = -Wl,--wrap=sqlite3_prepare_v2 -Wl,--wrap=sqlite3_bind_t -Wl,--wrap=u_realloc_impl -Wl,--wrap=u_calloc_impl test_chat_profile_SOURCES = test_chat_profile.c test_chat_profile_LDADD = $(LIBUTUN) $(COMMON_LIBS) -test_chat_profile_LDFLAGS = -Wl,--wrap=sqlite3_finalize +test_chat_profile_LDFLAGS = -Wl,--wrap=sqlite3_finalize -Wl,--wrap=sqlite3_prepare_v2 -Wl,--wrap=sqlite3_bind_text \ + -Wl,--wrap=sqlite3_bind_blob -Wl,--wrap=sqlite3_bind_int64 -Wl,--wrap=sqlite3_bind_int -Wl,--wrap=sqlite3_bind_null \ + -Wl,--wrap=sqlite3_step -Wl,--wrap=sqlite3_column_text -Wl,--wrap=sqlite3_exec -Wl,--wrap=sc_ed25519_sign \ + -Wl,--wrap=sc_ed25519_verify -Wl,--wrap=u_malloc_impl -Wl,--wrap=member_sync_add_apply_cbk else test_socket_monitor_LDFLAGS += -Wl,--wrap=read endif diff --git a/tests/test_chat_profile.c b/tests/test_chat_profile.c index 0ceaf917..cc7d272f 100644 --- a/tests/test_chat_profile.c +++ b/tests/test_chat_profile.c @@ -1,14 +1,147 @@ /* Подписанный профиль: ошибка чтения не даёт успешный/частичный снимок. */ +#define _GNU_SOURCE #include "../src/utun_instance.h" #include "../src/chat/chat_core.h" #include "../src/chat/member_sync.h" +#include "../src/chat/chat_event.h" +#include "../src/chat/chat_core_priv.h" +#include "../src/routing_layer/topo_node_sqlite.h" #include "../lib/debug_config.h" #include "../lib/mem.h" #include "../lib/json_flat.h" #include #include +#include +#include +#include static int fail_read; +enum fault { F_NONE, F_PREPARE, F_BIND, F_STEP, F_CHANGES, F_FINALIZE, F_EXEC, F_TEXT }; +static enum fault fault; +static const char* target; +static int index_number, fail_rollback, fail_member_rollback, member_rollback_failed, releases_after_failure; +static int fail_sign, fail_verify, fail_member_malloc, fail_apply_registration, delete_on_event; +static unsigned events, sequence; +static sqlite3* reader; +static char channel[32]; +static struct UTUN_INSTANCE* active; +static uint8_t original_join[64]; + +static int matches(const char* sql) { return target && !strncmp(sql, target, strlen(target)); } +static int take(enum fault point, const char* sql, int index) { + if (fault != point || !matches(sql) || ((point == F_BIND || point == F_TEXT) && index != index_number)) return 0; + fault = F_NONE; DEBUG_WARN(DEBUG_CATEGORY_CHAT, "profile test: SQL fault point=%d index=%d target=%s", point, index, target); return 1; +} +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 bytes, sqlite3_stmt** st, const char** tail) { + if (take(F_PREPARE, sql, 0)) { *st = NULL; return SQLITE_NOMEM; } + return __real_sqlite3_prepare_v2(db, sql, bytes, st, tail); +} +extern int __real_sqlite3_bind_text(sqlite3_stmt*, int, const char*, int, void (*)(void*)); +int __wrap_sqlite3_bind_text(sqlite3_stmt* st, int index, const char* value, int bytes, void (*destroy)(void*)) { + if (take(F_BIND, sqlite3_sql(st), index)) return SQLITE_NOMEM; + return __real_sqlite3_bind_text(st, index, value, bytes, destroy); +} +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* value, int bytes, void (*destroy)(void*)) { + if (take(F_BIND, sqlite3_sql(st), index)) return SQLITE_NOMEM; + return __real_sqlite3_bind_blob(st, index, value, bytes, 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(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(F_BIND, sqlite3_sql(st), index)) return SQLITE_NOMEM; + return __real_sqlite3_bind_int(st, index, value); +} +extern int __real_sqlite3_bind_null(sqlite3_stmt*, int); +int __wrap_sqlite3_bind_null(sqlite3_stmt* st, int index) { + if (take(F_BIND, sqlite3_sql(st), index)) return SQLITE_NOMEM; + return __real_sqlite3_bind_null(st, index); +} +extern int __real_sqlite3_step(sqlite3_stmt*); +int __wrap_sqlite3_step(sqlite3_stmt* st) { + if (take(F_STEP, sqlite3_sql(st), 0)) return SQLITE_IOERR; + return __real_sqlite3_step(st); +} +extern const unsigned char* __real_sqlite3_column_text(sqlite3_stmt*, int); +const unsigned char* __wrap_sqlite3_column_text(sqlite3_stmt* st, int column) { + if (take(F_TEXT, sqlite3_sql(st), column)) return NULL; + return __real_sqlite3_column_text(st, column); +} +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 (*callback)(void*, int, char**, char**), void* arg, char** error) { + if (fail_rollback && !strcmp(sql, "ROLLBACK")) { fail_rollback = 0; return SQLITE_IOERR; } + if (fail_member_rollback && !strcmp(sql, "ROLLBACK TO member_record")) { + fail_member_rollback = 0; member_rollback_failed = 1; return SQLITE_IOERR; + } + if (member_rollback_failed && !strcmp(sql, "RELEASE member_record")) releases_after_failure++; + if (take(F_EXEC, sql, 0)) return SQLITE_IOERR; + return __real_sqlite3_exec(db, sql, callback, arg, error); +} +extern int __real_sc_ed25519_sign(const uint8_t*, const uint8_t*, size_t, uint8_t*); +int __wrap_sc_ed25519_sign(const uint8_t* key, const uint8_t* bytes, size_t size, uint8_t* signature) { + if (fail_sign) { fail_sign = 0; return SC_ERR_CRYPTO; } + return __real_sc_ed25519_sign(key, bytes, size, signature); +} +extern int __real_sc_ed25519_verify(const uint8_t*, const uint8_t*, size_t, const uint8_t*); +int __wrap_sc_ed25519_verify(const uint8_t* key, const uint8_t* bytes, size_t size, const uint8_t* signature) { + int join = active && size == 112 && !memcmp(bytes + 72, active->my_keys.public_key, 32); + int update = size >= 73 && !memcmp(bytes, original_join, 64); + if ((fail_verify == 1 && join) || (fail_verify == 2 && update)) { fail_verify = 0; return SC_ERR_CRYPTO; } + return __real_sc_ed25519_verify(key, bytes, size, signature); +} +extern void* __real_u_malloc_impl(uint32_t, const char*); +void* __wrap_u_malloc_impl(uint32_t bytes, const char* location) { + if (fail_member_malloc && strstr(location, "member_sync.c")) { fail_member_malloc = 0; return NULL; } + return __real_u_malloc_impl(bytes, location); +} +extern int __real_member_sync_add_apply_cbk(struct UTUN_INSTANCE*, member_apply_cbk_fn, void*); +int __wrap_member_sync_add_apply_cbk(struct UTUN_INSTANCE* inst, member_apply_cbk_fn callback, void* arg) { + if (fail_apply_registration) { fail_apply_registration = 0; fail_member_malloc = 1; } + return __real_member_sync_add_apply_cbk(inst, callback, arg); +} + +static void snapshot(sqlite3* db, uint8_t out[32]) { + char peers[128]; snprintf(peers, sizeof(peers), "SELECT * FROM peers_%s ORDER BY node_id", channel); + const char* queries[] = {peers, "SELECT * FROM nodes ORDER BY node_id", + "SELECT namespace,level,prefix64,hash FROM merkle_tree_hash ORDER BY namespace,level,prefix64"}; + EVP_MD_CTX* hash = EVP_MD_CTX_new(); assert(hash && EVP_DigestInit_ex(hash, EVP_sha256(), NULL) == 1); + for (unsigned i = 0; i < 3; i++) { + sqlite3_stmt* st = NULL; assert(sqlite3_prepare_v2(db, queries[i], -1, &st, NULL) == SQLITE_OK); int rc; + while ((rc = sqlite3_step(st)) == SQLITE_ROW) for (int c = 0; c < sqlite3_column_count(st); c++) { + int type = sqlite3_column_type(st, c), bytes = sqlite3_column_bytes(st, c); const void* value = sqlite3_column_blob(st, c); + assert(!bytes || value); + assert(EVP_DigestUpdate(hash, &type, sizeof(type)) == 1 && EVP_DigestUpdate(hash, &bytes, sizeof(bytes)) == 1); + if (bytes) assert(EVP_DigestUpdate(hash, value, (size_t)bytes) == 1); + } + assert(rc == SQLITE_DONE && sqlite3_finalize(st) == SQLITE_OK); + } + unsigned length; assert(EVP_DigestFinal_ex(hash, out, &length) == 1 && length == 32); EVP_MD_CTX_free(hash); +} + +static void event(struct UTUN_INSTANCE* inst, int type, const uint8_t* data, int bytes) { + if (type != CHAT_EVT_MEMBER_UPDATED) return; + (void)data; (void)bytes; assert(sqlite3_get_autocommit(inst->topo_sqlite_db)); + char profile[MS_USERINFO_MAX + 1]; uint8_t signature[64], writer[32], committed[32]; uint64_t timestamp; + assert(!chat_core_get_my_profile(inst, channel, profile, sizeof(profile), signature, ×tamp)); + snapshot(inst->topo_sqlite_db, writer); snapshot(reader, committed); assert(!memcmp(writer, committed, 32)); events++; + if (delete_on_event) { delete_on_event = 0; chat_core_delete_channel(inst, channel); } +} + +static int update(struct UTUN_INSTANCE* inst) { + char value[32]; snprintf(value, sizeof(value), "%u", ++sequence); + const struct chat_profile_option option = {"test", value}; return chat_core_set_my_profile_options(inst, channel, &option, 1); +} +static void refused(struct UTUN_INSTANCE* inst) { + uint8_t before[32], after[32]; snapshot(reader, before); unsigned count = events; + assert(update(inst) < 0 && fault == F_NONE && !fail_sign && !fail_verify && sqlite3_get_autocommit(inst->topo_sqlite_db)); + snapshot(reader, after); assert(!memcmp(before, after, 32) && events == count); +} +static void retry(struct UTUN_INSTANCE* inst) { unsigned count = events; assert(!update(inst) && events == count + 1); } static void unicode(void) { const char* json = "{\"name\":\"\\u0410\\uD83D\\uDE00\",\"protect\":\"yes\",\"control\":\"\\u0001\"}"; @@ -25,6 +158,7 @@ int __wrap_sqlite3_finalize(sqlite3_stmt* st) { const char* prefix = "SELECT userinfo,update_sig,update_ts"; int fail = fail_read && st && !strncmp(sqlite3_sql(st), prefix, strlen(prefix)); if (fail) { fail_read = 0; DEBUG_WARN(DEBUG_CATEGORY_CHAT, "profile test: injected read finalize failure"); } + if (st && take(F_FINALIZE, sqlite3_sql(st), 0)) fail = 1; int rc = __real_sqlite3_finalize(st); return fail ? SQLITE_IOERR : rc; }