76464 bc low transient metering cache poisoning allows high compute transactions to bypass builder execution limits
Submitted on May 4th 2026 at 15:04:41 UTC by @Emmy for Audit Comp | Base Azul
Report ID: #76464
Report Type: Blockchain/DLT
Report severity: Low
Target: https://github.com/base/base/tree/v0.8.0-rc.28
Impacts:
Increasing network processing node resource consumption by at least 30% without brute force actions, compared to the preceding 24 hours
Description
Brief/Intro
The builder records a metering cache miss before it knows whether the transaction will actually be included. If that transaction is then rejected for a transient block-budget reason, the miss marker remains behind. When honest metering data later arrives, the builder incorrectly treats it as late data for an already-included transaction and discards it. The transaction can then be included in a later block without the execution-metering data that would have caused it to be rejected, bypassing enforced resource limits and allowing high-compute transactions through the builder.
Vulnerability Details
The Base builder uses MeteringStore to cache transaction resource usage data keyed by transaction hash. Execution metering is enforced during transaction selection when ExecutionMeteringMode::Enforce is enabled and a transaction has cached metering data.
The vulnerable state machine is:
A transaction is considered by the builder.
The builder calls
metering_provider.get(tx_hash).If the cache misses,
MeteringStore::get()recordsneeded_at[tx_hash] = now.The builder then performs block and transaction limit checks.
If the transaction is rejected for a transient block-budget reason, it is not included and is not removed from the pool.
The stale
needed_atmarker remains.When honest metering later arrives,
MeteringStore::insert()seesneeded_at[tx_hash]and drops the metering result.The transaction can be retried in the next block with no metering data, causing execution-metering limits to be skipped.
The root cause is that needed_at is used as if every cache miss means the transaction was included without metering. That invariant is false. A transaction can miss metering and still be rejected before execution due to a transient limit such as current block uncompressed size, block gas, DA bytes, or DA footprint.
In MeteringStore::get(), every cache miss records a needed_at entry:
In MeteringStore::insert(), any transaction with a needed_at entry is treated as a late-arriving metering result and is not cached:
The builder does clear needed_at in the specific MeteringDataPending path:
However, that cleanup is not performed for transient static rejections. For example, if the transaction exceeds the current block's remaining uncompressed-size budget, the builder only invalidates it for the current iterator:
BlockUncompressedSizeExceeded is not permanent. The transaction remains in the pool and can be included in a later block. Since the stale needed_at entry is not cleared, the first honest metering result for that transaction is discarded.
The execution-metering enforcement then fails open. The limit checks only run when metering data is present:
Therefore, once the honest metering result has been dropped, the transaction is treated as unmetered and the configured execution-time limit is skipped.
This issue does not require forged metering data, an unauthenticated RPC call, a trusted admin, or compromised infrastructure. The PoC uses the real MeteringStore and a normal builder flow. The attacker only needs to submit a transaction that first loses to a transient block-budget constraint, then becomes eligible in a later block after its metering result has been discarded.
Impact Details
This is a Blockchain/DLT Medium severity issue under Immunefi v2.2: high compute consumption by validator/mining nodes.
The builder's execution metering is intended to prevent transactions with excessive predicted execution time or state-root cost from entering flashblocks when enforcement is enabled. This bug breaks that protection. A transaction with honest metering data above the configured per-transaction execution limit can still be included if it first encounters a transient rejection that leaves a stale needed_at marker.
The impact is resource griefing against the builder path:
High-compute transactions can bypass
max_execution_time_per_tx_us.State-root or execution-heavy transactions can be admitted without their metering data being applied.
The builder may spend execution and state-root resources on transactions that operator policy explicitly configured it to reject.
The attack can be repeated by arranging for expensive transactions to first be considered while a transient block budget is exhausted.
The issue does not demonstrate theft of funds, protocol insolvency, network shutdown, or a chain split. The appropriate impact is high compute consumption / protocol griefing.
References
Proof of Concept
The following test demonstrates the bug using the real MeteringStore.
Place this file at:
The test also requires base-builder-metering.workspace = true under [dev-dependencies] in base/crates/builder/core/Cargo.toml.
Run the PoC:
Expected result:
Was this helpful?