Skip to content

Commit 4421c6c

Browse files
authored
[interp] Use u64 table operands so out-of-range table64 indices trap (#2760)
On a table declared with the i64 index type, an index past the end of the table does not trap as it should: $ wasm-interp --enable-memory64 --run-all-exports t.wasm get_oob() => funcref:0 The table has 10 slots and the index was `(i64.const 0x1_0000_0003)`. That is out of bounds, but it read slot 3. table.get/set/fill/copy/init/grow pop their operands as u64 for an i64 table (PopPtr), then pass them to Table::Get/Set/Fill/Copy/Init/Grow, which take u32. The high half is dropped, so any index in [2^32, 2^32 + size) wraps to an in-range slot and the bounds check passes. table.set overwrites a live entry that way, and table.grow truncates a delta above 2^32 and reports success instead of failing. Memory already takes u64 throughout for memory64, so this widens the Table side to match and rejects a grow delta that does not fit in u32. Behaviour for i32 tables is byte-identical. Added an interp test covering the wrap window.
1 parent 71acc6d commit 4421c6c

3 files changed

Lines changed: 57 additions & 21 deletions

File tree

include/wabt/interp/interp.h

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -827,26 +827,26 @@ class Table : public Extern {
827827

828828
Result Match(Store&, const ImportType&, Trap::Ptr* out_trap) override;
829829

830-
bool IsValidRange(u32 offset, u32 size) const;
830+
bool IsValidRange(u64 offset, u64 size) const;
831831

832-
Result Get(u32 offset, Ref* out) const;
833-
Result Set(Store&, u32 offset, Ref);
834-
Result Grow(Store&, u32 count, Ref);
835-
Result Fill(Store&, u32 offset, Ref, u32 size);
832+
Result Get(u64 offset, Ref* out) const;
833+
Result Set(Store&, u64 offset, Ref);
834+
Result Grow(Store&, u64 count, Ref);
835+
Result Fill(Store&, u64 offset, Ref, u64 size);
836836
Result Init(Store&,
837-
u32 dst_offset,
837+
u64 dst_offset,
838838
const ElemSegment&,
839839
u32 src_offset,
840840
u32 size);
841841
static Result Copy(Store&,
842842
Table& dst,
843-
u32 dst_offset,
843+
u64 dst_offset,
844844
const Table& src,
845-
u32 src_offset,
846-
u32 size);
845+
u64 src_offset,
846+
u64 size);
847847

848848
// Unsafe API.
849-
Ref UnsafeGet(u32 offset) const;
849+
Ref UnsafeGet(u64 offset) const;
850850

851851
const ExternType& extern_type() override;
852852
const TableType& type() const;

src/interp/interp.cc

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include <algorithm>
2020
#include <cassert>
2121
#include <cinttypes>
22+
#include <limits>
2223

2324
#include "wabt/interp/interp-math.h"
2425

@@ -593,25 +594,25 @@ Result Table::Match(Store& store,
593594
return MatchImpl(store, import_type, type_, out_trap);
594595
}
595596

596-
bool Table::IsValidRange(u32 offset, u32 size) const {
597+
bool Table::IsValidRange(u64 offset, u64 size) const {
597598
size_t elem_size = elements_.size();
598599
return size <= elem_size && offset <= elem_size - size;
599600
}
600601

601-
Result Table::Get(u32 offset, Ref* out) const {
602+
Result Table::Get(u64 offset, Ref* out) const {
602603
if (IsValidRange(offset, 1)) {
603604
*out = elements_[offset];
604605
return Result::Ok;
605606
}
606607
return Result::Error;
607608
}
608609

609-
Ref Table::UnsafeGet(u32 offset) const {
610+
Ref Table::UnsafeGet(u64 offset) const {
610611
assert(IsValidRange(offset, 1));
611612
return elements_[offset];
612613
}
613614

614-
Result Table::Set(Store& store, u32 offset, Ref ref) {
615+
Result Table::Set(Store& store, u64 offset, Ref ref) {
615616
assert(store.HasValueType(ref, type_.element));
616617
if (IsValidRange(offset, 1)) {
617618
elements_[offset] = ref;
@@ -620,11 +621,18 @@ Result Table::Set(Store& store, u32 offset, Ref ref) {
620621
return Result::Error;
621622
}
622623

623-
Result Table::Grow(Store& store, u32 count, Ref ref) {
624+
Result Table::Grow(Store& store, u64 count, Ref ref) {
624625
size_t old_size = elements_.size();
625626
u32 new_size;
626627
assert(store.HasValueType(ref, type_.element));
627-
if (CanGrow<u32>(type_.limits, old_size, count, &new_size)) {
628+
// Table sizes are bounded to 2^32-1 elements, so a delta that does not fit in
629+
// a u32 can never succeed; checking here keeps the u64 count from being
630+
// truncated by the u32 CanGrow below.
631+
if (count > std::numeric_limits<u32>::max()) {
632+
return Result::Error;
633+
}
634+
if (CanGrow<u32>(type_.limits, old_size, static_cast<u32>(count),
635+
&new_size)) {
628636
// Grow the limits of the table too, so that if it is used as an
629637
// import to another module its new size is honored.
630638
type_.limits.initial += count;
@@ -635,7 +643,7 @@ Result Table::Grow(Store& store, u32 count, Ref ref) {
635643
return Result::Error;
636644
}
637645

638-
Result Table::Fill(Store& store, u32 offset, Ref ref, u32 size) {
646+
Result Table::Fill(Store& store, u64 offset, Ref ref, u64 size) {
639647
assert(store.HasValueType(ref, type_.element));
640648
if (IsValidRange(offset, size)) {
641649
std::fill(elements_.begin() + offset, elements_.begin() + offset + size,
@@ -646,7 +654,7 @@ Result Table::Fill(Store& store, u32 offset, Ref ref, u32 size) {
646654
}
647655

648656
Result Table::Init(Store& store,
649-
u32 dst_offset,
657+
u64 dst_offset,
650658
const ElemSegment& src,
651659
u32 src_offset,
652660
u32 size) {
@@ -663,10 +671,10 @@ Result Table::Init(Store& store,
663671
// static
664672
Result Table::Copy(Store& store,
665673
Table& dst,
666-
u32 dst_offset,
674+
u64 dst_offset,
667675
const Table& src,
668-
u32 src_offset,
669-
u32 size) {
676+
u64 src_offset,
677+
u64 size) {
670678
if (dst.IsValidRange(dst_offset, size) &&
671679
src.IsValidRange(src_offset, size) &&
672680
TypesMatch(dst.type_.element, src.type_.element)) {

test/interp/table64-oob.txt

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
;;; TOOL: run-interp
2+
;;; ARGS*: --enable-memory64
3+
(module
4+
(table $t i64 10 funcref)
5+
6+
;; Indices in [2^32, 2^32 + size) must trap, not wrap into a valid slot.
7+
(func (export "get_wrap") (result funcref)
8+
(table.get $t (i64.const 0x1_0000_0003)))
9+
(func (export "set_wrap")
10+
(table.set $t (i64.const 0x1_0000_0003) (ref.null func)))
11+
(func (export "fill_wrap")
12+
(table.fill $t (i64.const 0x1_0000_0000) (ref.null func) (i64.const 1)))
13+
14+
;; A delta that does not fit in 32 bits must fail to grow.
15+
(func (export "grow_overflow") (result i64)
16+
(table.grow $t (ref.null func) (i64.const 0x1_0000_0000)))
17+
18+
;; In-range access still works.
19+
(func (export "get_ok") (result funcref)
20+
(table.get $t (i64.const 3)))
21+
)
22+
(;; STDOUT ;;;
23+
get_wrap() => error: out of bounds table access: table.get at 4294967299 >= max value 10
24+
set_wrap() => error: out of bounds table access: table.set at 4294967299 >= max value 10
25+
fill_wrap() => error: out of bounds table access: table.fill out of bounds
26+
grow_overflow() => i64:18446744073709551615
27+
get_ok() => funcref:0
28+
;;; STDOUT ;;)

0 commit comments

Comments
 (0)