Skip to content
This repository was archived by the owner on Nov 3, 2021. It is now read-only.

Commit be7ce1b

Browse files
eqrionrossberg
authored andcommitted
[spec] Bounds check bulk-memory before execution (#123)
Spec issue: #111 This commit changes the semantics of bulk-memory instructions to perform an upfront bounds check and trap if any access would be out-of-bounds without writing. This affects the following: * memory.init/copy/fill * table.init/copy (fill requires reftypes) * data segment init (lowers to memory.init) * elem segment init (lowers to table.init)
1 parent 9aef0c5 commit be7ce1b

13 files changed

+3177
-3334
lines changed

document/core/exec/instructions.rst

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -730,25 +730,25 @@ Memory Instructions
730730

731731
9. Let :math:`\X{data}^?` be the optional :ref:`data instance <syntax-datainst>` :math:`S.\SDATA[\X{da}]`.
732732

733-
10. If :math:`\X{data}^? = \epsilon`, then:
733+
10. Assert: due to :ref:`validation <valid-memory.init>`, a value of :ref:`value type <syntax-valtype>` |I32| is on the top of the stack.
734734

735-
a. Trap.
735+
11. Pop the value :math:`\I32.\CONST~cnt` from the stack.
736736

737-
11. Assert: due to :ref:`validation <valid-memory.init>`, a value of :ref:`value type <syntax-valtype>` |I32| is on the top of the stack.
737+
12. Assert: due to :ref:`validation <valid-memory.init>`, a value of :ref:`value type <syntax-valtype>` |I32| is on the top of the stack.
738738

739-
12. Pop the value :math:`\I32.\CONST~cnt` from the stack.
739+
13. Pop the value :math:`\I32.\CONST~src` from the stack.
740740

741-
13. Assert: due to :ref:`validation <valid-memory.init>`, a value of :ref:`value type <syntax-valtype>` |I32| is on the top of the stack.
741+
14. Assert: due to :ref:`validation <valid-memory.init>`, a value of :ref:`value type <syntax-valtype>` |I32| is on the top of the stack.
742742

743-
14. Pop the value :math:`\I32.\CONST~src` from the stack.
743+
15. Pop the value :math:`\I32.\CONST~dst` from the stack.
744744

745-
15. Assert: due to :ref:`validation <valid-memory.init>`, a value of :ref:`value type <syntax-valtype>` |I32| is on the top of the stack.
745+
16. If :math:`cnt = 0`, then:
746746

747-
16. Pop the value :math:`\I32.\CONST~dst` from the stack.
747+
a. Return.
748748

749-
17. If :math:`cnt = 0`, then:
749+
17. If :math:`\X{data}^? = \epsilon`, then:
750750

751-
a. Return.
751+
a. Trap.
752752

753753
18. If :math:`cnt = 1`, then:
754754

@@ -1091,25 +1091,25 @@ Table Instructions
10911091

10921092
9. Let :math:`\X{elem}^?` be the optional :ref:`element instance <syntax-eleminst>` :math:`S.\SELEM[\X{ea}]`.
10931093

1094-
10. If :math:`\X{elem}^? = \epsilon`, then:
1094+
10. Assert: due to :ref:`validation <valid-table.init>`, a value of :ref:`value type <syntax-valtype>` |I32| is on the top of the stack.
10951095

1096-
a. Trap.
1096+
11. Pop the value :math:`\I32.\CONST~cnt` from the stack.
10971097

1098-
11. Assert: due to :ref:`validation <valid-table.init>`, a value of :ref:`value type <syntax-valtype>` |I32| is on the top of the stack.
1098+
12. Assert: due to :ref:`validation <valid-table.init>`, a value of :ref:`value type <syntax-valtype>` |I32| is on the top of the stack.
10991099

1100-
12. Pop the value :math:`\I32.\CONST~cnt` from the stack.
1100+
13. Pop the value :math:`\I32.\CONST~src` from the stack.
11011101

1102-
13. Assert: due to :ref:`validation <valid-table.init>`, a value of :ref:`value type <syntax-valtype>` |I32| is on the top of the stack.
1102+
14. Assert: due to :ref:`validation <valid-table.init>`, a value of :ref:`value type <syntax-valtype>` |I32| is on the top of the stack.
11031103

1104-
14. Pop the value :math:`\I32.\CONST~src` from the stack.
1104+
15. Pop the value :math:`\I32.\CONST~dst` from the stack.
11051105

1106-
15. Assert: due to :ref:`validation <valid-table.init>`, a value of :ref:`value type <syntax-valtype>` |I32| is on the top of the stack.
1106+
16. If :math:`cnt = 0`, then:
11071107

1108-
16. Pop the value :math:`\I32.\CONST~dst` from the stack.
1108+
a. Return.
11091109

1110-
17. If :math:`cnt = 0`, then:
1110+
17. If :math:`\X{elem}^? = \epsilon`, then:
11111111

1112-
a. Return.
1112+
a. Trap.
11131113

11141114
18. If :math:`cnt = 1`, then:
11151115

interpreter/exec/eval.ml

Lines changed: 64 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -117,9 +117,27 @@ let drop n (vs : 'a stack) at =
117117
* c : config
118118
*)
119119

120-
let const_i32_add i j at msg =
121-
let k = I32.add i j in
122-
if I32.lt_u k i then Trapping msg else Plain (Const (I32 k @@ at))
120+
let mem_oob frame x i n =
121+
I64.gt_u (I64.add (I64_convert.extend_i32_u i) (I64_convert.extend_i32_u n))
122+
(Memory.bound (memory frame.inst x))
123+
124+
let data_oob frame x i n =
125+
match !(data frame.inst x) with
126+
| None -> false
127+
| Some bs ->
128+
I64.gt_u (I64.add (I64_convert.extend_i32_u i) (I64_convert.extend_i32_u n))
129+
(I64.of_int_u (String.length bs))
130+
131+
let table_oob frame x i n =
132+
I64.gt_u (I64.add (I64_convert.extend_i32_u i) (I64_convert.extend_i32_u n))
133+
(I64_convert.extend_i32_u (Table.size (table frame.inst x)))
134+
135+
let elem_oob frame x i n =
136+
match !(elem frame.inst x) with
137+
| None -> false
138+
| Some es ->
139+
I64.gt_u (I64.add (I64_convert.extend_i32_u i) (I64_convert.extend_i32_u n))
140+
(I64.of_int_u (List.length es))
123141

124142
let rec step (c : config) : config =
125143
let {frame; code = vs, es; _} = c in
@@ -205,6 +223,10 @@ let rec step (c : config) : config =
205223
| TableCopy, I32 0l :: I32 s :: I32 d :: vs' ->
206224
vs', []
207225

226+
| TableCopy, I32 n :: I32 s :: I32 d :: vs'
227+
when table_oob frame (0l @@ e.at) s n || table_oob frame (0l @@ e.at) d n ->
228+
vs', [Trapping (table_error e.at Table.Bounds) @@ e.at]
229+
208230
(* TODO: turn into small-step, but needs reference values *)
209231
| TableCopy, I32 n :: I32 s :: I32 d :: vs' ->
210232
let tab = table frame.inst (0l @@ e.at) in
@@ -214,6 +236,10 @@ let rec step (c : config) : config =
214236
| TableInit x, I32 0l :: I32 s :: I32 d :: vs' ->
215237
vs', []
216238

239+
| TableInit x, I32 n :: I32 s :: I32 d :: vs'
240+
when table_oob frame (0l @@ e.at) d n || elem_oob frame x s n ->
241+
vs', [Trapping (table_error e.at Table.Bounds) @@ e.at]
242+
217243
(* TODO: turn into small-step, but needs reference values *)
218244
| TableInit x, I32 n :: I32 s :: I32 d :: vs' ->
219245
let tab = table frame.inst (0l @@ e.at) in
@@ -233,22 +259,22 @@ let rec step (c : config) : config =
233259

234260
| Load {offset; ty; sz; _}, I32 i :: vs' ->
235261
let mem = memory frame.inst (0l @@ e.at) in
236-
let addr = I64_convert.extend_i32_u i in
262+
let a = I64_convert.extend_i32_u i in
237263
(try
238264
let v =
239265
match sz with
240-
| None -> Memory.load_value mem addr offset ty
241-
| Some (sz, ext) -> Memory.load_packed sz ext mem addr offset ty
266+
| None -> Memory.load_value mem a offset ty
267+
| Some (sz, ext) -> Memory.load_packed sz ext mem a offset ty
242268
in v :: vs', []
243269
with exn -> vs', [Trapping (memory_error e.at exn) @@ e.at])
244270

245271
| Store {offset; sz; _}, v :: I32 i :: vs' ->
246272
let mem = memory frame.inst (0l @@ e.at) in
247-
let addr = I64_convert.extend_i32_u i in
273+
let a = I64_convert.extend_i32_u i in
248274
(try
249275
(match sz with
250-
| None -> Memory.store_value mem addr offset v
251-
| Some sz -> Memory.store_packed sz mem addr offset v
276+
| None -> Memory.store_value mem a offset v
277+
| Some sz -> Memory.store_packed sz mem a offset v
252278
);
253279
vs', []
254280
with exn -> vs', [Trapping (memory_error e.at exn) @@ e.at]);
@@ -268,21 +294,17 @@ let rec step (c : config) : config =
268294
| MemoryFill, I32 0l :: v :: I32 i :: vs' ->
269295
vs', []
270296

271-
| MemoryFill, I32 1l :: v :: I32 i :: vs' ->
272-
vs', List.map (at e.at) [
273-
Plain (Const (I32 i @@ e.at));
274-
Plain (Const (v @@ e.at));
275-
Plain (Store
276-
{ty = I32Type; align = 0; offset = 0l; sz = Some Memory.Pack8});
277-
]
297+
| MemoryFill, I32 n :: v :: I32 i :: vs'
298+
when mem_oob frame (0l @@ e.at) i n ->
299+
vs', [Trapping (memory_error e.at Memory.Bounds) @@ e.at]
278300

279301
| MemoryFill, I32 n :: v :: I32 i :: vs' ->
280302
vs', List.map (at e.at) [
281303
Plain (Const (I32 i @@ e.at));
282304
Plain (Const (v @@ e.at));
283-
Plain (Const (I32 1l @@ e.at));
284-
Plain (MemoryFill);
285-
const_i32_add i 1l e.at (memory_error e.at Memory.Bounds);
305+
Plain (Store
306+
{ty = I32Type; align = 0; offset = 0l; sz = Some Memory.Pack8});
307+
Plain (Const (I32 (I32.add i 1l) @@ e.at));
286308
Plain (Const (v @@ e.at));
287309
Plain (Const (I32 (I32.sub n 1l) @@ e.at));
288310
Plain (MemoryFill);
@@ -291,71 +313,63 @@ let rec step (c : config) : config =
291313
| MemoryCopy, I32 0l :: I32 s :: I32 d :: vs' ->
292314
vs', []
293315

294-
| MemoryCopy, I32 1l :: I32 s :: I32 d :: vs' ->
316+
| MemoryCopy, I32 n :: I32 s :: I32 d :: vs'
317+
when mem_oob frame (0l @@ e.at) s n || mem_oob frame (0l @@ e.at) d n ->
318+
vs', [Trapping (memory_error e.at Memory.Bounds) @@ e.at]
319+
320+
| MemoryCopy, I32 n :: I32 s :: I32 d :: vs' when d <= s ->
295321
vs', List.map (at e.at) [
296322
Plain (Const (I32 d @@ e.at));
297323
Plain (Const (I32 s @@ e.at));
298324
Plain (Load
299325
{ty = I32Type; align = 0; offset = 0l; sz = Some Memory.(Pack8, ZX)});
300326
Plain (Store
301327
{ty = I32Type; align = 0; offset = 0l; sz = Some Memory.Pack8});
302-
]
303-
304-
| MemoryCopy, I32 n :: I32 s :: I32 d :: vs' when d <= s ->
305-
vs', List.map (at e.at) [
306-
Plain (Const (I32 d @@ e.at));
307-
Plain (Const (I32 s @@ e.at));
308-
Plain (Const (I32 1l @@ e.at));
309-
Plain (MemoryCopy);
310-
const_i32_add d 1l e.at (memory_error e.at Memory.Bounds);
311-
const_i32_add s 1l e.at (memory_error e.at Memory.Bounds);
328+
Plain (Const (I32 (I32.add d 1l) @@ e.at));
329+
Plain (Const (I32 (I32.add s 1l) @@ e.at));
312330
Plain (Const (I32 (I32.sub n 1l) @@ e.at));
313331
Plain (MemoryCopy);
314332
]
315333

316334
| MemoryCopy, I32 n :: I32 s :: I32 d :: vs' when s < d ->
317335
vs', List.map (at e.at) [
318-
const_i32_add d (I32.sub n 1l) e.at (memory_error e.at Memory.Bounds);
319-
const_i32_add s (I32.sub n 1l) e.at (memory_error e.at Memory.Bounds);
320-
Plain (Const (I32 1l @@ e.at));
336+
Plain (Const (I32 (I32.add d 1l) @@ e.at));
337+
Plain (Const (I32 (I32.add s 1l) @@ e.at));
338+
Plain (Const (I32 (I32.sub n 1l) @@ e.at));
321339
Plain (MemoryCopy);
322340
Plain (Const (I32 d @@ e.at));
323341
Plain (Const (I32 s @@ e.at));
324-
Plain (Const (I32 (I32.sub n 1l) @@ e.at));
325-
Plain (MemoryCopy);
342+
Plain (Load
343+
{ty = I32Type; align = 0; offset = 0l; sz = Some Memory.(Pack8, ZX)});
344+
Plain (Store
345+
{ty = I32Type; align = 0; offset = 0l; sz = Some Memory.Pack8});
326346
]
327347

328348
| MemoryInit x, I32 0l :: I32 s :: I32 d :: vs' ->
329349
vs', []
330350

331-
| MemoryInit x, I32 1l :: I32 s :: I32 d :: vs' ->
351+
| MemoryInit x, I32 n :: I32 s :: I32 d :: vs'
352+
when mem_oob frame (0l @@ e.at) d n || data_oob frame x s n ->
353+
vs', [Trapping (memory_error e.at Memory.Bounds) @@ e.at]
354+
355+
| MemoryInit x, I32 n :: I32 s :: I32 d :: vs' ->
332356
(match !(data frame.inst x) with
333357
| None ->
334358
vs', [Trapping "data segment dropped" @@ e.at]
335-
| Some bs when Int32.to_int s >= String.length bs ->
336-
vs', [Trapping "out of bounds data segment access" @@ e.at]
337359
| Some bs ->
338360
let b = Int32.of_int (Char.code bs.[Int32.to_int s]) in
339361
vs', List.map (at e.at) [
340362
Plain (Const (I32 d @@ e.at));
341363
Plain (Const (I32 b @@ e.at));
342364
Plain (
343365
Store {ty = I32Type; align = 0; offset = 0l; sz = Some Memory.Pack8});
366+
Plain (Const (I32 (I32.add d 1l) @@ e.at));
367+
Plain (Const (I32 (I32.add s 1l) @@ e.at));
368+
Plain (Const (I32 (I32.sub n 1l) @@ e.at));
369+
Plain (MemoryInit x);
344370
]
345371
)
346372

347-
| MemoryInit x, I32 n :: I32 s :: I32 d :: vs' ->
348-
vs', List.map (at e.at) [
349-
Plain (Const (I32 d @@ e.at));
350-
Plain (Const (I32 s @@ e.at));
351-
Plain (Const (I32 1l @@ e.at));
352-
Plain (MemoryInit x);
353-
const_i32_add d 1l e.at (memory_error e.at Memory.Bounds);
354-
const_i32_add s 1l e.at (memory_error e.at Memory.Bounds);
355-
Plain (Const (I32 (I32.sub n 1l) @@ e.at));
356-
Plain (MemoryInit x);
357-
]
358-
359373
| DataDrop x, vs ->
360374
let seg = data frame.inst x in
361375
(match !seg with

test/core/bulk.wast

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,11 @@
3939
;; Fill all of memory
4040
(invoke "fill" (i32.const 0) (i32.const 0) (i32.const 0x10000))
4141

42-
;; Out-of-bounds writes trap, but all previous writes succeed.
42+
;; Out-of-bounds writes trap, and nothing is written
4343
(assert_trap (invoke "fill" (i32.const 0xff00) (i32.const 1) (i32.const 0x101))
4444
"out of bounds memory access")
45-
(assert_return (invoke "load8_u" (i32.const 0xff00)) (i32.const 1))
46-
(assert_return (invoke "load8_u" (i32.const 0xffff)) (i32.const 1))
45+
(assert_return (invoke "load8_u" (i32.const 0xff00)) (i32.const 0))
46+
(assert_return (invoke "load8_u" (i32.const 0xffff)) (i32.const 0))
4747

4848
;; Succeed when writing 0 bytes at the end of the region.
4949
(invoke "fill" (i32.const 0x10000) (i32.const 0) (i32.const 0))
@@ -131,11 +131,11 @@
131131
;; Init ending at memory limit and segment limit is ok.
132132
(invoke "init" (i32.const 0xfffc) (i32.const 0) (i32.const 4))
133133

134-
;; Out-of-bounds writes trap, but all previous writes succeed.
134+
;; Out-of-bounds writes trap, and nothing is written.
135135
(assert_trap (invoke "init" (i32.const 0xfffe) (i32.const 0) (i32.const 3))
136136
"out of bounds memory access")
137-
(assert_return (invoke "load8_u" (i32.const 0xfffe)) (i32.const 0xaa))
138-
(assert_return (invoke "load8_u" (i32.const 0xffff)) (i32.const 0xbb))
137+
(assert_return (invoke "load8_u" (i32.const 0xfffe)) (i32.const 0xcc))
138+
(assert_return (invoke "load8_u" (i32.const 0xffff)) (i32.const 0xdd))
139139

140140
;; Succeed when writing 0 bytes at the end of either region.
141141
(invoke "init" (i32.const 0x10000) (i32.const 0) (i32.const 0))
@@ -190,6 +190,12 @@
190190
(local.get 0)))
191191
)
192192

193+
;; Out-of-bounds stores trap, and nothing is written.
194+
(assert_trap (invoke "init" (i32.const 2) (i32.const 0) (i32.const 2))
195+
"out of bounds table access")
196+
(assert_trap (invoke "call" (i32.const 2))
197+
"uninitialized element 2")
198+
193199
(invoke "init" (i32.const 0) (i32.const 1) (i32.const 2))
194200
(assert_return (invoke "call" (i32.const 0)) (i32.const 1))
195201
(assert_return (invoke "call" (i32.const 1)) (i32.const 0))
@@ -198,11 +204,6 @@
198204
;; Init ending at table limit and segment limit is ok.
199205
(invoke "init" (i32.const 1) (i32.const 2) (i32.const 2))
200206

201-
;; Out-of-bounds stores trap, but all previous stores succeed.
202-
(assert_trap (invoke "init" (i32.const 2) (i32.const 0) (i32.const 2))
203-
"out of bounds table access")
204-
(assert_return (invoke "call" (i32.const 2)) (i32.const 0))
205-
206207
;; Succeed when storing 0 elements at the end of either region.
207208
(invoke "init" (i32.const 3) (i32.const 0) (i32.const 0))
208209
(invoke "init" (i32.const 0) (i32.const 4) (i32.const 0))

0 commit comments

Comments
 (0)