Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

x/time/rate: Limiter depends on implementation-defined large float to int conversion #71154

Open
tomcoupland opened this issue Jan 7, 2025 · 7 comments
Labels
BugReport Issues describing a possible bug in the Go implementation. help wanted NeedsFix The path to resolution is known, but the work has not been done.
Milestone

Comments

@tomcoupland
Copy link

Go version

go version go1.22.1 darwin/arm64

Output of go env in your module/workspace:

GO111MODULE='auto'
GOARCH='arm64'
GOBIN=''
GOCACHE='/Users/tomcoupland/Library/Caches/go-build'
GOENV='/Users/tomcoupland/Library/Application Support/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFLAGS=''
GOHOSTARCH='arm64'
GOHOSTOS='darwin'
GOINSECURE=''
GOMODCACHE='/Users/tomcoupland/pkg/mod'
GONOPROXY='github.com/monzo'
GONOSUMDB='github.com/monzo'
GOOS='darwin'
GOPATH='/Users/tomcoupland'
GOPRIVATE='github.com/monzo'
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/Users/tomcoupland/pkg/mod/golang.org/[email protected]'
GOSUMDB='sum.golang.org'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/Users/tomcoupland/pkg/mod/golang.org/[email protected]/pkg/tool/darwin_arm64'
GOVCS=''
GOVERSION='go1.23.1'
GODEBUG=''
GOTELEMETRY='local'
GOTELEMETRYDIR='/Users/tomcoupland/Library/Application Support/go/telemetry'
GCCGO='gccgo'
GOARM64='v8.0'
AR='ar'
CC='clang'
CXX='clang++'
CGO_ENABLED='1'
GOMOD='/Users/tomcoupland/src/github.com/monzo/wearedev/go.mod'
GOWORK=''
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
PKG_CONFIG='pkg-config'
GOGCCFLAGS='-fPIC -arch arm64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -ffile-prefix-map=/var/folders/ll/3h483n6j4jbd64x_tjbgwzd40000gn/T/go-build4023913863=/tmp/go-build -gno-record-gcc-switches -fno-common'

What did you do?

fmt.Printf("GOARCH: %s\n", runtime.GOARCH)
limiter := rate.NewLimiter(1e-10, 1)

if limiter.Allow() {
  fmt.Println("allow")
} else {
  fmt.Println("not allow")
}
if limiter.Allow() {
  fmt.Println("allow")
} else {
  fmt.Println("not allow")
}

Playground

There is a conversion from float64 to int64 in durationFromTokens, when the final time.Duration is created:

func (limit Limit) durationFromTokens(tokens float64) time.Duration {
	if limit <= 0 {
		return InfDuration
	}
	seconds := tokens / float64(limit)
	return time.Duration(float64(time.Second) * seconds)
}

When limiters are configured with small values (1e-10) this overflows and allows request through that should be blocked. amd64 does not saturate int64's when converting, where as arm64 does. Therefore this code is safe on arm64, but not amd64.

Results of running:

GOARCH = "amd64", see "allow", "allow"
GOARCH = 'arm64', see "allow", "not allow"

This leads to the second "allow" on amd64. I'm preparing a PR with some overflow protection.

What did you see happen?

When GOARCH = "amd64", see "allow", "allow"
Whe GOARCH = 'arm64', see "allow", "not allow"

What did you expect to see?

Regardless of GOARCH: "allow", "not allow"

The first attempt should succeed, due to the burst of 1, but the second should be denied.

@tomcoupland tomcoupland changed the title import/path: Limiter depends on implementation-defined large float to int conversion x/time/rate: Limiter depends on implementation-defined large float to int conversion Jan 7, 2025
@gopherbot gopherbot added this to the Unreleased milestone Jan 7, 2025
@gabyhelp gabyhelp added the BugReport Issues describing a possible bug in the Go implementation. label Jan 7, 2025
@seankhliao
Copy link
Member

Is this something that really needs to be fixed? rate = 1e-10 corresponds to a limit of 1 event every 317 years. I think it's more likely that you've misconfigured your program than that being a reasonable limit.

@tomcoupland
Copy link
Author

We are developing an automated rate limiting system, that changes the limits dynamically, based on various factors. While we could (and probably will) add logic to prevent these small values escaping, it's a small enough fix to the library to guard against these scenarios, and would prevent any accidents occurring, both for us, and other folks using this rate limiter.

As it stands, the library does nothing to prevent this configuration, and it results in the opposite of what you'd expect.

@ianlancetaylor
Copy link
Member

Would you like to send a patch?

@tomcoupland
Copy link
Author

I'm just getting it ready, will post it tomorrow. I accept it's pretty far out there, but solving it at this level would shut to door on a class of future bugs.

@prattmic prattmic added the NeedsFix The path to resolution is known, but the work has not been done. label Jan 8, 2025
@tomcoupland
Copy link
Author

https://go-review.googlesource.com/c/time/+/641335 - patch submitted

@gopherbot
Copy link
Contributor

Change https://go.dev/cl/641336 mentions this issue: rate: prevent overflows when calculating durationFromTokens

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
BugReport Issues describing a possible bug in the Go implementation. help wanted NeedsFix The path to resolution is known, but the work has not been done.
Projects
None yet
Development

No branches or pull requests

6 participants