Skip to content

Commit d8a898c

Browse files
committed
[exec] Bounds check bulk-memory before execution
Spec issue: WebAssembly#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 d8a898c

12 files changed

+3162
-3317
lines changed

interpreter/exec/eval.ml

Lines changed: 66 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -117,9 +117,25 @@ 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 -> I64.gt_u (I64.add (I64_convert.extend_i32_u i) (I64_convert.extend_i32_u n))
128+
(I64.of_int_u (String.length bs))
129+
130+
let table_oob frame x i n =
131+
I64.gt_u (I64.add (I64_convert.extend_i32_u i) (I64_convert.extend_i32_u n))
132+
(I64_convert.extend_i32_u (Table.size (table frame.inst x)))
133+
134+
let elem_oob frame x i n =
135+
match !(elem frame.inst x) with
136+
| None -> false
137+
| Some es -> I64.gt_u (I64.add (I64_convert.extend_i32_u i) (I64_convert.extend_i32_u n))
138+
(I64.of_int_u (List.length es))
123139

124140
let rec step (c : config) : config =
125141
let {frame; code = vs, es; _} = c in
@@ -205,6 +221,10 @@ let rec step (c : config) : config =
205221
| TableCopy, I32 0l :: I32 s :: I32 d :: vs' ->
206222
vs', []
207223

224+
| TableCopy, I32 n :: I32 s :: I32 d :: vs'
225+
when table_oob frame (0l @@ e.at) s n || table_oob frame (0l @@ e.at) d n ->
226+
vs', [Trapping (table_error e.at Table.Bounds) @@ e.at]
227+
208228
(* TODO: turn into small-step, but needs reference values *)
209229
| TableCopy, I32 n :: I32 s :: I32 d :: vs' ->
210230
let tab = table frame.inst (0l @@ e.at) in
@@ -214,6 +234,10 @@ let rec step (c : config) : config =
214234
| TableInit x, I32 0l :: I32 s :: I32 d :: vs' ->
215235
vs', []
216236

237+
| TableInit x, I32 n :: I32 s :: I32 d :: vs'
238+
when table_oob frame (0l @@ e.at) d n || elem_oob frame x s n ->
239+
vs', [Trapping (table_error e.at Table.Bounds) @@ e.at]
240+
217241
(* TODO: turn into small-step, but needs reference values *)
218242
| TableInit x, I32 n :: I32 s :: I32 d :: vs' ->
219243
let tab = table frame.inst (0l @@ e.at) in
@@ -233,22 +257,22 @@ let rec step (c : config) : config =
233257

234258
| Load {offset; ty; sz; _}, I32 i :: vs' ->
235259
let mem = memory frame.inst (0l @@ e.at) in
236-
let addr = I64_convert.extend_i32_u i in
260+
let a = I64_convert.extend_i32_u i in
237261
(try
238262
let v =
239263
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
264+
| None -> Memory.load_value mem a offset ty
265+
| Some (sz, ext) -> Memory.load_packed sz ext mem a offset ty
242266
in v :: vs', []
243267
with exn -> vs', [Trapping (memory_error e.at exn) @@ e.at])
244268

245269
| Store {offset; sz; _}, v :: I32 i :: vs' ->
246270
let mem = memory frame.inst (0l @@ e.at) in
247-
let addr = I64_convert.extend_i32_u i in
271+
let a = I64_convert.extend_i32_u i in
248272
(try
249273
(match sz with
250-
| None -> Memory.store_value mem addr offset v
251-
| Some sz -> Memory.store_packed sz mem addr offset v
274+
| None -> Memory.store_value mem a offset v
275+
| Some sz -> Memory.store_packed sz mem a offset v
252276
);
253277
vs', []
254278
with exn -> vs', [Trapping (memory_error e.at exn) @@ e.at]);
@@ -268,21 +292,17 @@ let rec step (c : config) : config =
268292
| MemoryFill, I32 0l :: v :: I32 i :: vs' ->
269293
vs', []
270294

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-
]
295+
| MemoryFill, I32 n :: v :: I32 i :: vs'
296+
when mem_oob frame (0l @@ e.at) i n ->
297+
vs', [Trapping (memory_error e.at Memory.Bounds) @@ e.at]
278298

279299
| MemoryFill, I32 n :: v :: I32 i :: vs' ->
280300
vs', List.map (at e.at) [
281301
Plain (Const (I32 i @@ e.at));
282302
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);
303+
Plain (Store
304+
{ty = I32Type; align = 0; offset = 0l; sz = Some Memory.Pack8});
305+
Plain (Const (I32 (I32.add i 1l) @@ e.at));
286306
Plain (Const (v @@ e.at));
287307
Plain (Const (I32 (I32.sub n 1l) @@ e.at));
288308
Plain (MemoryFill);
@@ -291,71 +311,67 @@ let rec step (c : config) : config =
291311
| MemoryCopy, I32 0l :: I32 s :: I32 d :: vs' ->
292312
vs', []
293313

294-
| MemoryCopy, I32 1l :: I32 s :: I32 d :: vs' ->
314+
| MemoryCopy, I32 n :: I32 s :: I32 d :: vs'
315+
when mem_oob frame (0l @@ e.at) s n || mem_oob frame (0l @@ e.at) d n ->
316+
vs', [Trapping (memory_error e.at Memory.Bounds) @@ e.at]
317+
318+
| MemoryCopy, I32 n :: I32 s :: I32 d :: vs' when d <= s ->
295319
vs', List.map (at e.at) [
296320
Plain (Const (I32 d @@ e.at));
297321
Plain (Const (I32 s @@ e.at));
298322
Plain (Load
299323
{ty = I32Type; align = 0; offset = 0l; sz = Some Memory.(Pack8, ZX)});
300324
Plain (Store
301325
{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);
326+
Plain (Const (I32 (I32.add d 1l) @@ e.at));
327+
Plain (Const (I32 (I32.add s 1l) @@ e.at));
312328
Plain (Const (I32 (I32.sub n 1l) @@ e.at));
313329
Plain (MemoryCopy);
314330
]
315331

316332
| MemoryCopy, I32 n :: I32 s :: I32 d :: vs' when s < d ->
317333
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));
334+
Plain (Const (I32 (I32.add d 1l) @@ e.at));
335+
Plain (Const (I32 (I32.add s 1l) @@ e.at));
336+
Plain (Const (I32 (I32.sub n 1l) @@ e.at));
321337
Plain (MemoryCopy);
322338
Plain (Const (I32 d @@ e.at));
323339
Plain (Const (I32 s @@ e.at));
324-
Plain (Const (I32 (I32.sub n 1l) @@ e.at));
325-
Plain (MemoryCopy);
340+
Plain (Load
341+
{ty = I32Type; align = 0; offset = 0l; sz = Some Memory.(Pack8, ZX)});
342+
Plain (Store
343+
{ty = I32Type; align = 0; offset = 0l; sz = Some Memory.Pack8});
326344
]
327345

328346
| MemoryInit x, I32 0l :: I32 s :: I32 d :: vs' ->
329347
vs', []
330348

331-
| MemoryInit x, I32 1l :: I32 s :: I32 d :: vs' ->
349+
| MemoryInit x, I32 n :: I32 s :: I32 d :: vs'
350+
when mem_oob frame (0l @@ e.at) d n ->
351+
vs', [Trapping (memory_error e.at Memory.Bounds) @@ e.at]
352+
353+
| MemoryInit x, I32 n :: I32 s :: I32 d :: vs'
354+
when data_oob frame x s n ->
355+
vs', [Trapping "out of bounds data segment access" @@ e.at]
356+
357+
| MemoryInit x, I32 n :: I32 s :: I32 d :: vs' ->
332358
(match !(data frame.inst x) with
333359
| None ->
334360
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]
337361
| Some bs ->
338362
let b = Int32.of_int (Char.code bs.[Int32.to_int s]) in
339363
vs', List.map (at e.at) [
340364
Plain (Const (I32 d @@ e.at));
341365
Plain (Const (I32 b @@ e.at));
342366
Plain (
343367
Store {ty = I32Type; align = 0; offset = 0l; sz = Some Memory.Pack8});
368+
Plain (Const (I32 (I32.add d 1l) @@ e.at));
369+
Plain (Const (I32 (I32.add s 1l) @@ e.at));
370+
Plain (Const (I32 (I32.sub n 1l) @@ e.at));
371+
Plain (MemoryInit x);
344372
]
345373
)
346374

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-
359375
| DataDrop x, vs ->
360376
let seg = data frame.inst x in
361377
(match !seg with

test/core/bulk.wast

Lines changed: 15 additions & 14 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))
@@ -128,14 +128,14 @@
128128
(assert_return (invoke "load8_u" (i32.const 1)) (i32.const 0xcc))
129129
(assert_return (invoke "load8_u" (i32.const 2)) (i32.const 0))
130130

131-
;; Init ending at memory limit and segment limit is ok.
132-
(invoke "init" (i32.const 0xfffc) (i32.const 0) (i32.const 4))
133-
134-
;; Out-of-bounds writes trap, but all previous writes succeed.
131+
;; Out-of-bounds writes trap, and nothing is written.
135132
(assert_trap (invoke "init" (i32.const 0xfffe) (i32.const 0) (i32.const 3))
136133
"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))
134+
(assert_return (invoke "load8_u" (i32.const 0xfffe)) (i32.const 0x00))
135+
(assert_return (invoke "load8_u" (i32.const 0xffff)) (i32.const 0x00))
136+
137+
;; Init ending at memory limit and segment limit is ok.
138+
(invoke "init" (i32.const 0xfffc) (i32.const 0) (i32.const 4))
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)