Attackathon _ Fuel Network 32390 - [Smart Contract - Low] Unchecked Virtual Immediate Construction O
Submitted on Thu Jun 20 2024 04:55:58 GMT-0400 (Atlantic Standard Time) by @anatomist for Attackathon | Fuel Network
Report ID: #32390
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
Directly constructing VirtualImmediates without using the constructor functions with checks causes silent truncation of immediates and incorrect program behavior.
Vulnerability Details
VirtualImmediates are constructed when converting ir to Ops. The ir does not impose limits on immediates, so the values must be checked at this stage. Normally VirtualImmediates should be constructed through the VirtualImmediate::new constructors, which checks that the provided immediate is within the allowed range. However, there are some instances which directly builds the VirtualImmediate.
For example, compile_mem_copy_bytes casts a u64 bytes_len to u32, then builds a VirtualImmediate18 from it. If the bytes_len does not fit in 18 bits, the compiler would later silently truncate its value, causing the MCP to copy fewer bytes than it should.
fn compile_mem_copy_bytes(
&mut self,
instr_val: &Value,
dst_val_ptr: &Value,
src_val_ptr: &Value,
byte_len: u64,
) -> Result<(), CompileError> {
if byte_len == 0 {
// A zero length MCP will revert.
return Ok(());
}
let owning_span = self.md_mgr.val_to_span(self.context, *instr_val);
let dst_reg = self.value_to_register(dst_val_ptr)?;
let src_reg = self.value_to_register(src_val_ptr)?;
let len_reg = self.reg_seqr.next();
self.cur_bytecode.push(Op {
opcode: Either::Left(VirtualOp::MOVI(
len_reg.clone(),
VirtualImmediate18 {
value: byte_len as u32,
},
)),
comment: "get length for mcp".into(),
owning_span: owning_span.clone(),
});
self.cur_bytecode.push(Op {
opcode: Either::Left(VirtualOp::MCP(dst_reg, src_reg, len_reg)),
comment: "copy memory with mem_copy".into(),
owning_span,
});
Ok(())
}Impact Details
As usual, it is hard to precisely estimate the impact 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 loss of funds due to incorrect program logic.
References
These are the places where I think the VirtualImmediates could overflow. There are more raw construction of VirtualImmediates in the compiler, but most immediates are checked right before VirtualImmediates are built from it, so they are not affected.
https://github.com/FuelLabs/sway/blob/6e9130bbf22da7971fd5178c2a4167dc46c43b20/sway-core/src/asm_generation/fuel/fuel_asm_builder.rs#L138https://github.com/FuelLabs/sway/blob/6e9130bbf22da7971fd5178c2a4167dc46c43b20/sway-core/src/asm_generation/fuel/fuel_asm_builder.rs#L150https://github.com/FuelLabs/sway/blob/6e9130bbf22da7971fd5178c2a4167dc46c43b20/sway-core/src/asm_generation/fuel/fuel_asm_builder.rs#L1312https://github.com/FuelLabs/sway/blob/6e9130bbf22da7971fd5178c2a4167dc46c43b20/sway-core/src/asm_generation/fuel/fuel_asm_builder.rs#L1433
Proof of concept
This test would fail because MCP length is truncated, and the slice is not properly copied.
#[test]
fn immediate_truncation() -> () {
let mut a = [0u8; 0x40000];
a[0x3ffff] = 1;
let b = a;
assert(a[0x3ffff] == 1);
assert(b[0x3ffff] == 1);
()
}Last updated
Was this helpful?