Skip to content

config: reject overflowing seconds in CustomDuration.UnmarshalJSON - #1703

Open
RajeshRajendiran wants to merge 2 commits into
cloudflare:masterfrom
RajeshRajendiran:fix-connecttimeout-json-overflow
Open

config: reject overflowing seconds in CustomDuration.UnmarshalJSON#1703
RajeshRajendiran wants to merge 2 commits into
cloudflare:masterfrom
RajeshRajendiran:fix-connecttimeout-json-overflow

Conversation

@RajeshRajendiran

@RajeshRajendiran RajeshRajendiran commented Jul 26, 2026

Copy link
Copy Markdown

Summary

  • CustomDuration.UnmarshalJSON (config/configuration.go) parses the JSON wire value as seconds, then multiplies by time.Second to build a time.Duration. This is intentional (see the comment above CustomDuration) so remote/API-pushed configs aren't limited by JS's 32-bit-safe-integer nanosecond convention, and it's mirrored by existing tests (TestMarshalUnmarshalOriginRequest).
  • However there was no bounds check: a caller supplying a value on the order of 1e10 (e.g. assuming nanoseconds, the usual time.Duration convention) overflows int64 when multiplied by 1e9, silently wrapping into a large negative time.Duration.
  • That negative duration flows straight into net.Dialer.Timeout for originRequest.connectTimeout (and any other CustomDuration field arriving via remote JSON config). A negative/expired deadline makes net.Dialer.DialContext fail instantly with dial tcp: ... i/o timeout — before any socket()/connect() syscall — with no indication that the config value was the problem.
  • This fix bounds-checks the parsed seconds value against the safe int64 range before multiplying, returning a clear unmarshal error instead of silently producing a corrupted, confusing timeout.
  • Also fixes pre-existing lint issues (errcheck, staticcheck, testifylint, gosec) in the two touched files - this repo's .golangci.yaml sets whole-files: true, so editing a file surfaces lint findings across the entire file, not just the diff.

Test plan

  • Added TestCustomDurationUnmarshalJSONOverflow in config/configuration_test.go covering: the reported repro value, a normal value, the boundary value, and one past the boundary.
  • go build ./...
  • go test -race ./config/... ./orchestration/...
  • gofmt -l / go vet ./config/...
  • make lint (golangci-lint run ./config/... -> 0 issues)

Fixes #1702

CustomDuration's JSON wire format is documented as seconds (unlike its
YAML format, which uses time.Duration's own "10s"-style parser). When
a caller instead supplies a value on the order of 1e10 (e.g. mistaking
it for nanoseconds, which is the usual convention for time.Duration),
multiplying by time.Second overflows int64 and silently wraps into a
large negative time.Duration.

That negative duration is passed straight into net.Dialer.Timeout for
originRequest.connectTimeout, so the dial context is already expired
before net.Dialer is ever invoked: dials fail instantly with "i/o
timeout" and zero socket()/connect() syscalls, with no indication the
config value itself was invalid.

Bounds-check the parsed seconds value against the int64 range before
multiplying, returning a descriptive unmarshal error instead of
silently producing a corrupted duration.

Fixes cloudflare#1702
This repo's golangci-lint config uses whole-files: true, so editing
configuration.go/configuration_test.go surfaces lint issues across
the entire file, not just the diff. Address them:

- errcheck: check file.Close() via defer func() { _ = file.Close() }()
- staticcheck: de-capitalize ErrNoConfigFile message (ST1005), drop
  redundant embedded-field selectors in CustomDuration (QF1008)
- testifylint: use require.NoError for error assertions that
  subsequent lines depend on, assert.InDelta for float comparison,
  assert.True instead of assert.Equal(true, ...)
- gosec: nolint with justification for G304/G703 (paths are local
  config search paths, not attacker-controlled) and G301 (pre-existing
  directory permissions; tightening them is a separate behavior
  change out of scope for this PR)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

🐛Origin dial fails instantly with i/o timeout when originRequest.connectTimeout is set via remote-managed tunnel config

1 participant