Skip to content

[privileges_manager] PeriodicResource refill add can overflow (no checked arithmetic in fee-metering path) #17

Description

@GideonBature

Summary

PeriodicResource::current_left computes the refilled amount with self.latest_left + refill_amount — a plain + on u64. Because PeriodicResource backs fee/tx-limit exemptions (Exemption), this is on a metering path where a wrap silently grants (or denies) resources. There is no checked_add/saturating_add.

Affected file(s)

  • src/inscriptive/privileges_manager/elements/exemption/periodic_resource/periodic_resource.rs

Location

src/inscriptive/privileges_manager/elements/exemption/periodic_resource/periodic_resource.rs:41-81:

pub fn current_left(
    &self,
    current_timestamp: u64,
    latest_activity_timestamp: u64,
) -> Option<u64> {
    if latest_activity_timestamp > current_timestamp {
        return None;
    }
    let time_passed = current_timestamp - latest_activity_timestamp;
    match time_passed >= self.period {
        true => Some(self.limit),
        false => {
            let refill_amount = (time_passed * self.limit) / self.period;   // <-- (1) multiply can overflow too
            let new_amount = self.latest_left + refill_amount;              // <-- (2) add can overflow
            let new_amount = new_amount.min(self.limit);
            Some(new_amount)
        }
    }
}

Two overflow sites in this expression:

  1. time_passed * self.limit — if limit is large and time_passed is non-trivial, the product wraps before the / self.period division. This makes refill_amount wrong (small), which understates the refill.
  2. self.latest_left + refill_amount — even with a correct refill_amount, the add can wrap. The subsequent .min(self.limit) does not recover a wrapped value (a wrapped sum is small, so .min(limit) keeps the small wrapped value).

Root cause / analysis

PeriodicResource models a leaky/refilling bucket: limit is the cap, period is the refill window, latest_left is the residual. The refill is pro-rata by elapsed time. u64 is wide enough that overflow is unlikely under modest values, but:

  • limit and period are governance-set params (see params_manager); a misconfiguration (huge limit) makes overflow reachable.
  • The downstream refill_and_consume (periodic_resource.rs:84-110) uses current_left to decide whether a consume fits, so a wrapped current_left directly affects whether a fee/entry exemption is granted.

Impact

  • Wrong refill accounting → exemptions granted incorrectly (over- or under-granted).
  • Silent (no panic), so undetectable without accounting cross-checks.
  • Metering/fee paths are exactly where silent arithmetic errors are most costly.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions