on master help sign in
server.knot: saturate decimalValueGo accumulator to prevent i64 overflow panic

decimalValueGo is reached from isBadServerName via isReservedIPv4 on peer-supplied input (/federation/gossip fromServer only passes a length/lowercase check at the route boundary). A 19+-digit octet like "9999999999999999999.0.0.1" would overflow i64 and abort the request with an unhandled panic. Saturate the accumulator at 2^32 — above any valid IPv4 octet (0-255) but safe from overflow. Valid octets are unaffected; an oversized first octet still reads as >= 224 (reserved, fail-closed). Third deep server audit. No other correctness bugs found — resource caps, HTTP semantics, persistence, race primitive, fetch behavior, and config interactions all verified sound under scrutiny.

commit9118bb4a7f1d76d6228df056f0959f326caa5322 authoriko <mail@iko.soy> dateSun 12 Jul 2026, 02:15 (3 months ago) parent4acbb40
1 file changed+12−1
server.knot+12−1
server.knot+12−1
@@ -539,6 +539,17 @@ digitValue = \c -> if c == "0" then 0 else if c == "1" then 1 else if c == "2" t
539539-- Value of an all-digits string (e.g. "172" -> 172); -1 when empty or
540540-- non-numeric. `chars` is fine for the membership guard (set semantics don't
541541-- matter there); the actual parse walks the string in order via take/drop.
542+--
543+-- The accumulator saturates at 2^32 so an overlong all-digits octet cannot
544+-- overflow i64 and panic the handler. `isBadServerName` reaches this via
545+-- `isReservedIPv4` on a peer-supplied name (`/federation/gossip`'s `fromServer`
546+-- only passes a length/lowercase check at the route boundary), so a 19+-digit
547+-- octet like "9999999999999999999.0.0.1" would otherwise abort the request with
548+-- an unhandled `integer overflow in ... * 10`. A real IPv4 octet is 0-255 and
549+-- every `isReservedIPv4` comparison is against a constant <= 255, so saturating
550+-- above that range preserves the result for valid octets while an oversized
551+-- first octet still reads as `>= 224` (reserved → fail-closed). The `* 10`
552+-- stays safe because `acc` is capped at 2^32 before each step.
542553decimalValue : Text -> Int
543554decimalValue = \s -> if s == "" || not all (\c -> contains c "0123456789") (chars s)
544555then -1
@@ -546,7 +557,7 @@ else decimalValueGo 0 s
546557decimalValueGo : Int -> Text -> Int
547558decimalValueGo = \acc s -> if s == ""
548559then acc
549−else decimalValueGo (acc * 10 + digitValue (take 1 s)) (drop 1 s)
560+else decimalValueGo (min 4294967296 (acc * 10 + digitValue (take 1 s))) (drop 1 s)
550561
551562-- Substring of `s` before / after its first ",". Same shape as `beforeDot` /
552563-- `afterDot`, on a different separator.