Skip to content

Commit 9d73a6e

Browse files
alexcrichtonshumbosaulecabrerafitzgen
authored
[42.0.x] Combined backports for a 42.0.2 release (#13004)
* Fix pooling allocator predicate to reset VM permissions This commit fixes a mistake that was introduced in #9583 where the logic to reset a linear memory slot in the pooling allocator used the wrong predicate. Specifically VM permissions must be reset if virtual memory can be relied on at all, and the preexisting predicate of `can_elide_bounds_check` was an inaccurate representation of this. The correct predicate to check is `can_use_virtual_memory`. * winch: Fix the type of the `table.size` output register This commit corrects the tagged size of the output of the `table.size` instruction. Previously this was hardcoded as a 32-bit integer instead of consulting the table's index type to use the index-type-sized-register instead. * winch: Fix a host panic when executing `table.fill` This commit fixes a possible panic when a Winch-compiled module executes the `table.fill` instruction. Refactoring in #11254 updated Cranelift but forgot to update Winch meaning that Winch's indices were still using the module-level indices instead of the `DefinedTableIndex` space. This adds some tests and updates Winch's translation to use preexisting helpers. * x64: Fix `f64x2.splat` without SSE3 Don't sink a load into `pshufd` which loads 16 bytes, instead force `put_in_xmm` to ensure only 8 bytes are loaded. * Properly verify alignment in string transcoding This commit updates string transcoding between guest modules to properly verify alignment. Previously alignment was only verified on the first allocation, not reallocations, which is not spec-compliant. This additionally fixes a possible host panic when dealing with unaligned pointers. * Fix type confusion in AArch64 amode RegScaled folding * winch: Add add_uextend to perform explicit extension when needed. This commit fixes an out-of-bounds access caused by the lack zero extension in the code responsible for calculating the heap address for loads/stores. This issue manifests in aarch64 (unlike x64) given that no automatic extension is performed, resulting in an out-of-bounds access. An alternative approach is to emit an extend for the index, however this approach is preferred given that it gives the MacroAssembler layer better control of how to lower addition, e.g., in aarch64 we can inline the desired extension in a single instruction. * winch: Correctly type the result of table.grow This commit fixes an out-of-bounds access caused by the lack of type narrowing from the `table.grow` builtin. Without explicit narrowing, the type is treated as 64-bit value, which could cause issues when paired with loads/stores. * Review comments * Properly handle table index types Only narrow when dealing with the 64-bit pointer/32-bit tables * Fix panic with out-of-bounds flags in `Value` This commit fixes a panic when a component model `Value` is lifted from a flags value which specifies out-of-bounds bits as 1. This is specified in the component model to ignore the out-of-bounds bits, which `flags!` correctly did (and thus `bindgen!`), but `Value` treated out-of-bounds bits as a panic due to indexing an array. * Fix bounds checks in FACT's `string_to_compact` method We need to bounds check the source byte length, not the number of code units. * Add missing realloc validation in string transcoding This commit adds a missing validation that a return value of `realloc` is inbounds during string transcoding. This was accidentally missing on the transcoding path from `utf8` to `latin1+utf16` which meant that a nearly-raw pointer could get passed to the host to perform the transcode. * winch: Refine zero extension heuristic This commit refines the zero extension heuristic such that it unconditionally emits a zero extension when dealing with 32-bit heaps. This eliminates any ambiguity related to the value of the memory indices across ISAs. * Add release notes --------- Co-authored-by: Shun Kashiwa <shunthedev@gmail.com> Co-authored-by: Saúl Cabrera <saulecabrera@gmail.com> Co-authored-by: Nick Fitzgerald <fitzgen@gmail.com>
1 parent 6844a83 commit 9d73a6e

132 files changed

Lines changed: 1510 additions & 495 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

RELEASES.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,46 @@
1+
## 42.0.2
2+
3+
Released 2026-04-09.
4+
5+
### Fixed
6+
7+
* Miscompiled guest heap access enables sandbox escape on aarch64 Cranelift.
8+
[GHSA-jhxm-h53p-jm7w](https://github.com/bytecodealliance/wasmtime/security/advisories/GHSA-jhxm-h53p-jm7w)
9+
10+
* Wasmtime with Winch compiler backend may allow a sandbox-escaping memory
11+
access.
12+
[GHSA-xx5w-cvp6-jv83](https://github.com/bytecodealliance/wasmtime/security/advisories/GHSA-xx5w-cvp6-jv83)
13+
14+
* Out-of-bounds write or crash when transcoding component model strings.
15+
[GHSA-394w-hwhg-8vgm](https://github.com/bytecodealliance/wasmtime/security/advisories/GHSA-394w-hwhg-8vgm)
16+
17+
* Host panic when Winch compiler executes `table.fill`.
18+
[GHSA-q49f-xg75-m9xw](https://github.com/bytecodealliance/wasmtime/security/advisories/GHSA-q49f-xg75-m9xw)
19+
20+
* Wasmtime segfault or unused out-of-sandbox load with `f64x2.splat` operator
21+
on x86-64.
22+
[GHSA-qqfj-4vcm-26hv](https://github.com/bytecodealliance/wasmtime/security/advisories/GHSA-qqfj-4vcm-26hv)
23+
24+
* Improperly masked return value from `table.grow` with Winch compiler backend.
25+
[GHSA-f984-pcp8-v2p7](https://github.com/bytecodealliance/wasmtime/security/advisories/GHSA-f984-pcp8-v2p7)
26+
27+
* Panic when transcoding misaligned utf-16 strings.
28+
[GHSA-jxhv-7h78-9775](https://github.com/bytecodealliance/wasmtime/security/advisories/GHSA-jxhv-7h78-9775)
29+
30+
* Panic when lifting `flags` component value.
31+
[GHSA-m758-wjhj-p3jq](https://github.com/bytecodealliance/wasmtime/security/advisories/GHSA-m758-wjhj-p3jq)
32+
33+
* Heap OOB read in component model UTF-16 to latin1+utf16 string transcoding.
34+
[GHSA-hx6p-xpx3-jvvv](https://github.com/bytecodealliance/wasmtime/security/advisories/GHSA-hx6p-xpx3-jvvv)
35+
36+
* Data leakage between pooling allocator instances.
37+
[GHSA-6wgr-89rj-399p](https://github.com/bytecodealliance/wasmtime/security/advisories/GHSA-6wgr-89rj-399p)
38+
39+
* Host data leakage with 64-bit tables and Winch.
40+
[GHSA-m9w2-8782-2946](https://github.com/bytecodealliance/wasmtime/security/advisories/GHSA-m9w2-8782-2946)
41+
42+
--------------------------------------------------------------------------------
43+
144
## 42.0.1
245

346
Released 2026-02-25.

cranelift/codegen/src/isa/aarch64/inst.isle

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3953,11 +3953,11 @@
39533953
;; Note that this can additionally bundle an extending operation but the
39543954
;; extension must happen before the shift. This will pattern-match the shift
39553955
;; first and then if that succeeds afterwards try to find an extend.
3956-
(rule 6 (amode_no_more_iconst ty (iadd x (ishl y (iconst (u64_from_imm64 n)))) offset)
3957-
(if-let true (u64_eq (ty_bytes ty) (u64_wrapping_shl 1 (shift_masked_imm ty n))))
3956+
(rule 6 (amode_no_more_iconst ty (iadd x (ishl y @ (value_type shift_ty) (iconst (u64_from_imm64 n)))) offset)
3957+
(if-let true (u64_eq (ty_bytes ty) (u64_wrapping_shl 1 (shift_masked_imm shift_ty n))))
39583958
(amode_reg_scaled (amode_add x offset) y))
3959-
(rule 7 (amode_no_more_iconst ty (iadd (ishl y (iconst (u64_from_imm64 n))) x) offset)
3960-
(if-let true (u64_eq (ty_bytes ty) (u64_wrapping_shl 1 (shift_masked_imm ty n))))
3959+
(rule 7 (amode_no_more_iconst ty (iadd (ishl y @ (value_type shift_ty) (iconst (u64_from_imm64 n))) x) offset)
3960+
(if-let true (u64_eq (ty_bytes ty) (u64_wrapping_shl 1 (shift_masked_imm shift_ty n))))
39613961
(amode_reg_scaled (amode_add x offset) y))
39623962

39633963
(decl amode_reg_scaled (Reg Value) AMode)

cranelift/codegen/src/isa/x64/lower.isle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4860,7 +4860,7 @@
48604860
(rule 0 (lower (has_type $I64X2 (splat src)))
48614861
(x64_pshufd (bitcast_gpr_to_xmm 64 src) 0b01_00_01_00))
48624862
(rule 0 (lower (has_type $F64X2 (splat src)))
4863-
(x64_pshufd src 0b01_00_01_00))
4863+
(x64_pshufd (put_in_xmm src) 0b01_00_01_00))
48644864
(rule 6 (lower (has_type (multi_lane 64 2) (splat (sinkable_load addr))))
48654865
(if-let true (has_sse3))
48664866
(x64_movddup addr))
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
test compile precise-output
2+
set unwind_info=false
3+
target aarch64
4+
5+
;; Regression test: shift_masked_imm in amode_no_more_iconst must use the ishl
6+
;; type, not the load access type. When load.i8 has ishl.i64 by 56, the old code
7+
;; computed shift_masked_imm(I8, 56) = 56 & 7 = 0, incorrectly folding the
8+
;; shift into a RegScaled amode with LSL #0. The correct masking is
9+
;; shift_masked_imm(I64, 56) = 56 & 63 = 56, which does not match ty_bytes(I8)
10+
;; and prevents the fold.
11+
12+
function %load_i8_ishl56_should_not_fold(i64, i64) -> i8 {
13+
block0(v0: i64, v1: i64):
14+
v2 = iconst.i64 56
15+
v3 = ishl v1, v2
16+
v4 = iadd v0, v3
17+
v5 = load.i8 v4
18+
return v5
19+
}
20+
21+
; VCode:
22+
; block0:
23+
; lsl x4, x1, #56
24+
; ldrb w0, [x0, x4]
25+
; ret
26+
;
27+
; Disassembled:
28+
; block0: ; offset 0x0
29+
; lsl x4, x1, #0x38
30+
; ldrb w0, [x0, x4] ; trap: heap_oob
31+
; ret
32+
33+
function %load_i16_ishl17_should_not_fold(i64, i64) -> i16 {
34+
block0(v0: i64, v1: i64):
35+
v2 = iconst.i64 17
36+
v3 = ishl v1, v2
37+
v4 = iadd v0, v3
38+
v5 = load.i16 v4
39+
return v5
40+
}
41+
42+
; VCode:
43+
; block0:
44+
; lsl x4, x1, #17
45+
; ldrh w0, [x0, x4]
46+
; ret
47+
;
48+
; Disassembled:
49+
; block0: ; offset 0x0
50+
; lsl x4, x1, #0x11
51+
; ldrh w0, [x0, x4] ; trap: heap_oob
52+
; ret
53+
54+
function %load_i32_ishl34_should_not_fold(i64, i64) -> i32 {
55+
block0(v0: i64, v1: i64):
56+
v2 = iconst.i64 34
57+
v3 = ishl v1, v2
58+
v4 = iadd v0, v3
59+
v5 = load.i32 v4
60+
return v5
61+
}
62+
63+
; VCode:
64+
; block0:
65+
; lsl x4, x1, #34
66+
; ldr w0, [x0, x4]
67+
; ret
68+
;
69+
; Disassembled:
70+
; block0: ; offset 0x0
71+
; lsl x4, x1, #0x22
72+
; ldr w0, [x0, x4] ; trap: heap_oob
73+
; ret
74+
75+
;; Same as the i8 case but with iadd operands swapped
76+
function %load_i8_ishl56_swapped_should_not_fold(i64, i64) -> i8 {
77+
block0(v0: i64, v1: i64):
78+
v2 = iconst.i64 56
79+
v3 = ishl v1, v2
80+
v4 = iadd v3, v0
81+
v5 = load.i8 v4
82+
return v5
83+
}
84+
85+
; VCode:
86+
; block0:
87+
; lsl x4, x1, #56
88+
; ldrb w0, [x4, x0]
89+
; ret
90+
;
91+
; Disassembled:
92+
; block0: ; offset 0x0
93+
; lsl x4, x1, #0x38
94+
; ldrb w0, [x4, x0] ; trap: heap_oob
95+
; ret

crates/environ/src/fact/trampoline.rs

Lines changed: 48 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1754,7 +1754,6 @@ impl<'a, 'b> Compiler<'a, 'b> {
17541754
dst_enc: FE,
17551755
) -> WasmString<'c> {
17561756
assert!(dst_enc.width() >= src_enc.width());
1757-
self.validate_string_length(src, dst_enc);
17581757

17591758
let src_mem_opts = {
17601759
match &src.opts.data_model {
@@ -1769,22 +1768,8 @@ impl<'a, 'b> Compiler<'a, 'b> {
17691768
}
17701769
};
17711770

1772-
// Calculate the source byte length given the size of each code
1773-
// unit. Note that this shouldn't overflow given
1774-
// `validate_string_length` above.
1775-
let mut src_byte_len_tmp = None;
1776-
let src_byte_len = if src_enc.width() == 1 {
1777-
src.len.idx
1778-
} else {
1779-
assert_eq!(src_enc.width(), 2);
1780-
self.instruction(LocalGet(src.len.idx));
1781-
self.ptr_uconst(src_mem_opts, 1);
1782-
self.ptr_shl(src_mem_opts);
1783-
let tmp = self.local_set_new_tmp(src.opts.data_model.unwrap_memory().ptr());
1784-
let ret = tmp.idx;
1785-
src_byte_len_tmp = Some(tmp);
1786-
ret
1787-
};
1771+
let (src_byte_len_tmp, src_byte_len) =
1772+
self.source_string_byte_len(src, src_enc, src_mem_opts);
17881773

17891774
// Convert the source code units length to the destination byte
17901775
// length type.
@@ -1846,6 +1831,39 @@ impl<'a, 'b> Compiler<'a, 'b> {
18461831

18471832
dst
18481833
}
1834+
1835+
/// Calculate the source byte length given the size of each code
1836+
/// unit.
1837+
///
1838+
/// Returns an optional temporary local if it was needed, which the caller
1839+
/// needs to deallocate with `free_temp_local`. Additionally returns the
1840+
/// index of the local which contains the byte length of the string, which
1841+
/// may point to the temporary local passed in.
1842+
fn source_string_byte_len(
1843+
&mut self,
1844+
src: &WasmString<'_>,
1845+
src_enc: FE,
1846+
src_mem_opts: &LinearMemoryOptions,
1847+
) -> (Option<TempLocal>, u32) {
1848+
self.validate_string_length(src, src_enc);
1849+
1850+
if src_enc.width() == 1 {
1851+
(None, src.len.idx)
1852+
} else {
1853+
assert_eq!(src_enc.width(), 2);
1854+
1855+
// Note that this shouldn't overflow given `validate_string_length`
1856+
// above.
1857+
self.instruction(LocalGet(src.len.idx));
1858+
self.ptr_uconst(src_mem_opts, 1);
1859+
self.ptr_shl(src_mem_opts);
1860+
let tmp = self.local_set_new_tmp(src.opts.data_model.unwrap_memory().ptr());
1861+
1862+
let idx = tmp.idx;
1863+
(Some(tmp), idx)
1864+
}
1865+
}
1866+
18491867
// Corresponding function for `store_string_to_utf8` in the spec.
18501868
//
18511869
// This translation works by possibly performing a number of
@@ -2123,6 +2141,7 @@ impl<'a, 'b> Compiler<'a, 'b> {
21232141
}
21242142
}));
21252143
self.instruction(LocalSet(dst.ptr.idx));
2144+
self.verify_aligned(dst_opts.data_model.unwrap_memory(), dst.ptr.idx, 2);
21262145
self.instruction(End); // end of shrink-to-fit
21272146

21282147
self.free_temp_local(dst_byte_len);
@@ -2217,6 +2236,7 @@ impl<'a, 'b> Compiler<'a, 'b> {
22172236
self.instruction(LocalGet(dst.len.idx)); // new_size
22182237
self.instruction(Call(dst_mem_opts.realloc.unwrap().as_u32()));
22192238
self.instruction(LocalSet(dst.ptr.idx));
2239+
self.verify_aligned(dst_opts.data_model.unwrap_memory(), dst.ptr.idx, 2);
22202240

22212241
self.free_temp_local(dst_byte_len);
22222242
self.free_temp_local(src_byte_len);
@@ -2245,7 +2265,9 @@ impl<'a, 'b> Compiler<'a, 'b> {
22452265
DataModel::LinearMemory(opts) => opts,
22462266
};
22472267

2248-
self.validate_string_length(src, src_enc);
2268+
let (src_byte_len_tmp, src_byte_len) =
2269+
self.source_string_byte_len(src, src_enc, src_mem_opts);
2270+
22492271
self.convert_src_len_to_dst(src.len.idx, src_mem_opts.ptr(), dst_mem_opts.ptr());
22502272
let dst_len = self.local_tee_new_tmp(dst_mem_opts.ptr());
22512273
let dst_byte_len = self.local_set_new_tmp(dst_mem_opts.ptr());
@@ -2258,7 +2280,7 @@ impl<'a, 'b> Compiler<'a, 'b> {
22582280
}
22592281
};
22602282

2261-
self.validate_string_inbounds(src, src.len.idx);
2283+
self.validate_string_inbounds(src, src_byte_len);
22622284
self.validate_string_inbounds(&dst, dst_byte_len.idx);
22632285

22642286
// Perform the initial latin1 transcode. This returns the number of
@@ -2298,6 +2320,7 @@ impl<'a, 'b> Compiler<'a, 'b> {
22982320
self.instruction(LocalGet(dst.len.idx)); // new_size
22992321
self.instruction(Call(dst_mem_opts.realloc.unwrap().as_u32()));
23002322
self.instruction(LocalSet(dst.ptr.idx));
2323+
self.verify_aligned(dst_opts.data_model.unwrap_memory(), dst.ptr.idx, 2);
23012324
self.instruction(End);
23022325

23032326
// In this block the latin1 encoding failed. The host transcode
@@ -2323,6 +2346,8 @@ impl<'a, 'b> Compiler<'a, 'b> {
23232346
self.instruction(LocalTee(dst_byte_len.idx));
23242347
self.instruction(Call(dst_mem_opts.realloc.unwrap().as_u32()));
23252348
self.instruction(LocalSet(dst.ptr.idx));
2349+
self.verify_aligned(dst_opts.data_model.unwrap_memory(), dst.ptr.idx, 2);
2350+
self.validate_string_inbounds(&dst, dst_byte_len.idx);
23262351

23272352
// Call the host utf16 transcoding function. This will inflate the
23282353
// prior latin1 bytes and then encode the rest of the source string
@@ -2362,6 +2387,7 @@ impl<'a, 'b> Compiler<'a, 'b> {
23622387
self.ptr_shl(dst_mem_opts);
23632388
self.instruction(Call(dst_mem_opts.realloc.unwrap().as_u32()));
23642389
self.instruction(LocalSet(dst.ptr.idx));
2390+
self.verify_aligned(dst_opts.data_model.unwrap_memory(), dst.ptr.idx, 2);
23652391
self.instruction(End);
23662392

23672393
// Tag the returned pointer as utf16
@@ -2374,6 +2400,9 @@ impl<'a, 'b> Compiler<'a, 'b> {
23742400

23752401
self.free_temp_local(src_len_tmp);
23762402
self.free_temp_local(dst_byte_len);
2403+
if let Some(tmp) = src_byte_len_tmp {
2404+
self.free_temp_local(tmp);
2405+
}
23772406

23782407
dst
23792408
}

crates/wasmtime/src/runtime/component/values.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1030,7 +1030,7 @@ fn lower_list<T>(
10301030
}
10311031

10321032
fn push_flags(ty: &TypeFlags, flags: &mut Vec<String>, mut offset: u32, mut bits: u32) {
1033-
while bits > 0 {
1033+
while bits > 0 && usize::try_from(offset).unwrap() < ty.names.len() {
10341034
if bits & 1 != 0 {
10351035
flags.push(ty.names[offset as usize].clone());
10361036
}

crates/wasmtime/src/runtime/vm/cow.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -460,7 +460,7 @@ impl MemoryImageSlot {
460460
let host_page_size_log2 = u8::try_from(host_page_size().ilog2()).unwrap();
461461
if initial_size_bytes_page_aligned < self.accessible
462462
&& (tunables.memory_guard_size > 0
463-
|| ty.can_elide_bounds_check(tunables, host_page_size_log2))
463+
|| ty.can_use_virtual_memory(tunables, host_page_size_log2))
464464
{
465465
self.set_protection(initial_size_bytes_page_aligned..self.accessible, false)?;
466466
self.accessible = initial_size_bytes_page_aligned;

0 commit comments

Comments
 (0)