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:
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.
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.
Summary
PeriodicResource::current_leftcomputes the refilled amount withself.latest_left + refill_amount— a plain+onu64. BecausePeriodicResourcebacks fee/tx-limit exemptions (Exemption), this is on a metering path where a wrap silently grants (or denies) resources. There is nochecked_add/saturating_add.Affected file(s)
src/inscriptive/privileges_manager/elements/exemption/periodic_resource/periodic_resource.rsLocation
src/inscriptive/privileges_manager/elements/exemption/periodic_resource/periodic_resource.rs:41-81:Two overflow sites in this expression:
time_passed * self.limit— iflimitis large andtime_passedis non-trivial, the product wraps before the/ self.perioddivision. This makesrefill_amountwrong (small), which understates the refill.self.latest_left + refill_amount— even with a correctrefill_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
PeriodicResourcemodels a leaky/refilling bucket:limitis the cap,periodis the refill window,latest_leftis the residual. The refill ispro-rataby elapsed time.u64is wide enough that overflow is unlikely under modest values, but:limitandperiodare governance-set params (seeparams_manager); a misconfiguration (hugelimit) makes overflow reachable.refill_and_consume(periodic_resource.rs:84-110) usescurrent_leftto decide whether a consume fits, so a wrappedcurrent_leftdirectly affects whether a fee/entry exemption is granted.Impact