on master help sign in
Fix URL backslash injection, safeCompare length leak, token entropy, gossip capacity logging, and idle expiry
07b1acciko
server.knot+32−10
@@ -84,7 +84,12 @@ route FederationApi where
84 -> {ok: Bool}
84 -> {ok: Bool}
85 = FederationAck
85 = FederationAck
86
86
87+route MaintenanceApi where
88+ POST {} /maintenance/prune headers {authorization: Text}
89+ -> {ok: Bool}
90+ = Prune
91+
87−route Api = AuthApi | ClientApi | FederationApi
92+route Api = AuthApi | ClientApi | FederationApi | MaintenanceApi
88
93
89-- ============================================
94-- ============================================
90-- Helpers
95-- Helpers
@@ -95,7 +100,8 @@ genToken = do
95 b <- randomInt 2147483647
100 b <- randomInt 2147483647
96 c <- randomInt 2147483647
101 c <- randomInt 2147483647
97 d <- randomInt 2147483647
102 d <- randomInt 2147483647
103+ e <- randomInt 2147483647
98− yield (bytesToHex (textToBytes (show a ++ "." ++ show b ++ "." ++ show c ++ "." ++ show d)))
104+ yield (bytesToHex (textToBytes (show a ++ "." ++ show b ++ "." ++ show c ++ "." ++ show d ++ "." ++ show e)))
99
105
100tooLong = \s maxLen -> not (drop maxLen s == "")
106tooLong = \s maxLen -> not (drop maxLen s == "")
101
107
@@ -103,7 +109,7 @@ isValidHex = \s -> not (s == "") && bytesToHex (bytesFromHex s) == s
103
109
104containsBadUrlChar = \s ->
110containsBadUrlChar = \s ->
105 if s == "" then False {}
111 if s == "" then False {}
106− else if take 1 s == "/" || take 1 s == "?" || take 1 s == "#" || take 1 s == " " || take 1 s == "@" || take 1 s == "%"
112+ else if take 1 s == "/" || take 1 s == "?" || take 1 s == "#" || take 1 s == " " || take 1 s == "@" || take 1 s == "%" || take 1 s == "\\"
107 || take 1 (bytesToHex (textToBytes (take 1 s))) == "0" || take 1 (bytesToHex (textToBytes (take 1 s))) == "1" || bytesToHex (textToBytes (take 1 s)) == "7f"
113 || take 1 (bytesToHex (textToBytes (take 1 s))) == "0" || take 1 (bytesToHex (textToBytes (take 1 s))) == "1" || bytesToHex (textToBytes (take 1 s)) == "7f"
108 then True {}
114 then True {}
109 else containsBadUrlChar (drop 1 s)
115 else containsBadUrlChar (drop 1 s)
@@ -131,16 +137,17 @@ verifyFederationAuth = \authorization ->
131
137
132clampTtl = \ttl -> if ttl < 1 then 1 else if ttl > maxPresenceTtl then maxPresenceTtl else ttl
138clampTtl = \ttl -> if ttl < 1 then 1 else if ttl > maxPresenceTtl then maxPresenceTtl else ttl
133
139
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+
134safeCompareLoop = \s1 s2 result ->
145safeCompareLoop = \s1 s2 result ->
135 if s1 == "" && s2 == ""
146 if s1 == "" && s2 == ""
136 then result
147 then result
137− else if s1 == ""
138− then safeCompareLoop "" (drop 1 s2) (result + 1)
139− else if s2 == ""
140− then safeCompareLoop (drop 1 s1) "" (result + 1)
141− else safeCompareLoop (drop 1 s1) (drop 1 s2) (result + (if take 1 s1 == take 1 s2 then 0 else 1))
148+ else safeCompareLoop (drop 1 s1) (drop 1 s2) (result + (if take 1 s1 == take 1 s2 then 0 else 1))
142
149
143−safeCompare = \a b -> safeCompareLoop a b 0 == 0
150+safeCompare = \a b -> sameLength a b && safeCompareLoop a b 0 == 0
144
151
145sendToEach = \servers action ->
152sendToEach = \servers action ->
146 if count servers == 0
153 if count servers == 0
@@ -675,7 +682,7 @@ serve = \req -> case req of
675 if p.server == serverHost then acc
682 if p.server == serverHost then acc
676 else if count (do a <- acc; where a == p.server; yield a) > 0 then acc
683 else if count (do a <- acc; where a == p.server; yield a) > 0 then acc
677 else union acc [p.server]) [] finalPresence
684 else union acc [p.server]) [] finalPresence
678− yield {appliedKeys: (if withinCapacity then union appliedOnline appliedOffline else union appliedOnlineUpdates appliedOffline), expiredLocal: pr.expiredLocal, peerServers: pr.peerServers, gossipPeerServers: gossipPeerServers}
685+ yield {appliedKeys: (if withinCapacity then union appliedOnline appliedOffline else union appliedOnlineUpdates appliedOffline), expiredLocal: pr.expiredLocal, peerServers: pr.peerServers, gossipPeerServers: gossipPeerServers, withinCapacity: withinCapacity}
679 let validEvents = do
686 let validEvents = do
680 e <- events
687 e <- events
681 where validRequest
688 where validRequest
@@ -701,6 +708,9 @@ serve = \req -> case req of
701 where count (do a <- gossipResult.appliedKeys; where a.pubkey == e.pubkey; where a.server == e.server; yield a) > 0
708 where count (do a <- gossipResult.appliedKeys; where a.pubkey == e.pubkey; where a.server == e.server; yield a) > 0
702 yield e
709 yield e
703 respond {ok: validRequest}
710 respond {ok: validRequest}
711+ if not gossipResult.withinCapacity
712+ then println ("gossip: presence at capacity (" ++ show maxPresenceEntries ++ "), dropping new entries from " ++ fromServer)
713+ else yield {}
704 if validRequest && cappedHops > 1 && not (federationSecret == "") && count effectiveEvents > 0
714 if validRequest && cappedHops > 1 && not (federationSecret == "") && count effectiveEvents > 0
705 then do
715 then do
706 let senderServers = fold (\acc e ->
716 let senderServers = fold (\acc e ->
@@ -785,6 +795,18 @@ serve = \req -> case req of
785 respond {ok: pruneResult.validAck}
795 respond {ok: pruneResult.validAck}
786 broadcastOfflineGossip t pruneResult.peerServers pruneResult.expiredLocal
796 broadcastOfflineGossip t pruneResult.peerServers pruneResult.expiredLocal
787
797
798+ Prune {authorization, respond} -> do
799+ t <- now
800+ let authorized = verifyFederationAuth authorization
801+ if authorized
802+ then do
803+ result <- atomic do
804+ pr <- pruneExpired t
805+ yield pr
806+ respond {ok: True {}}
807+ broadcastOfflineGossip t result.peerServers result.expiredLocal
808+ else respond {ok: False {}}
809+
788-- ============================================
810-- ============================================
789-- Entry Point
811-- Entry Point
790-- ============================================
812-- ============================================