Skip to content

Commit 96dde3a

Browse files
alexcrichtonflavioshumbosaulecabrerafitzgen
authored
[43.0.x] Combined backports for a 43.0.1 release (#13005)
* fix(environ): repair unsound StringPool::try_clone() The 43.0 release introduced a soundness bug in StringPool::try_clone(): the cloned map retains &'static str keys pointing into the original pool's strings storage. Once the original Linker is dropped those keys dangle. Cloning a Linker, then dropping the original one, leaves a linker whose registered imports could no longer be found, causing instantiation to fail with "unknown import". Signed-off-by: Flavio Castelli <fcastelli@suse.com> * 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 --------- Signed-off-by: Flavio Castelli <fcastelli@suse.com> Co-authored-by: Flavio Castelli <fcastelli@suse.com> 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 be23469 commit 96dde3a

File tree

134 files changed

+1561
-499
lines changed

Some content is hidden

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

134 files changed

+1561
-499
lines changed

RELEASES.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,49 @@
1+
## 43.0.1
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+
* Use-after-free bug after cloning `wasmtime::Linker`.
37+
[GHSA-hfr4-7c6c-48w2](https://github.com/bytecodealliance/wasmtime/security/advisories/GHSA-hfr4-7c6c-48w2)
38+
39+
* Data leakage between pooling allocator instances.
40+
[GHSA-6wgr-89rj-399p](https://github.com/bytecodealliance/wasmtime/security/advisories/GHSA-6wgr-89rj-399p)
41+
42+
* Host data leakage with 64-bit tables and Winch.
43+
[GHSA-m9w2-8782-2946](https://github.com/bytecodealliance/wasmtime/security/advisories/GHSA-m9w2-8782-2946)
44+
45+
--------------------------------------------------------------------------------
46+
147
## 43.0.0
248

349
Released 2026-03-20.

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

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

39673967
(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
@@ -4905,7 +4905,7 @@
49054905
(rule 0 (lower (has_type $I64X2 (splat src)))
49064906
(x64_pshufd (bitcast_gpr_to_xmm 64 src) 0b01_00_01_00))
49074907
(rule 0 (lower (has_type $F64X2 (splat src)))
4908-
(x64_pshufd src 0b01_00_01_00))
4908+
(x64_pshufd (put_in_xmm src) 0b01_00_01_00))
49094909
(rule 6 (lower (has_type (multi_lane 64 2) (splat (sinkable_load addr))))
49104910
(if-let true (has_sse3))
49114911
(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
@@ -1761,7 +1761,6 @@ impl<'a, 'b> Compiler<'a, 'b> {
17611761
dst_enc: FE,
17621762
) -> WasmString<'c> {
17631763
assert!(dst_enc.width() >= src_enc.width());
1764-
self.validate_string_length(src, dst_enc);
17651764

17661765
let src_mem_opts = {
17671766
match &src.opts.data_model {
@@ -1776,22 +1775,8 @@ impl<'a, 'b> Compiler<'a, 'b> {
17761775
}
17771776
};
17781777

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

17961781
// Convert the source code units length to the destination byte
17971782
// length type.
@@ -1853,6 +1838,39 @@ impl<'a, 'b> Compiler<'a, 'b> {
18531838

18541839
dst
18551840
}
1841+
1842+
/// Calculate the source byte length given the size of each code
1843+
/// unit.
1844+
///
1845+
/// Returns an optional temporary local if it was needed, which the caller
1846+
/// needs to deallocate with `free_temp_local`. Additionally returns the
1847+
/// index of the local which contains the byte length of the string, which
1848+
/// may point to the temporary local passed in.
1849+
fn source_string_byte_len(
1850+
&mut self,
1851+
src: &WasmString<'_>,
1852+
src_enc: FE,
1853+
src_mem_opts: &LinearMemoryOptions,
1854+
) -> (Option<TempLocal>, u32) {
1855+
self.validate_string_length(src, src_enc);
1856+
1857+
if src_enc.width() == 1 {
1858+
(None, src.len.idx)
1859+
} else {
1860+
assert_eq!(src_enc.width(), 2);
1861+
1862+
// Note that this shouldn't overflow given `validate_string_length`
1863+
// above.
1864+
self.instruction(LocalGet(src.len.idx));
1865+
self.ptr_uconst(src_mem_opts, 1);
1866+
self.ptr_shl(src_mem_opts);
1867+
let tmp = self.local_set_new_tmp(src.opts.data_model.unwrap_memory().ptr());
1868+
1869+
let idx = tmp.idx;
1870+
(Some(tmp), idx)
1871+
}
1872+
}
1873+
18561874
// Corresponding function for `store_string_to_utf8` in the spec.
18571875
//
18581876
// This translation works by possibly performing a number of
@@ -2130,6 +2148,7 @@ impl<'a, 'b> Compiler<'a, 'b> {
21302148
}
21312149
}));
21322150
self.instruction(LocalSet(dst.ptr.idx));
2151+
self.verify_aligned(dst_opts.data_model.unwrap_memory(), dst.ptr.idx, 2);
21332152
self.instruction(End); // end of shrink-to-fit
21342153

21352154
self.free_temp_local(dst_byte_len);
@@ -2224,6 +2243,7 @@ impl<'a, 'b> Compiler<'a, 'b> {
22242243
self.instruction(LocalGet(dst.len.idx)); // new_size
22252244
self.instruction(Call(dst_mem_opts.realloc.unwrap().as_u32()));
22262245
self.instruction(LocalSet(dst.ptr.idx));
2246+
self.verify_aligned(dst_opts.data_model.unwrap_memory(), dst.ptr.idx, 2);
22272247

22282248
self.free_temp_local(dst_byte_len);
22292249
self.free_temp_local(src_byte_len);
@@ -2252,7 +2272,9 @@ impl<'a, 'b> Compiler<'a, 'b> {
22522272
DataModel::LinearMemory(opts) => opts,
22532273
};
22542274

2255-
self.validate_string_length(src, src_enc);
2275+
let (src_byte_len_tmp, src_byte_len) =
2276+
self.source_string_byte_len(src, src_enc, src_mem_opts);
2277+
22562278
self.convert_src_len_to_dst(src.len.idx, src_mem_opts.ptr(), dst_mem_opts.ptr());
22572279
let dst_len = self.local_tee_new_tmp(dst_mem_opts.ptr());
22582280
let dst_byte_len = self.local_set_new_tmp(dst_mem_opts.ptr());
@@ -2265,7 +2287,7 @@ impl<'a, 'b> Compiler<'a, 'b> {
22652287
}
22662288
};
22672289

2268-
self.validate_string_inbounds(src, src.len.idx);
2290+
self.validate_string_inbounds(src, src_byte_len);
22692291
self.validate_string_inbounds(&dst, dst_byte_len.idx);
22702292

22712293
// Perform the initial latin1 transcode. This returns the number of
@@ -2305,6 +2327,7 @@ impl<'a, 'b> Compiler<'a, 'b> {
23052327
self.instruction(LocalGet(dst.len.idx)); // new_size
23062328
self.instruction(Call(dst_mem_opts.realloc.unwrap().as_u32()));
23072329
self.instruction(LocalSet(dst.ptr.idx));
2330+
self.verify_aligned(dst_opts.data_model.unwrap_memory(), dst.ptr.idx, 2);
23082331
self.instruction(End);
23092332

23102333
// In this block the latin1 encoding failed. The host transcode
@@ -2330,6 +2353,8 @@ impl<'a, 'b> Compiler<'a, 'b> {
23302353
self.instruction(LocalTee(dst_byte_len.idx));
23312354
self.instruction(Call(dst_mem_opts.realloc.unwrap().as_u32()));
23322355
self.instruction(LocalSet(dst.ptr.idx));
2356+
self.verify_aligned(dst_opts.data_model.unwrap_memory(), dst.ptr.idx, 2);
2357+
self.validate_string_inbounds(&dst, dst_byte_len.idx);
23332358

23342359
// Call the host utf16 transcoding function. This will inflate the
23352360
// prior latin1 bytes and then encode the rest of the source string
@@ -2369,6 +2394,7 @@ impl<'a, 'b> Compiler<'a, 'b> {
23692394
self.ptr_shl(dst_mem_opts);
23702395
self.instruction(Call(dst_mem_opts.realloc.unwrap().as_u32()));
23712396
self.instruction(LocalSet(dst.ptr.idx));
2397+
self.verify_aligned(dst_opts.data_model.unwrap_memory(), dst.ptr.idx, 2);
23722398
self.instruction(End);
23732399

23742400
// Tag the returned pointer as utf16
@@ -2381,6 +2407,9 @@ impl<'a, 'b> Compiler<'a, 'b> {
23812407

23822408
self.free_temp_local(src_len_tmp);
23832409
self.free_temp_local(dst_byte_len);
2410+
if let Some(tmp) = src_byte_len_tmp {
2411+
self.free_temp_local(tmp);
2412+
}
23842413

23852414
dst
23862415
}

crates/environ/src/string_pool.rs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,20 @@ impl fmt::Debug for StringPool {
7777

7878
impl TryClone for StringPool {
7979
fn try_clone(&self) -> Result<Self, OutOfMemory> {
80-
Ok(StringPool {
81-
map: self.map.try_clone()?,
82-
strings: self.strings.try_clone()?,
83-
})
80+
let mut new_pool = StringPool::new();
81+
// Re-intern strings in index order so that each Atom value is
82+
// identical in the clone — callers that hold Atoms from the original
83+
// can use them interchangeably with the clone.
84+
//
85+
// Directly cloning `self.map` would copy &'static str keys that point
86+
// into the *original* pool's `strings` allocation. Those pointers
87+
// become dangling once the original is dropped, leading to UB on any
88+
// subsequent lookup. Re-interning ensures the cloned map's keys point
89+
// into the clone's own `strings`.
90+
for s in self.strings.iter() {
91+
new_pool.insert(s)?;
92+
}
93+
Ok(new_pool)
8494
}
8595
}
8696

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)