Skip to content

Commit e82550c

Browse files
fixed a bug with array slice index wrapping (#1594)
1 parent b8c6890 commit e82550c

3 files changed

Lines changed: 51 additions & 13 deletions

File tree

src/libfuncs/array.rs

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1185,24 +1185,26 @@ pub fn build_slice<'ctx, 'this>(
11851185

11861186
let slice_start = entry.argument(2)?.into();
11871187
let slice_len = entry.argument(3)?.into();
1188-
let slice_end = entry.append_op_result(arith::addi(slice_start, slice_len, location))?;
11891188

1190-
let slice_lhs_bound = entry.append_op_result(arith::cmpi(
1189+
// Compute `slice_end = slice_start + slice_len` in 64 bits. In 32-bit
1190+
// arithmetic the sum wraps and lets an attacker-chosen `slice_len`
1191+
// sneak past the bound below. Since `slice_len` is unsigned (Sierra
1192+
// u32) it cannot be negative, so once we've checked
1193+
// `slice_end <= array_len` we also know `slice_start <= array_len`.
1194+
let wide_ty = IntegerType::new(context, 64).into();
1195+
let slice_start_wide = entry.extui(slice_start, wide_ty, location)?;
1196+
let slice_len_wide = entry.extui(slice_len, wide_ty, location)?;
1197+
let array_len_wide = entry.extui(array_len, wide_ty, location)?;
1198+
let slice_end_wide =
1199+
entry.append_op_result(arith::addi(slice_start_wide, slice_len_wide, location))?;
1200+
1201+
let slice_bounds = entry.append_op_result(arith::cmpi(
11911202
context,
11921203
CmpiPredicate::Ule,
1193-
slice_start,
1194-
array_len,
1195-
location,
1196-
))?;
1197-
let slice_rhs_bound = entry.append_op_result(arith::cmpi(
1198-
context,
1199-
CmpiPredicate::Ule,
1200-
slice_end,
1201-
array_len,
1204+
slice_end_wide,
1205+
array_len_wide,
12021206
location,
12031207
))?;
1204-
let slice_bounds =
1205-
entry.append_op_result(arith::andi(slice_lhs_bound, slice_rhs_bound, location))?;
12061208

12071209
let valid_block = helper.append_block(Block::new(&[]));
12081210
let error_block = helper.append_block(Block::new(&[]));
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
fn run_test(user_len: u32) {
2+
let arr: Array<u64> = array![10_u64, 20_u64];
3+
let slice = arr.span().slice(1, user_len);
4+
}

tests/tests/arrays.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,35 @@ proptest! {
5959
.unwrap();
6060
}
6161
}
62+
63+
#[test]
64+
fn array_slice_u32_overflow() {
65+
let program = &load_program_and_runner("test_data_artifacts/programs/array_slice_overflow");
66+
let user_len = u32::MAX;
67+
68+
let result_vm = run_vm_program(
69+
program,
70+
"run_test",
71+
vec![Arg::Value(Felt::from(user_len))],
72+
Some(DEFAULT_GAS as usize),
73+
)
74+
.unwrap();
75+
let result_native = run_native_program(
76+
program,
77+
"run_test",
78+
&[Value::Uint32(user_len)],
79+
Some(DEFAULT_GAS),
80+
Option::<DummySyscallHandler>::None,
81+
);
82+
83+
compare_outputs(
84+
&program.1,
85+
&program.2.find_function("run_test").unwrap().id,
86+
&result_vm,
87+
&result_native,
88+
)
89+
.expect(
90+
"array_slice diverges between VM and native — native let a u32-overflowing \
91+
slice through; see build_slice in src/libfuncs/array.rs",
92+
);
93+
}

0 commit comments

Comments
 (0)