Attackathon _ Fuel Network 32491 - [Smart Contract - Low] Incorrect PushA PopA Mask Calculation
Submitted on Sun Jun 23 2024 22:48:44 GMT-0400 (Atlantic Standard Time) by @anatomist for Attackathon | Fuel Network
Report ID: #32491
Report type: Smart Contract
Report severity: Low
Target: https://github.com/FuelLabs/sway/tree/7b56ec734d4a4fda550313d448f7f20dba818b59
Impacts:
Incorrect sway compilation leading to incorrect bytecode
Description
Brief/Intro
emit_pusha_popa only handles the first def_reg of each instruction, and could lead to corruption of register in function caller.
Vulnerability Details
When calling a function, we need to store caller registers onto stack and restore it later, so that it doesn't get overwritten by the callee. The compiler does a small optimization by storing only the registers that callee modifies to reduce the amount of stack memory writes.
However, the compiler incorrectly assumes each instruction only modifies one register, so if there are more than one register modified, the remaining registers will not be pushed and popped from stack. Without a push and pop for modified registers, caller register can be modified unexpectedly and cause incorrect execution result.
let reg = match &op.opcode {
Either::Right(ControlFlowOp::PushAll(label)) => {
active_sets.insert(*label);
None
}
Either::Right(ControlFlowOp::PopAll(label)) => {
active_sets.swap_remove(label);
None
}
Either::Left(alloc_op) => alloc_op.def_registers().into_iter().next(),
Either::Right(ctrl_op) => ctrl_op.def_registers().into_iter().next(),
};
if let Some(reg) = reg {
for active_label in active_sets.clone() {
reg_sets
.entry(active_label)
.and_modify(|regs: &mut BTreeSet<AllocatedRegister>| {
regs.insert(reg.clone());
})
.or_insert_with(|| {
BTreeSet::from_iter(std::iter::once(reg).cloned())
});
}
}
For example, we compile this code
And the allocated abstract instruction after compiling the code is this
The store_read_24 function uses pshl i23 to store caller registers, which does not include the $r3 register. But $r3 register in modified by srw $r2 $r3 $r1. This causes caller registers to be modified after the call. And any usage of the $r3 register after this will have incorrect value in it.
Impact Details
As usual, it is hard to come up with a precise impact estimation of incorrect code generation because it depends on what code the user writes. The best case scenario would be contracts that run into those bugs getting bricked, and the worst case scenario would be that incorrect program behaviors lead to loss of funds.
References
https://github.com/FuelLabs/sway/blob/c186d93155d0ef9f4432773be08c7da6d0fdf6de/sway-core/src/asm_generation/fuel/allocated_abstract_instruction_set.rs#L63https://github.com/FuelLabs/sway/blob/c186d93155d0ef9f4432773be08c7da6d0fdf6de/sway-core/src/asm_lang/allocated_ops.rs#L341
Proof of concept
Proof of Concept
This test would fail because c in the asm block of setup is overwritten by store_read srw a b slot unexpectedly.
Last updated
Was this helpful?