on master help sign in
Fix safeCompare timing leak, federation ack message loss, gossip hop-0 rejection, federation retry, and gossip source spoofing
commit5b68b19ebb2336c61f726966982a3492ba39f1e7 authoriko <mail@iko.soy> dateSun 29 Mar 2026, 15:51 (7 months ago) parent07b1acc
1 file changed+21−33
server.knot+21−33
server.knot+21−33
@@ -137,17 +137,12 @@ verifyFederationAuth = \authorization ->
137137
138138clampTtl = \ttl -> if ttl < 1 then 1 else if ttl > maxPresenceTtl then maxPresenceTtl else ttl
139139
140−sameLength = \s1 s2 ->
141− if s1 == "" && s2 == "" then True {}
142− else if s1 == "" || s2 == "" then False {}
143− else sameLength (drop 1 s1) (drop 1 s2)
144−
145140safeCompareLoop = \s1 s2 result ->
146141 if s1 == "" && s2 == ""
147142 then result
148143 else safeCompareLoop (drop 1 s1) (drop 1 s2) (result + (if take 1 s1 == take 1 s2 then 0 else 1))
149144
150−safeCompare = \a b -> sameLength a b && safeCompareLoop a b 0 == 0
145+safeCompare = \a b -> safeCompareLoop a b 0 == 0
151146
152147sendToEach = \servers action ->
153148 if count servers == 0
@@ -160,7 +155,9 @@ sendToEach = \servers action ->
160155 else do
161156 catch (action server) (\err -> do
162157 println ("federation: error reaching " ++ server ++ ": " ++ show err)
163− yield {})
158+ catch (action server) (\err2 -> do
159+ println ("federation: retry failed for " ++ server ++ ": " ++ show err2)
160+ yield {}))
164161 sendToEach rest action
165162
166163pruneExpired = \t -> do
@@ -517,7 +514,7 @@ serve = \req -> case req of
517514 t <- now
518515 let authorized = verifyFederationAuth authorization
519516 let cappedHops = if hopsRemaining > 5 then 5 else hopsRemaining
520− let validRequest = authorized && cappedHops >= 1 && not (fromServer == "") && not (fromServer == serverHost) && not (tooLong fromServer maxPubkeyLen) && not (containsBadUrlChar fromServer) && count events <= maxGossipEvents
517+ let validRequest = authorized && cappedHops >= 0 && not (fromServer == "") && not (fromServer == serverHost) && not (tooLong fromServer maxPubkeyLen) && not (containsBadUrlChar fromServer) && count events <= maxGossipEvents
521518 let onlineEntries = do
522519 e <- events
523520 where validRequest
@@ -579,8 +576,12 @@ serve = \req -> case req of
579576 gossipResult <- atomic do
580577 pr <- pruneExpired t
581578 presence <- *presence
582− let appliedOnline = do
579+ let trustedDeduped = do
583580 e <- deduped
581+ where e.server == fromServer || count (do p <- presence; where p.pubkey == e.pubkey; where p.server == e.server; yield p) > 0
582+ yield e
583+ let appliedOnline = do
584+ e <- trustedDeduped
584585 where count (do
585586 p <- presence
586587 where p.pubkey == e.pubkey
@@ -601,10 +602,10 @@ serve = \req -> case req of
601602 p <- acc
602603 where not (p.pubkey == e.pubkey && p.server == e.server)
603604 yield p)
604− [e]) presence deduped
605+ [e]) presence trustedDeduped
605606 let offlineOnlyKeys = do
606607 k <- affectedKeys
607− where count (do d <- deduped; where d.pubkey == k.pubkey; where d.server == k.server; yield d) == 0
608+ where count (do d <- trustedDeduped; where d.pubkey == k.pubkey; where d.server == k.server; yield d) == 0
608609 yield k
609610 let appliedOffline = do
610611 k <- offlineOnlyKeys
@@ -649,7 +650,7 @@ serve = \req -> case req of
649650 where not (p.pubkey == e.pubkey && p.server == e.server)
650651 yield p)
651652 [e]
652− else acc) presence deduped
653+ else acc) presence trustedDeduped
653654 let updatesOnlyAfterOffline = if withinCapacity then afterOffline
654655 else diff updatesOnlyMerged (do
655656 p <- updatesOnlyMerged
@@ -665,7 +666,7 @@ serve = \req -> case req of
665666 yield p)
666667 let appliedOnlineUpdates = if withinCapacity then appliedOnline
667668 else do
668− e <- deduped
669+ e <- trustedDeduped
669670 where count (do p <- presence; where p.pubkey == e.pubkey; where p.server == e.server; yield p) > 0
670671 where count (do
671672 p <- presence
@@ -768,29 +769,16 @@ serve = \req -> case req of
768769 let validAckFields = authorized && originServer == serverHost && not (fromServer == "") && not (fromServer == serverHost) && not (tooLong fromServer maxPubkeyLen) && not (containsBadUrlChar fromServer)
769770 pruneResult <- atomic do
770771 pr <- pruneExpired t
771− messages <- *messages
772− presence <- *presence
773772 forwards <- *forwards
774773 let validAck = validAckFields
775− set *messages = if validAck
776− then diff messages (do
777− m <- messages
774+ set *forwards = if validAck
775+ then diff forwards (do
776+ f <- forwards
778777 mid <- dedupedIds
779− where m.messageId == mid
780− where m.originServer == ""
781− where count (do
782− f <- forwards
783− where f.messageId == mid
784− where f.toServer == fromServer
785− yield f) > 0
786− where count (do
787− p <- presence
788− where p.pubkey == m.toKey
789− where p.server == serverHost
790− where p.expiresAt > t
791− yield p) == 0
792− yield m)
793− else messages
778+ where f.messageId == mid
779+ where f.toServer == fromServer
780+ yield f)
781+ else forwards
794782 yield {peerServers: pr.peerServers, expiredLocal: pr.expiredLocal, validAck: validAck}
795783 respond {ok: pruneResult.validAck}
796784 broadcastOfflineGossip t pruneResult.peerServers pruneResult.expiredLocal