Title: pallet-xqvm execute: unbounded sample alloc + step_limit 0 is unlimited (validator OOM / hang)
Summary
pallet-xqvm::execute is a signed, permissionless extrinsic. Weight is pre-charged from the caller-supplied step_limit, then the interpreter runs.
Two independent metering holes:
-
Memory is not metered. BSMX / SSMX / XSMX take a stack i64, convert to usize, and allocate a dense Vec<i64> of that length in one instruction (xquad/xqvm/src/vm.rs). WeightPerStep is 1000 ref_time. A 3-instruction program (PUSH huge; BSMX; HALT) pays for a handful of steps and can request gigabytes.
-
Zero step limit is treated as infinity. The pallet only checks step_limit <= MaxStepLimit. 0 passes. Vm::set_step_limit maps 0 to u64::MAX. Weight charged is execute_base + WeightPerStep * 0. The node then runs until halt, OOM, or process kill. Substrate will not stop the extrinsic mid-loop because announced weight was tiny.
The crate has xqvm::verifier::verify. The pallet only Program::decodes (magic/CRC). It never calls the verifier.
Impact
Any account can stall or OOM every authoring/importing validator. Liveness of any runtime that includes this pallet.
Toy: PUSH 268435456; BSMX; HALT. Three steps. ~2GB per validator. Or step_limit=0: fee for zero steps, loop forever if the program does not halt.
Suggested fix
- Reject
step_limit == 0 on the consensus path; never map 0 to u64::MAX there.
- Cap every dense allocation (
BSMX/SSMX/XSMX, vec growth, ITER copies); charge proof-size/memory weight.
- Run
verifier::verify before Vm::run.
- Prefer
DispatchResultWithPostInfo and fail closed if an opcode would allocate above remaining weight.
Files
xquad/xqvm/src/vm.rs (BSMX/SSMX/XSMX alloc; set_step_limit(0) -> u64::MAX)
quip-validator/pallets/xqvm/src/lib.rs (execute, weight = WeightPerStep * step_limit)
quip-validator/runtime/src/configs/mod.rs (WeightPerStep)
Title: pallet-xqvm execute: unbounded sample alloc + step_limit 0 is unlimited (validator OOM / hang)
Summary
pallet-xqvm::executeis a signed, permissionless extrinsic. Weight is pre-charged from the caller-suppliedstep_limit, then the interpreter runs.Two independent metering holes:
Memory is not metered.
BSMX/SSMX/XSMXtake a stack i64, convert to usize, and allocate a denseVec<i64>of that length in one instruction (xquad/xqvm/src/vm.rs).WeightPerStepis 1000 ref_time. A 3-instruction program (PUSH huge; BSMX; HALT) pays for a handful of steps and can request gigabytes.Zero step limit is treated as infinity. The pallet only checks
step_limit <= MaxStepLimit.0passes.Vm::set_step_limitmaps0tou64::MAX. Weight charged isexecute_base + WeightPerStep * 0. The node then runs until halt, OOM, or process kill. Substrate will not stop the extrinsic mid-loop because announced weight was tiny.The crate has
xqvm::verifier::verify. The pallet onlyProgram::decodes (magic/CRC). It never calls the verifier.Impact
Any account can stall or OOM every authoring/importing validator. Liveness of any runtime that includes this pallet.
Toy:
PUSH 268435456; BSMX; HALT. Three steps. ~2GB per validator. Orstep_limit=0: fee for zero steps, loop forever if the program does not halt.Suggested fix
step_limit == 0on the consensus path; never map 0 tou64::MAXthere.BSMX/SSMX/XSMX, vec growth, ITER copies); charge proof-size/memory weight.verifier::verifybeforeVm::run.DispatchResultWithPostInfoand fail closed if an opcode would allocate above remaining weight.Files
xquad/xqvm/src/vm.rs(BSMX/SSMX/XSMX alloc;set_step_limit(0)->u64::MAX)quip-validator/pallets/xqvm/src/lib.rs(execute, weight =WeightPerStep * step_limit)quip-validator/runtime/src/configs/mod.rs(WeightPerStep)