Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions crates/spacewasm_c_api/include/spacewasm.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ enum spacewasm_status_t
SPACEWASM_ERR_MEMORY_TOO_LARGE = 92,
SPACEWASM_ERR_MEMORY_IMPORT_TOO_LARGE = 93,
SPACEWASM_ERR_MEM_ALIGN_TOO_LARGE = 94,
SPACEWASM_ERR_TABLE_TOO_LARGE = 95,
SPACEWASM_ERR_CONTROL_FLOW_TOO_DEEP = 96,
SPACEWASM_ERR_STACK_UNDERFLOW = 97,
SPACEWASM_ERR_STACK_TOO_LARGE = 98,
Expand Down
2 changes: 2 additions & 0 deletions crates/spacewasm_c_api/src/status.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ pub enum spacewasm_status_t {
SPACEWASM_ERR_MEMORY_TOO_LARGE = 92,
SPACEWASM_ERR_MEMORY_IMPORT_TOO_LARGE = 93,
SPACEWASM_ERR_MEM_ALIGN_TOO_LARGE = 94,
SPACEWASM_ERR_TABLE_TOO_LARGE = 95,

// Parse / validation errors - Control flow validation
SPACEWASM_ERR_CONTROL_FLOW_TOO_DEEP = 96,
Expand Down Expand Up @@ -273,6 +274,7 @@ pub fn validation_status(e: &ValidationError) -> spacewasm_status_t {
ValidationError::IdxTooLarge => SPACEWASM_ERR_IDX_TOO_LARGE,
ValidationError::ModuleIdxTooLarge => SPACEWASM_ERR_MODULE_IDX_TOO_LARGE,
ValidationError::MemoryTooLarge => SPACEWASM_ERR_MEMORY_TOO_LARGE,
ValidationError::TableTooLarge => SPACEWASM_ERR_TABLE_TOO_LARGE,
ValidationError::MemoryImportTooLarge => SPACEWASM_ERR_MEMORY_IMPORT_TOO_LARGE,
ValidationError::MemAlignTooLarge => SPACEWASM_ERR_MEM_ALIGN_TOO_LARGE,
ValidationError::ControlFlowTooDeep => SPACEWASM_ERR_CONTROL_FLOW_TOO_DEEP,
Expand Down
1 change: 1 addition & 0 deletions crates/spacewasm_c_api/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -929,6 +929,7 @@ fn validation_error_codes_map() {
status::SPACEWASM_ERR_MEMORY_IMPORT_TOO_LARGE,
),
(MemAlignTooLarge, status::SPACEWASM_ERR_MEM_ALIGN_TOO_LARGE),
(TableTooLarge, status::SPACEWASM_ERR_TABLE_TOO_LARGE),
// Control flow validation
(
ControlFlowTooDeep,
Expand Down
1 change: 1 addition & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ pub enum ValidationError {
IdxTooLarge,
ModuleIdxTooLarge,
MemoryTooLarge,
TableTooLarge,
MemoryImportTooLarge,
MemAlignTooLarge,
ControlFlowTooDeep,
Expand Down
14 changes: 9 additions & 5 deletions src/host.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,8 +164,8 @@ pub enum HostFunctionBreak {

pub type HostFunctionResult = ControlFlow<HostFunctionBreak, Option<Value>>;

/// Maximum number of values in a host function parameter / result signature.
pub const HOST_SIGNATURE_CAP: usize = 63;
/// Maximum number of parameters a host function may declare.
pub const MAX_HOST_FUNCTION_PARAMS: usize = 9;

/// Error returned when a host value signature contains an invalid character or
/// exceeds [`HOST_SIGNATURE_CAP`] entries.
Expand All @@ -177,7 +177,7 @@ pub struct HostValListError;
/// `i` (i32), `I` (i64), `f` (f32), `d` (f64).
#[derive(Copy, Clone)]
pub struct HostValList {
data: [ValType; HOST_SIGNATURE_CAP],
data: [ValType; MAX_HOST_FUNCTION_PARAMS],
len: u8,
}

Expand Down Expand Up @@ -215,11 +215,11 @@ impl HostValList {
/// is not one of `iIfd` or the signature exceeds [`HOST_SIG_CAP`] entries.
/// This is the FFI-safe constructor.
pub fn try_new(s: &str) -> Result<Self, HostValListError> {
let mut data = [ValType::I32; HOST_SIGNATURE_CAP];
let mut data = [ValType::I32; MAX_HOST_FUNCTION_PARAMS];
let mut len = 0usize;

for c in s.chars() {
if len >= HOST_SIGNATURE_CAP {
if len >= MAX_HOST_FUNCTION_PARAMS {
return Err(HostValListError);
}
data[len] = HostValList::map_char(c)?;
Expand Down Expand Up @@ -373,6 +373,10 @@ impl HostFunction {
return Err(HostValListError);
}

if params.len() > MAX_HOST_FUNCTION_PARAMS {
return Err(HostValListError);
}

let mut rs: Option<ValType> = None;
for r in returns.iter() {
if rs.is_some() {
Expand Down
2 changes: 1 addition & 1 deletion src/interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1384,7 +1384,7 @@ impl IrVisitor for Interpreter {
x: u16,
state: &mut Self::State,
) -> Result<(), Self::Error> {
let mut sv: StaticVec<Value, 9> = StaticVec::new();
let mut sv: StaticVec<Value, MAX_HOST_FUNCTION_PARAMS> = StaticVec::new();

let f = &state.store.host_modules_mut()[module.0 as usize].functions[x as usize];
state.sp -= f.param_size();
Expand Down
12 changes: 9 additions & 3 deletions src/text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -582,9 +582,15 @@ impl<'a, const MAX_CONTROL_FRAMES: usize, const MAX_STACK_DEPTH: usize>
// This bucket has the local variable
// Compute it's offset as a word index from the frame
let offset = current_offset + ty.size() * (x - current_index) as usize;

let word_offset = offset / 4;
if word_offset > (i16::MAX as usize) - 2 {
return Err(ValidationError::LocalIdxOutOfRange);
}

return Ok(LocalVariable {
// Add 2 to skip over fp and lr
frame_offset: ((offset / 4) as i16) + 2,
frame_offset: (word_offset as i16) + 2,
ty: *ty,
});
}
Expand Down Expand Up @@ -1037,8 +1043,8 @@ impl<'a, const MAX_CONTROL_FRAMES: usize, const MAX_STACK_DEPTH: usize>
}
BlockKind::If => {
// We are currently inside an if-statement without an else.
// Only if-statements without return values are valid here (or inside an unreachable state).
if last.out.0.is_none() || last.unreachable {
// Only if-statements without return values are valid here
if last.out.0.is_none() {
let pc = self.pc();
self.code.backpatch(last.target, |code, address, label| {
let patched = label.with_jump(JumpOffset::new(address, pc)?);
Expand Down
19 changes: 15 additions & 4 deletions src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -423,13 +423,24 @@ pub struct TableType {
pub limits: Limit,
}

/// Maximum number of elements permitted in a table.
pub const MAX_TABLE_ELEMENTS: u32 = 10_000_000;

impl TableType {
pub(crate) fn read(wasm: &mut Reader) -> Result<Self, ValidationError> {
// Table types are encoded with their limits and a constant byte indicating their element type.
Ok(TableType {
elem_type: ElemType::read(wasm)?,
limits: Limit::read(wasm)?,
})
let elem_type = ElemType::read(wasm)?;
let limits = Limit::read(wasm)?;

if limits.min > MAX_TABLE_ELEMENTS {
return Err(ValidationError::TableTooLarge);
} else if let Some(max) = limits.max {
if max > MAX_TABLE_ELEMENTS {
return Err(ValidationError::TableTooLarge);
}
}

Ok(TableType { elem_type, limits })
}
}

Expand Down
7 changes: 5 additions & 2 deletions src/util/rc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,11 @@ impl<T, A: Allocator + Clone> Rc<[T], A> {

// Calculate the layout we need: Cell<u32> + align padding + [T; len]
let count_layout = core::alloc::Layout::new::<Cell<u32>>();
let slice_layout = core::alloc::Layout::array::<T>(len).unwrap();
let (full_layout, slice_offset) = count_layout.extend(slice_layout).unwrap();
let slice_layout =
core::alloc::Layout::array::<T>(len).map_err(|_| AllocError::OutOfMemory)?;
let (full_layout, slice_offset) = count_layout
.extend(slice_layout)
.map_err(|_| AllocError::OutOfMemory)?;
let full_layout = full_layout.pad_to_align();

// Allocate new memory for RcInner<[T]>
Expand Down
40 changes: 40 additions & 0 deletions tests/regression/decode-errors.wast
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,17 @@
(module binary "\00asm\01\00\00\00\04\05\01\70\01\02\01")
"size minimum must not be greater than maximum")

;; ---------------------------------------------------------------------------
;; A table whose `limits.min` is used unbounded as an allocation length is
;; rejected at decode time (symmetric with the memory-size bound). Left
;; unchecked, `min` drove a panic on 32-bit targets (Layout::array failure) or
;; a multi-gigabyte allocation on 64-bit hosts.
;; table section (id 4): count=1, funcref, flag=0x00 (min only), min=0xFFFFFFFF
;; ---------------------------------------------------------------------------
(assert_invalid
(module binary "\00asm\01\00\00\00\04\08\01\70\00\ff\ff\ff\ff\0f")
"table size too large")

;; ---------------------------------------------------------------------------
;; Memory type flag with the "shared" bit (bit 1) set is unsupported.
;; memory section (id 5): count=1, flag 0x02, min 0
Expand Down Expand Up @@ -109,3 +120,32 @@
(assert_malformed
(module binary "\00asm\01\00\00\00\0c\01\00")
"malformed section id")

;; ---------------------------------------------------------------------------
;; We normally accept up to 0xFFFF locals but the real check if whether it's
;; 16-bit word offset overflows i16::MAX. This is a failure case.
;; ---------------------------------------------------------------------------
(assert_invalid
(module binary
"\00asm\01\00\00\00\01\04\01\60\00\00\03\02\01\00\07\08"
"\01\04\74\65\73\74\00\00\0a\0d\01\0b\01\c0\b8\02\7f\20\b8\91"
"\02\1a\0b")
"local offset out of range")

;; ---------------------------------------------------------------------------
;; A result-typed `if` without an `else` used to be accepted when the then-arm
;; ended unreachable. The false path is still reachable and produces no result,
;; desynchronizing the validator's operand-stack model from the runtime stack
;; pointer. Such a module must be rejected regardless of then-arm reachability.
;; Function `f` of type () -> i32 whose body is
;; `i32.const 0; if (result i32); unreachable; end`.
;; ---------------------------------------------------------------------------
(assert_invalid
(module
(type $t0 (func (result i32)))
(func $f (export "f") (type $t0) (result i32)
(if $I0 (result i32)
(i32.const 0)
(then
(unreachable)))))
"result-typed if without else")
3 changes: 3 additions & 0 deletions tests/util/spectest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -982,6 +982,9 @@ fn check_decode_error(err: ParseError, text: String) {
(ValidationError::InvalidMaxLimit, "size minimum must not be greater than maximum") => {}
(ValidationError::MemoryTooLarge, "memory size must be at most 65536 pages (4GiB)") => {}
(ValidationError::MemoryTooLarge, "memory size must be at most 4 GiB") => {}
(ValidationError::TableTooLarge, "table size too large") => {}
(ValidationError::LocalIdxOutOfRange, "local offset out of range") => {}
(ValidationError::BlockResultTypeMismatch, "result-typed if without else") => {}
(ValidationError::InvalidNegativeMemOffset, "data segment does not fit") => {}
(ValidationError::InvalidMemOffsetType, "type mismatch") => {}
(ValidationError::InvalidStartFunctionSignature, "start function") => {}
Expand Down
Loading