Skip to content

util/limiter: don't panic on Allow for a zero-value limiter#20267

Open
sueun-dev wants to merge 1 commit into
tailscale:mainfrom
sueun-dev:fix/limiter-zero-value-panic
Open

util/limiter: don't panic on Allow for a zero-value limiter#20267
sueun-dev wants to merge 1 commit into
tailscale:mainfrom
sueun-dev:fix/limiter-zero-value-panic

Conversation

@sueun-dev

Copy link
Copy Markdown

The Limiter type doc says:

The zero value is a valid limiter that rejects all requests. A useful limiter must specify a Size, Max and RefillInterval.

But calling Allow on a zero-value Limiter panics instead of rejecting:

panic: runtime error: integer divide by zero
    tailscale.com/util/limiter.(*Limiter[...]).updateBucketLocked .../util/limiter/limiter.go:139
    tailscale.com/util/limiter.(*Limiter[...]).allowBucketLocked .../util/limiter/limiter.go:124
    tailscale.com/util/limiter.(*Limiter[...]).allow .../util/limiter/limiter.go:104

A fresh bucket starts at cur = Max (0 for the zero value), so allowBucketLocked sees cur <= 0 and calls updateBucketLocked, which does int64(timeDelta / lm.RefillInterval). RefillInterval is 0 for the zero value, so the division panics.

This is the same class of bug as #18396 ("don't panic when dumping a new Limiter"), which fixed the DumpHTML path but not the Allow path.

Fix: return early from updateBucketLocked when RefillInterval <= 0. With no refill rate there is nothing to refill, so the bucket stays at 0 tokens and Allow rejects the request, matching the documented behavior. Configured limiters (RefillInterval > 0) are unaffected.

Added TestZeroValueRejects, which panics before this change and passes after. go test ./util/limiter/, go vet, and gofmt are clean.

The Limiter type doc says "The zero value is a valid limiter that
rejects all requests", but calling Allow on a zero-value Limiter panics
with an integer divide by zero. allowBucketLocked sees a fresh bucket
with cur <= 0 and calls updateBucketLocked, which computes
int64(timeDelta / lm.RefillInterval); RefillInterval is 0 for the zero
value, so the division panics.

Guard updateBucketLocked against a non-positive RefillInterval and
return early. With no refill rate there is nothing to refill, so the
bucket stays at 0 tokens and Allow rejects the request, matching the
documented behavior. tailscale#18396 fixed the same class of panic for DumpHTML;
this covers the Allow path.

Signed-off-by: Sueun Cho <[email protected]>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: eb4816bdce

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread util/limiter/limiter.go
Comment on lines +134 to +139
if lm.RefillInterval <= 0 {
// No refill rate configured (e.g. the zero-value limiter, which
// is documented to reject all requests). Tokens never regenerate,
// so there's nothing to update, and dividing by the interval below
// would panic.
return

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid caching keys for zero-value limiters

In the zero-value case this returns after Allow has already created and inserted a bucket via getBucketLocked; because that cache is initialized with MaxEntries: lm.Size and util/lru treats MaxEntries == 0 as unbounded, every distinct denied key is retained permanently. If a default Limiter is used on attacker-controlled keys, this fixes the panic but turns the documented “rejects all requests” behavior into unbounded memory growth; the zero/disabled path needs to reject before allocating or otherwise avoid storing buckets.

Useful? React with 👍 / 👎.

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.

1 participant