Skip to content

Commit 0657017

Browse files
authored
fix: insert critical overflow checks preventing heap corruption (#3077)
Fixes two classes of `u32`-undetected overflow bugs that could corrupt heap, stable variables, or both: * ic.rs: `alloc_words` wasn't checking validity of addresses, leading to possible (and observed) heap corruption. * compile.ml: `buffer_size` wasn't checking overflow of 32-bit size pre-computation. The first is fixed using an overflow detecting add function of Rust. The second is checked by using a local `i64` in each type-directed size function. These functions are MV and return two `i32`s (for the data buffer and (vestigial) reference buffer sizes. I did not modify the functions to return 2 `i64`s because our MultiValue emulation only support `i32` at the moment. Fixes #3068.
1 parent ecadc8d commit 0657017

19 files changed

+245
-7
lines changed

rts/motoko-rts/src/memory/ic.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,11 @@ impl Memory for IcMemory {
7676

7777
// Update heap pointer
7878
let old_hp = HP;
79-
let new_hp = old_hp + bytes.as_u32();
79+
let (new_hp, overflow) = old_hp.overflowing_add(bytes.as_u32());
80+
// Check for overflow
81+
if overflow {
82+
rts_trap_with("Out of memory");
83+
}
8084
HP = new_hp;
8185

8286
// Grow memory if needed

src/codegen/compile.ml

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4386,14 +4386,15 @@ module Serialization = struct
43864386
(fun env get_x ->
43874387

43884388
(* Some combinators for writing values *)
4389-
let (set_data_size, get_data_size) = new_local env "data_size" in
4389+
let (set_data_size, get_data_size) = new_local64 env "data_size" in
43904390
let (set_ref_size, get_ref_size) = new_local env "ref_size" in
4391-
compile_unboxed_const 0l ^^ set_data_size ^^
4391+
compile_const_64 0L ^^ set_data_size ^^
43924392
compile_unboxed_const 0l ^^ set_ref_size ^^
43934393

43944394
let inc_data_size code =
4395-
get_data_size ^^ code ^^
4396-
G.i (Binary (Wasm.Values.I32 I32Op.Add)) ^^
4395+
get_data_size ^^
4396+
code ^^ G.i (Convert (Wasm.Values.I64 I64Op.ExtendUI32)) ^^
4397+
G.i (Binary (Wasm.Values.I64 I64Op.Add)) ^^
43974398
set_data_size
43984399
in
43994400

@@ -4404,9 +4405,10 @@ module Serialization = struct
44044405
in
44054406

44064407
let size env t =
4408+
let (set_inc, get_inc) = new_local env "inc" in
44074409
buffer_size env t ^^
44084410
get_ref_size ^^ G.i (Binary (Wasm.Values.I32 I32Op.Add)) ^^ set_ref_size ^^
4409-
get_data_size ^^ G.i (Binary (Wasm.Values.I32 I32Op.Add)) ^^ set_data_size
4411+
set_inc ^^ inc_data_size get_inc
44104412
in
44114413

44124414
let size_alias size_thing =
@@ -4508,7 +4510,14 @@ module Serialization = struct
45084510
size_alias (fun () -> get_x ^^ Heap.load_field MutBox.field ^^ size env t)
45094511
| _ -> todo "buffer_size" (Arrange_ir.typ t) G.nop
45104512
end ^^
4513+
(* Check 32-bit overflow of buffer_size *)
4514+
get_data_size ^^
4515+
compile_shrU64_const 32L ^^
4516+
G.i (Test (Wasm.Values.I64 I64Op.Eqz)) ^^
4517+
E.else_trap_with env "buffer_size overflow" ^^
4518+
(* Convert to 32-bit *)
45114519
get_data_size ^^
4520+
G.i (Convert (Wasm.Values.I32 I32Op.WrapI64)) ^^
45124521
get_ref_size
45134522
)
45144523

@@ -5343,9 +5352,14 @@ module Serialization = struct
53435352
get_x ^^
53445353
buffer_size env (Type.seq ts) ^^
53455354
set_refs_size ^^
5346-
5355+
(* add tydesc_len *)
53475356
compile_add_const tydesc_len ^^
53485357
set_data_size ^^
5358+
(* check for overflow *)
5359+
get_data_size ^^
5360+
compile_unboxed_const tydesc_len ^^
5361+
G.i (Compare (Wasm.Values.I32 I32Op.LtU)) ^^
5362+
E.then_trap_with env "serialization overflow" ^^
53495363

53505364
let (set_data_start, get_data_start) = new_local env "data_start" in
53515365
let (set_refs_start, get_refs_start) = new_local env "refs_start" in

test/run-drun/inc-oom.mo

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// test incremental oom by allocating 5 GB, one GB at a time!
2+
import P "mo:⛔";
3+
actor {
4+
5+
var c = 5;
6+
7+
while(c > 0) {
8+
let a : [var Nat8] = P.Array_init<Nat8>(1024*1024*1024/4, 0xFF);
9+
c -= 1;
10+
};
11+
12+
}
13+
14+
//SKIP run
15+
//SKIP run-low
16+
//SKIP run-ir
17+
// too slow on ic-ref-run:
18+
//SKIP comp-ref
19+
// too resource heavy on GH:
20+
//SKIP comp
21+
22+
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101
2+
ingress Completed: Reply: 0x4449444c0000
3+
Err: IC0503: Canister rwlgt-iiaaa-aaaaa-aaaaa-cai trapped explicitly: RTS error: Out of memory
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101
2+
ingress Completed: Reply: 0x4449444c0000
3+
Err: IC0503: Canister rwlgt-iiaaa-aaaaa-aaaaa-cai trapped explicitly: RTS error: Out of memory
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101
2+
ingress Completed: Reply: 0x4449444c0000
3+
Err: IC0503: Canister rwlgt-iiaaa-aaaaa-aaaaa-cai trapped explicitly: buffer_size overflow
4+
Err: IC0503: Canister rwlgt-iiaaa-aaaaa-aaaaa-cai trapped explicitly: buffer_size overflow
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
→ update create_canister(record {settings = null})
2+
← replied: (record {hymijyo = principal "cvccv-qqaaq-aaaaa-aaaaa-c"})
3+
→ update install_code(record {arg = blob ""; kca_xin = blob "\00asm\01\00\00\00\0…
4+
← replied: ()
5+
→ query overflow()
6+
← rejected (RC_CANISTER_ERROR): canister trapped: EvalTrapError region:0xXXX-0xXXX "canister trapped explicitly: buffer_size overflow"
7+
→ query overflowOpt()
8+
← rejected (RC_CANISTER_ERROR): canister trapped: EvalTrapError region:0xXXX-0xXXX "canister trapped explicitly: buffer_size overflow"
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101
2+
ingress Completed: Reply: 0x4449444c0000
3+
debug.print: upgrading...
4+
ingress Err: IC0503: Canister rwlgt-iiaaa-aaaaa-aaaaa-cai trapped explicitly: buffer_size overflow
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
→ update create_canister(record {settings = null})
2+
← replied: (record {hymijyo = principal "cvccv-qqaaq-aaaaa-aaaaa-c"})
3+
→ update install_code(record {arg = blob ""; kca_xin = blob "\00asm\01\00\00\00\0…
4+
← replied: ()
5+
→ update install_code(record {arg = blob ""; kca_xin = blob "\00asm\01\00\00\00\0…
6+
debug.print: upgrading...
7+
← rejected (RC_CANISTER_ERROR): Pre-upgrade trapped: EvalTrapError region:0xXXX-0xXXX "canister trapped explicitly: buffer_size overflow"

test/run-drun/oom-query.mo

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// test incremental oom
2+
import P "mo:⛔";
3+
actor {
4+
5+
public query func go() : async () {
6+
// allocate 3GB
7+
var c = 3;
8+
while(c > 0) {
9+
let a : [var Nat8] = P.Array_init<Nat8>(1024*1024*1024/4, 0xFF);
10+
c -= 1;
11+
};
12+
13+
// allocate 1GB in 1k chunks and trigger Oom
14+
var d = 1024*1024;
15+
while(d > 0) {
16+
let a : [var Nat8] = P.Array_init<Nat8>(1024/4, 0xFF);
17+
d -= 1;
18+
};
19+
}
20+
}
21+
22+
//SKIP run
23+
//SKIP run-low
24+
//SKIP run-ir
25+
// too slow on ic-ref-run:
26+
//SKIP comp-ref
27+
28+
//CALL query go "DIDL\x00\x00"

0 commit comments

Comments
 (0)