From 8982df364edb435da0a4117fd33e6768d4753df8 Mon Sep 17 00:00:00 2001 From: Mollusk Date: Wed, 17 Jun 2026 16:17:03 -0400 Subject: [PATCH] Fix gossip announce address binding --- src/network/gossip.rs | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/src/network/gossip.rs b/src/network/gossip.rs index aabaa7c..c233e20 100644 --- a/src/network/gossip.rs +++ b/src/network/gossip.rs @@ -79,6 +79,8 @@ enum GossipReject { BadSignature, /// Timestamp outside the freshness window — stale (replay) or implausibly future. OutOfWindow, + /// A signed Announce advertised an address for a different node id. + AnnounceAddressMismatch, } /// Authenticate a received payload against the room topic and local clock. The @@ -99,6 +101,10 @@ fn verify_gossip( if now_ms.abs_diff(payload.ts) > window_ms { return Err(GossipReject::OutOfWindow); } + if let GossipMessage::Announce(state) = &payload.msg + && state.addr.id != payload.author { + return Err(GossipReject::AnnounceAddressMismatch); + } Ok(()) } @@ -490,10 +496,8 @@ mod tests { use crate::network::PeerState; use iroh::SecretKey; - fn sample_peer_state() -> PeerState { - let secret = SecretKey::generate(); - let public = secret.public(); - let addr = iroh::EndpointAddr::from(public); + fn sample_peer_state_for(id: EndpointId) -> PeerState { + let addr = iroh::EndpointAddr::from(id); PeerState { name: "TestPeerGossip".to_string(), is_muted: true, @@ -563,7 +567,7 @@ mod tests { fn test_gossip_payload_announce_round_trip() { let secret = SecretKey::generate(); let topic = [9u8; 32]; - let peer_state = sample_peer_state(); + let peer_state = sample_peer_state_for(secret.public()); let payload = sign_gossip(&secret, &topic, 1000, GossipMessage::Announce(peer_state.clone())); let serialized = serde_json::to_string(&payload).unwrap(); @@ -732,5 +736,18 @@ mod tests { // Within the window (clock skew tolerance) → accepted. assert!(verify_gossip(&p, &topic, 1_000_000 + GOSSIP_FRESHNESS_MS - 1, GOSSIP_FRESHNESS_MS).is_ok()); } -} + #[test] + fn verify_rejects_announce_with_address_for_another_identity() { + let signer = SecretKey::generate(); + let advertised = SecretKey::generate(); + let topic = [6u8; 32]; + let state = sample_peer_state_for(advertised.public()); + let p = sign_gossip(&signer, &topic, 5_000, GossipMessage::Announce(state)); + + assert_eq!( + verify_gossip(&p, &topic, 5_000, GOSSIP_FRESHNESS_MS), + Err(GossipReject::AnnounceAddressMismatch) + ); + } +}