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

Enable arithmetic lint in rate-limiter #6875

Open
michaelsproul opened this issue Jan 28, 2025 · 0 comments
Open

Enable arithmetic lint in rate-limiter #6875

michaelsproul opened this issue Jan 28, 2025 · 0 comments

Comments

@michaelsproul
Copy link
Member

The rate-limiter is called prior to application-level checks on request validity, so we should be careful to avoid integer overflow in its code, as it is operating on untrusted data.

There is integer overflow here:

pub fn allows(
&mut self,
time_since_start: Duration,
key: &Key,
tokens: u64,
) -> Result<(), RateLimitedErr> {
let time_since_start = time_since_start.as_nanos() as u64;
let tau = self.tau;
let t = self.t;
// how long does it take to replenish these tokens
let additional_time = t * tokens;

The tokens value is derived from untrusted user input:

pub fn allows<Item: RateLimiterItem>(
&mut self,
peer_id: &PeerId,
request: &Item,
) -> Result<(), RateLimitedErr> {
let time_since_start = self.init_time.elapsed();
let tokens = request.max_responses().max(1);

The count from the request is untrusted and can be set arbitrarily:

pub fn max_responses(&self) -> u64 {
match self {
RequestType::Status(_) => 1,
RequestType::Goodbye(_) => 0,
RequestType::BlocksByRange(req) => *req.count(),
RequestType::BlocksByRoot(req) => req.block_roots().len() as u64,
RequestType::BlobsByRange(req) => req.max_blobs_requested::<E>(),
RequestType::BlobsByRoot(req) => req.blob_ids.len() as u64,
RequestType::DataColumnsByRoot(req) => req.data_column_ids.len() as u64,
RequestType::DataColumnsByRange(req) => req.max_requested::<E>(),
RequestType::Ping(_) => 1,
RequestType::MetaData(_) => 1,
RequestType::LightClientBootstrap(_) => 1,
RequestType::LightClientOptimisticUpdate => 1,
RequestType::LightClientFinalityUpdate => 1,
RequestType::LightClientUpdatesByRange(req) => req.count,
}
}

This was briefly fixed for BlobsByRange requests on unstable when we implemented a size check during decoding, but will regress again when this PR is merged:

The fix that existed in unstable was coincidental, so I think it is better if we address the root cause and avoid overflowing integer arithmetic in the rate limiter altogether. We can probably replace most uses of +, -, * by their saturating_ equivalents to get the right semantics.

We can also enable the arithmetic_side_effects lint, which we use in consensus crates. See:

This issue is not deemed security-sensitive because other mechanisms exist to defend the node from peers that would bypass the rate limiter. Any request with an invalid count will be rejected by application-level checks and result in the peer being quickly banned.

Thanks to @gln for reporting this bug as part of the Immunefi Ethereum Attackathon.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant