184 lines
11 KiB
C
184 lines
11 KiB
C
/* Подписанный профиль: ошибка чтения не даёт успешный/частичный снимок. */
|
|
#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 <assert.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <openssl/evp.h>
|
|
#include <openssl/crypto.h>
|
|
|
|
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\"}";
|
|
char value[64]; assert(!json_flat_get(json, "name", value, sizeof(value)) && !strcmp(value, "А😀"));
|
|
assert(!json_flat_get(json, "protect", value, sizeof(value)) && !strcmp(value, "yes"));
|
|
assert(!json_flat_get(json, "control", value, sizeof(value)) && value[0] == 1 && !value[1]);
|
|
const char* invalid[] = {"{\"a\":\"\\u0000\"}", "{\"a\":\"\\uD800\"}", "{\"a\":\"\\uDC00\"}",
|
|
"{\"a\":\"\\uD800\\u0001\"}", "{\"a\":\"\\uXX12\"}", "{\"a\":\"\\u123\"}", "{\"a\":\"raw\nline\"}"};
|
|
for (unsigned i = 0; i < sizeof(invalid) / sizeof(invalid[0]); i++) assert(json_flat_parse(invalid[i], NULL, NULL) < 0);
|
|
puts("PASS profile Unicode reader:UTF-8,surrogate pair,control,following policy field;invalid Unicode/NUL/raw control refused");
|
|
}
|
|
extern int __real_sqlite3_finalize(sqlite3_stmt*);
|
|
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;
|
|
}
|
|
|
|
int main(void) {
|
|
debug_config_init(); debug_set_level(DEBUG_LEVEL_ERROR); size_t baseline = u_get_allocated_count();
|
|
unicode();
|
|
utun_instance_set_tun_init_enabled(0); struct UASYNC* ua = uasync_create(); assert(ua);
|
|
struct UTUN_INSTANCE* inst = utun_instance_create_from_str(ua,
|
|
"[global]\nmy_node_name=profile test\n[ntp]\nenabled=no\n[chatserver]\ngroup_autoconnect=0\nstorage_autoload=0\n");
|
|
assert(inst && !utun_core_start(inst) && !chat_service_start(inst));
|
|
uint64_t group = chat_core_create_channel_auto(inst, "profile"); assert(group); char channel[32];
|
|
snprintf(channel, sizeof(channel), "%llu", (unsigned long long)group);
|
|
char profile[MS_USERINFO_MAX + 1], previous[MS_USERINFO_MAX + 1]; uint8_t signature[64], old_signature[64];
|
|
memset(profile, 0xa5, sizeof(profile)); memcpy(previous, profile, sizeof(profile));
|
|
memset(signature, 0xa5, sizeof(signature)); memcpy(old_signature, signature, sizeof(signature)); uint64_t timestamp = 123;
|
|
fail_read = 1; int rc = chat_core_get_my_profile(inst, channel, profile, sizeof(profile), signature, ×tamp);
|
|
DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "profile test: failed finalize rc=%d pending=%d", rc, fail_read);
|
|
assert(rc < 0 && !fail_read && timestamp == 123 && !memcmp(profile, previous, sizeof(profile)) && !memcmp(signature, old_signature, 64));
|
|
assert(!chat_core_get_my_profile(inst, channel, profile, sizeof(profile), signature, ×tamp) && timestamp > 0);
|
|
utun_instance_destroy(inst); uasync_poll(ua, 0); uasync_destroy(ua, 0); assert(u_get_allocated_count() == baseline);
|
|
puts("PASS profile complete read and retry"); return 0;
|
|
}
|