From 302c092cced3d0e8e7b029bcf193abfb7bf6daf0 Mon Sep 17 00:00:00 2001 From: Stefan Prodan Date: Mon, 8 Jun 2026 23:58:36 +0300 Subject: [PATCH] Fix panic on GET /chunked from integer overflow HttpServerTimeout is a time.Duration (nanoseconds); the default-delay branch scaled it by time.Second again, overflowing int64 to a negative value and passing it to rand.Intn, which panics. The bare /chunked route (no wait param) hit this on every request. Convert the timeout to whole seconds in a guarded helper that keeps rand.Intn's argument positive, and add a regression test. --- pkg/api/http/chunked.go | 14 +++++++++++++- pkg/api/http/chunked_test.go | 17 +++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/pkg/api/http/chunked.go b/pkg/api/http/chunked.go index 8438cee..d758d25 100644 --- a/pkg/api/http/chunked.go +++ b/pkg/api/http/chunked.go @@ -26,7 +26,7 @@ func (s *Server) chunkedHandler(w http.ResponseWriter, r *http.Request) { delay, err := strconv.Atoi(vars["wait"]) if err != nil { - delay = rand.Intn(int(s.config.HttpServerTimeout*time.Second)-10) + 10 + delay = randomDelaySeconds(s.config.HttpServerTimeout) } flusher, ok := w.(http.Flusher) @@ -46,3 +46,15 @@ func (s *Server) chunkedHandler(w http.ResponseWriter, r *http.Request) { flusher.Flush() } + +// randomDelaySeconds returns a random delay in seconds within [10, timeout), +// used when no explicit wait is provided. timeout is a time.Duration, so it is +// converted to whole seconds; the upper bound is clamped to keep rand.Intn's +// argument positive (it panics on a non-positive argument). +func randomDelaySeconds(timeout time.Duration) int { + maxDelay := int(timeout / time.Second) + if maxDelay <= 11 { + maxDelay = 12 + } + return rand.Intn(maxDelay-10) + 10 +} diff --git a/pkg/api/http/chunked_test.go b/pkg/api/http/chunked_test.go index 167b3b1..d753438 100644 --- a/pkg/api/http/chunked_test.go +++ b/pkg/api/http/chunked_test.go @@ -5,6 +5,7 @@ import ( "net/http/httptest" "regexp" "testing" + "time" ) func TestChunkedHandler(t *testing.T) { @@ -33,3 +34,19 @@ func TestChunkedHandler(t *testing.T) { rr.Body.String(), expected) } } + +// TestRandomDelaySeconds covers the default-delay branch taken by the bare +// /chunked route (no {wait} value). This used to panic because the +// duration-to-seconds math overflowed int64 and handed rand.Intn a negative +// argument. Every timeout must yield a valid delay in [10, max] without panicking. +func TestRandomDelaySeconds(t *testing.T) { + timeouts := []time.Duration{30 * time.Second, 12 * time.Second, time.Second, 0, -1} + for _, timeout := range timeouts { + for range 100 { + d := randomDelaySeconds(timeout) + if d < 10 { + t.Fatalf("timeout %s: delay %d below floor of 10", timeout, d) + } + } + } +}