From 5cf2ab4f693dc9645b021e13c2c643a5e407383d Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Sat, 31 Oct 2026 14:53:20 +0100 Subject: [PATCH] fix(tuic): bound the timeout settings at a day max_idle_time and authentication_timeout are second counts multiplied into a Duration with no bound, so a stored value past the int64 range wrapped negative and the QUIC listener panicked on the first ClientHello, taking the panel down at every start until the row was fixed. Saves now refuse more than a day, and a stored row past it fails its own listener start instead. --- internal/tuic/server.go | 8 ++++++++ internal/tuic/server_settings_test.go | 16 ++++++++++++++++ internal/web/service/inbound_tuic_test.go | 6 ++++++ internal/web/service/tuic_settings.go | 13 +++++++++++++ 4 files changed, 43 insertions(+) diff --git a/internal/tuic/server.go b/internal/tuic/server.go index 1245b335c..4c0ff5e05 100644 --- a/internal/tuic/server.go +++ b/internal/tuic/server.go @@ -91,6 +91,11 @@ func NewServer(inst Instance, relay *SocksRelay) (*Server, error) { if authTimeout <= 0 { authTimeout = 3 } + // A second count past a day only wraps the Duration below; rows stored before the save + // path bounded it fail here instead of panicking the listener on its first handshake. + if maxIdle > MaxTimeoutSeconds || authTimeout > MaxTimeoutSeconds { + return nil, fmt.Errorf("tuic: max_idle_time and authentication_timeout must not exceed %d seconds", MaxTimeoutSeconds) + } maxUdpSize := inst.MaxUdpRelayPacketSize if maxUdpSize <= 0 { maxUdpSize = 1500 @@ -636,6 +641,9 @@ type packetReassembler struct { packets map[packetFragmentKey]*packetReassembly } +// MaxTimeoutSeconds bounds the second-count settings, far above any useful timeout. +const MaxTimeoutSeconds = 86400 + const ( maxSafeUdpRelayPacketSize = maxSocksUdpDatagramSize - 262 maxLegacyUdpRelayPacketSize = maxSocksUdpDatagramSize diff --git a/internal/tuic/server_settings_test.go b/internal/tuic/server_settings_test.go index f5f7eb3b8..c1d6f1458 100644 --- a/internal/tuic/server_settings_test.go +++ b/internal/tuic/server_settings_test.go @@ -127,3 +127,19 @@ func TestEnsureStartupFailureHonorsInboundLogThreshold(t *testing.T) { }) } } + +// Seconds turn into a Duration by multiplication; a pushed value past the int64 range +// wrapped negative and the QUIC listener panicked on the first ClientHello, every start. +func TestNewServerRefusesTimeoutsPastADay(t *testing.T) { + cert, key := generateTestCert(t) + for name, inst := range map[string]Instance{ + "max_idle_time": {MaxIdleTime: 1 << 40}, + "authentication_timeout": {AuthenticationTimeout: MaxTimeoutSeconds + 1}, + } { + inst.Listen, inst.Certificate, inst.PrivateKey = "127.0.0.1", string(cert), string(key) + if s, err := NewServer(inst, &SocksRelay{Addr: "127.0.0.1:1"}); err == nil { + _ = s.Close() + t.Errorf("%s past a day was accepted", name) + } + } +} diff --git a/internal/web/service/inbound_tuic_test.go b/internal/web/service/inbound_tuic_test.go index 4b076be23..84d1b06c5 100644 --- a/internal/web/service/inbound_tuic_test.go +++ b/internal/web/service/inbound_tuic_test.go @@ -359,6 +359,12 @@ func TestNormalizeTuicSettingsCanonicalizesCongestionAndPacketLimit(t *testing.T if err := normalizeTuicSettings(ib); err == nil { t.Fatal("unsupported congestion controller was accepted") } + for _, settings := range []string{`{"max_idle_time":2199023255552}`, `{"server":{"authentication_timeout":86401}}`} { + ib.Settings = settings + if err := normalizeTuicSettings(ib); err == nil { + t.Fatalf("a timeout past a day was accepted: %s", settings) + } + } } func TestTuicRelayIdentitySurvivesNativeEligibilityChanges(t *testing.T) { diff --git a/internal/web/service/tuic_settings.go b/internal/web/service/tuic_settings.go index 4d567f558..16ced47c0 100644 --- a/internal/web/service/tuic_settings.go +++ b/internal/web/service/tuic_settings.go @@ -90,6 +90,19 @@ func normalizeTuicSettingsBlock(settings map[string]json.RawMessage) error { encoded, _ := json.Marshal(value) settings["udp_relay_mode"] = encoded } + for _, key := range []string{"max_idle_time", "authentication_timeout"} { + raw, ok := settings[key] + if !ok { + continue + } + var value int64 + if err := json.Unmarshal(raw, &value); err != nil { + return fmt.Errorf("TUIC %s must be an integer: %w", key, err) + } + if value > tuic.MaxTimeoutSeconds { + return fmt.Errorf("TUIC %s must not exceed %d seconds", key, tuic.MaxTimeoutSeconds) + } + } if raw, ok := settings["max_udp_relay_packet_size"]; ok { var value int if err := json.Unmarshal(raw, &value); err != nil {