Prevent invalid old versions from winning freshness comparisons
This commit is contained in:
1 parent
7319f900c4
commit
c02d5a6cfc
2 files changed
+28
-9
No files matched your search
@@ -948,6 +948,13 @@ int member_sync_verify_local_record(struct sqlite3* db, const char* ch_id, const
|
||||
MS_ID, (unsigned long long)m->node_id, ch_id);
|
||||
return 0;
|
||||
}
|
||||
uint8_t trusted_ed[32];
|
||||
int trusted = topo_node_sqlite_get_ed25519_pubkey(db, m->node_id, trusted_ed);
|
||||
if (trusted == TOPO_SQLITE_LOOKUP_ERROR) return -1;
|
||||
if (!trusted && memcmp(trusted_ed, m->ed25519, 32)) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_MEMBER_SYNC, "member identity conflicts with verified node ch=%s node=%016llx",
|
||||
ch_id, (unsigned long long)m->node_id); return 0;
|
||||
}
|
||||
|
||||
/* JOIN проверяется и при наличии профиля: профиль сам по себе не связывает запись с группой. */
|
||||
if (m->join_sig && !_sig_is_zero64(m->join_sig)) {
|
||||
@@ -1439,6 +1446,18 @@ static int member_commit_pull(void* ctx, void* state) {
|
||||
rc = sqlite3_finalize(st); st = NULL; if (rc != SQLITE_OK) goto fail;
|
||||
struct member_wire record; size_t used;
|
||||
if (member_decode(bytes, length, &record, &used) || used != length) { result = MT_ERR_DATA; goto fail; }
|
||||
struct member_saved previous;
|
||||
int found = member_read_saved(db, pull->channel, record.rec.node_id, 0, 0, &previous);
|
||||
if (found < 0) goto fail;
|
||||
if (found && !previous.source) {
|
||||
int valid = previous.malformed ? 0 : member_sync_verify_local_record(db, pull->channel, &previous.rec);
|
||||
if (valid < 0) goto fail;
|
||||
if (!valid) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_MEMBER_SYNC, "member receive ignores invalid baseline ch=%s sync=%lld node=%016llx",
|
||||
pull->channel, (long long)pull->id, (unsigned long long)record.rec.node_id);
|
||||
if (topo_node_sqlite_member_del(db, pull->channel, record.rec.node_id)) goto fail;
|
||||
}
|
||||
}
|
||||
if (!record.rec.join_sig && !topo_node_sqlite_member_get_join(db, pull->channel, record.rec.node_id, record.local_join, &record.rec.join_ts))
|
||||
record.rec.join_sig = record.local_join;
|
||||
rc = member_apply_record(inst, pull->channel, pull->peer, &record.rec, 1);
|
||||
|
||||
@@ -57,7 +57,7 @@ static struct member_pull* receive(struct UTUN_INSTANCE* source, struct UTUN_INS
|
||||
assert(!member_stage_page(target, state, prefix, 0, &after, &next, more, bytes, length)); return state;
|
||||
}
|
||||
|
||||
static void accept(struct UTUN_INSTANCE* inst, struct member_pull* pull) {
|
||||
static void finish_receive(struct UTUN_INSTANCE* inst, struct member_pull* pull) {
|
||||
assert(!member_commit_pull(inst, pull)); member_publish_pull(inst, pull); member_abort_pull(inst, pull);
|
||||
assert(sqlite3_get_autocommit(inst->topo_sqlite_db));
|
||||
}
|
||||
@@ -83,30 +83,30 @@ int main(void) {
|
||||
profile(&source, 2, 3);
|
||||
struct member_pull* b = receive(&source, &target, 2, identities[3].id);
|
||||
assert(a->id != b->id && scalar(&target, "SELECT COUNT(*) FROM peers_1001 WHERE record_state=1") == 4);
|
||||
accept(&target, b); assert(version(&target) == 3);
|
||||
finish_receive(&target, b); assert(version(&target) == 3);
|
||||
assert(scalar(&target, "SELECT COUNT(*) FROM peers_1001 WHERE record_state=1") == 2);
|
||||
accept(&target, a); assert(version(&target) == 3);
|
||||
finish_receive(&target, a); assert(version(&target) == 3);
|
||||
assert(!scalar(&target, "SELECT COUNT(*) FROM peers_1001 WHERE record_state<>0"));
|
||||
DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "PASS two peers: isolated staging, no early profile/hash change, stale completion preserves newer commit");
|
||||
|
||||
profile(&source, 2, 4); a = receive(&source, &target, 2, identities[1].id); b = receive(&source, &target, 2, identities[3].id);
|
||||
member_abort_pull(&target, a);
|
||||
assert(scalar(&target, "SELECT COUNT(*) FROM peers_1001 WHERE record_state=1") == 2);
|
||||
accept(&target, b); assert(version(&target) == 4);
|
||||
finish_receive(&target, b); assert(version(&target) == 4);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "PASS cancelling one peer leaves the other exchange intact");
|
||||
|
||||
profile(&source, 2, 5);
|
||||
char sql[256]; snprintf(sql, sizeof(sql), "UPDATE peers_1001 SET update_sig=randomblob(64) WHERE node_id=%lld AND record_state=0",
|
||||
(long long)identities[2].id);
|
||||
assert(sqlite3_exec(source.topo_sqlite_db, sql, NULL, NULL, NULL) == SQLITE_OK);
|
||||
a = receive(&source, &target, 2, identities[1].id); accept(&target, a); assert(version(&target) == 4);
|
||||
a = receive(&source, &target, 2, identities[1].id); finish_receive(&target, a); assert(version(&target) == 4);
|
||||
assert(sqlite3_exec(target.topo_sqlite_db, sql, NULL, NULL, NULL) == SQLITE_OK);
|
||||
a = receive(&source, &target, 2, identities[1].id); accept(&target, a);
|
||||
a = receive(&source, &target, 2, identities[1].id); finish_receive(&target, a);
|
||||
assert(!topo_node_sqlite_member_in_channel(target.topo_sqlite_db, "1001", identities[2].id));
|
||||
assert(member_sync_count(&target, "1001") == 2 && topo_node_sqlite_member_allowed(target.topo_sqlite_db, "1001", identities[0].id));
|
||||
DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "PASS bad new keeps good old; both invalid removes only that member");
|
||||
|
||||
profile(&source, 2, 6); a = receive(&source, &target, 2, identities[1].id); accept(&target, a); assert(version(&target) == 6);
|
||||
profile(&source, 2, 6); a = receive(&source, &target, 2, identities[1].id); finish_receive(&target, a); assert(version(&target) == 6);
|
||||
profile(&source, 2, 7); a = receive(&source, &target, 2, identities[1].id);
|
||||
snprintf(sql, sizeof(sql), "CREATE TRIGGER fail_identity BEFORE INSERT ON nodes WHEN NEW.node_id=%lld"
|
||||
" BEGIN SELECT RAISE(ABORT,'injected identity failure'); END", (long long)identities[2].id);
|
||||
@@ -121,11 +121,11 @@ int main(void) {
|
||||
member_sync_destroy(&target); assert(!member_sync_init(&target));
|
||||
assert(version(&target) == 6 && !scalar(&target, "SELECT COUNT(*) FROM peers_1001 WHERE record_state<>0"));
|
||||
b = receive(&source, &target, 2, identities[3].id); assert(b->id > previous);
|
||||
member_abort_pull(&target, a); accept(&target, b); assert(version(&target) == 7);
|
||||
member_abort_pull(&target, a); finish_receive(&target, b); assert(version(&target) == 7);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "PASS restart removes abandoned versions without reusing exchange identifiers");
|
||||
|
||||
corrupt(&target); profile(&source, 2, 8);
|
||||
a = receive(&source, &target, 2, identities[1].id); accept(&target, a); assert(version(&target) == 8);
|
||||
a = receive(&source, &target, 2, identities[1].id); finish_receive(&target, a); assert(version(&target) == 8);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "PASS valid new version repairs a malformed previous version");
|
||||
member_sync_destroy(&source); member_sync_destroy(&target);
|
||||
assert(sqlite3_close(source.topo_sqlite_db) == SQLITE_OK && sqlite3_close(target.topo_sqlite_db) == SQLITE_OK);
|
||||
|
||||
Reference in new issue
Block a user