Skip to content

Commit 39e910b

Browse files
alexcrichtonflavioshumbosaulecabrerafitzgen
authored
[44.0.0] Merged backports for security advisories (#13007)
* 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. * Fix failure on 32-bit * Fix miri test --------- 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 eb4c527 commit 39e910b

File tree

133 files changed

+1525
-502
lines changed

Some content is hidden

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

133 files changed

+1525
-502
lines changed

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3954,11 +3954,11 @@
39543954
;; Note that this can additionally bundle an extending operation but the
39553955
;; extension must happen before the shift. This will pattern-match the shift
39563956
;; first and then if that succeeds afterwards try to find an extend.
3957-
(rule 6 (amode_no_more_iconst ty (iadd _ x (ishl _ y (iconst _ (u64_from_imm64 n)))) offset)
3958-
(if-let true (u64_eq (ty_bytes ty) (u64_wrapping_shl 1 (shift_masked_imm ty n))))
3957+
(rule 6 (amode_no_more_iconst ty (iadd _ x (ishl shift_ty y (iconst _ (u64_from_imm64 n)))) offset)
3958+
(if-let true (u64_eq (ty_bytes ty) (u64_wrapping_shl 1 (shift_masked_imm shift_ty n))))
39593959
(amode_reg_scaled (amode_add x offset) y))
3960-
(rule 7 (amode_no_more_iconst ty (iadd _ (ishl _ y (iconst _ (u64_from_imm64 n))) x) offset)
3961-
(if-let true (u64_eq (ty_bytes ty) (u64_wrapping_shl 1 (shift_masked_imm ty n))))
3960+
(rule 7 (amode_no_more_iconst ty (iadd _ (ishl shift_ty y (iconst _ (u64_from_imm64 n))) x) 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))
39633963

39643964
(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
@@ -4903,7 +4903,7 @@
49034903
(rule 0 (lower (has_type $I64X2 (splat _ src)))
49044904
(x64_pshufd (bitcast_gpr_to_xmm 64 src) 0b01_00_01_00))
49054905
(rule 0 (lower (has_type $F64X2 (splat _ src)))
4906-
(x64_pshufd src 0b01_00_01_00))
4906+
(x64_pshufd (put_in_xmm src) 0b01_00_01_00))
49074907
(rule 6 (lower (has_type (multi_lane 64 2) (splat _ (sinkable_load addr))))
49084908
(if-let true (has_sse3))
49094909
(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
@@ -1764,7 +1764,6 @@ impl<'a, 'b> Compiler<'a, 'b> {
17641764
dst_enc: FE,
17651765
) -> WasmString<'c> {
17661766
assert!(dst_enc.width() >= src_enc.width());
1767-
self.validate_string_length(src, dst_enc);
17681767

17691768
let src_mem_opts = {
17701769
match &src.opts.data_model {
@@ -1779,22 +1778,8 @@ impl<'a, 'b> Compiler<'a, 'b> {
17791778
}
17801779
};
17811780

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

17991784
// Convert the source code units length to the destination byte
18001785
// length type.
@@ -1856,6 +1841,39 @@ impl<'a, 'b> Compiler<'a, 'b> {
18561841

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

21382157
self.free_temp_local(dst_byte_len);
@@ -2227,6 +2246,7 @@ impl<'a, 'b> Compiler<'a, 'b> {
22272246
self.instruction(LocalGet(dst.len.idx)); // new_size
22282247
self.instruction(Call(dst_mem_opts.realloc.unwrap().as_u32()));
22292248
self.instruction(LocalSet(dst.ptr.idx));
2249+
self.verify_aligned(dst_opts.data_model.unwrap_memory(), dst.ptr.idx, 2);
22302250

22312251
self.free_temp_local(dst_byte_len);
22322252
self.free_temp_local(src_byte_len);
@@ -2255,7 +2275,9 @@ impl<'a, 'b> Compiler<'a, 'b> {
22552275
DataModel::LinearMemory(opts) => opts,
22562276
};
22572277

2258-
self.validate_string_length(src, src_enc);
2278+
let (src_byte_len_tmp, src_byte_len) =
2279+
self.source_string_byte_len(src, src_enc, src_mem_opts);
2280+
22592281
self.convert_src_len_to_dst(src.len.idx, src_mem_opts.ptr(), dst_mem_opts.ptr());
22602282
let dst_len = self.local_tee_new_tmp(dst_mem_opts.ptr());
22612283
let dst_byte_len = self.local_set_new_tmp(dst_mem_opts.ptr());
@@ -2268,7 +2290,7 @@ impl<'a, 'b> Compiler<'a, 'b> {
22682290
}
22692291
};
22702292

2271-
self.validate_string_inbounds(src, src.len.idx);
2293+
self.validate_string_inbounds(src, src_byte_len);
22722294
self.validate_string_inbounds(&dst, dst_byte_len.idx);
22732295

22742296
// Perform the initial latin1 transcode. This returns the number of
@@ -2308,6 +2330,7 @@ impl<'a, 'b> Compiler<'a, 'b> {
23082330
self.instruction(LocalGet(dst.len.idx)); // new_size
23092331
self.instruction(Call(dst_mem_opts.realloc.unwrap().as_u32()));
23102332
self.instruction(LocalSet(dst.ptr.idx));
2333+
self.verify_aligned(dst_opts.data_model.unwrap_memory(), dst.ptr.idx, 2);
23112334
self.instruction(End);
23122335

23132336
// In this block the latin1 encoding failed. The host transcode
@@ -2333,6 +2356,8 @@ impl<'a, 'b> Compiler<'a, 'b> {
23332356
self.instruction(LocalTee(dst_byte_len.idx));
23342357
self.instruction(Call(dst_mem_opts.realloc.unwrap().as_u32()));
23352358
self.instruction(LocalSet(dst.ptr.idx));
2359+
self.verify_aligned(dst_opts.data_model.unwrap_memory(), dst.ptr.idx, 2);
2360+
self.validate_string_inbounds(&dst, dst_byte_len.idx);
23362361

23372362
// Call the host utf16 transcoding function. This will inflate the
23382363
// prior latin1 bytes and then encode the rest of the source string
@@ -2372,6 +2397,7 @@ impl<'a, 'b> Compiler<'a, 'b> {
23722397
self.ptr_shl(dst_mem_opts);
23732398
self.instruction(Call(dst_mem_opts.realloc.unwrap().as_u32()));
23742399
self.instruction(LocalSet(dst.ptr.idx));
2400+
self.verify_aligned(dst_opts.data_model.unwrap_memory(), dst.ptr.idx, 2);
23752401
self.instruction(End);
23762402

23772403
// Tag the returned pointer as utf16
@@ -2384,6 +2410,9 @@ impl<'a, 'b> Compiler<'a, 'b> {
23842410

23852411
self.free_temp_local(src_len_tmp);
23862412
self.free_temp_local(dst_byte_len);
2413+
if let Some(tmp) = src_byte_len_tmp {
2414+
self.free_temp_local(tmp);
2415+
}
23872416

23882417
dst
23892418
}

crates/environ/src/string_pool.rs

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

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

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1142,7 +1142,7 @@ fn lower_map<T>(
11421142
}
11431143

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

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

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

tests/all/component_model/dynamic.rs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1237,3 +1237,72 @@ fn introspection() -> Result<()> {
12371237
);
12381238
Ok(())
12391239
}
1240+
1241+
#[test]
1242+
fn flags_beyond_end() -> Result<()> {
1243+
let engine = super::engine();
1244+
1245+
let component = Component::new(
1246+
&engine,
1247+
r#"
1248+
(component
1249+
(type $f' (flags "a" "b"))
1250+
(export $f "f" (type $f'))
1251+
1252+
(core module $m
1253+
(func (export "r") (param i32) (result i32) local.get 0)
1254+
)
1255+
(core instance $i (instantiate $m))
1256+
(func (export "run") (param "x" u32) (result $f)
1257+
(canon lift (core func $i "r")))
1258+
)
1259+
"#,
1260+
)?;
1261+
let mut store = Store::new(&engine, ());
1262+
let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
1263+
let run = instance.get_func(&mut store, "run").unwrap();
1264+
1265+
let mut output = [Val::Bool(false)];
1266+
run.call(&mut store, &[Val::U32(0)], &mut output)?;
1267+
assert_eq!(output[0], Val::Flags(vec![]));
1268+
1269+
let mut output = [Val::Bool(false)];
1270+
run.call(&mut store, &[Val::U32(1)], &mut output)?;
1271+
assert_eq!(output[0], Val::Flags(vec!["a".to_string()]));
1272+
1273+
let mut output = [Val::Bool(false)];
1274+
run.call(&mut store, &[Val::U32(2)], &mut output)?;
1275+
assert_eq!(output[0], Val::Flags(vec!["b".to_string()]));
1276+
1277+
let mut output = [Val::Bool(false)];
1278+
run.call(&mut store, &[Val::U32(3)], &mut output)?;
1279+
assert_eq!(
1280+
output[0],
1281+
Val::Flags(vec!["a".to_string(), "b".to_string()])
1282+
);
1283+
let mut output = [Val::Bool(false)];
1284+
1285+
run.call(&mut store, &[Val::U32(4)], &mut output)?;
1286+
assert_eq!(output[0], Val::Flags(vec![]));
1287+
1288+
run.call(&mut store, &[Val::U32(5)], &mut output)?;
1289+
assert_eq!(output[0], Val::Flags(vec!["a".to_string()]));
1290+
1291+
wasmtime::component::flags! {
1292+
F {
1293+
#[component(name = "a")]
1294+
const A;
1295+
#[component(name = "b")]
1296+
const B;
1297+
}
1298+
}
1299+
1300+
let run = instance.get_typed_func::<(u32,), (F,)>(&mut store, "run")?;
1301+
assert_eq!(run.call(&mut store, (0,))?, (F::empty(),));
1302+
assert_eq!(run.call(&mut store, (1,))?, (F::A,));
1303+
assert_eq!(run.call(&mut store, (2,))?, (F::B,));
1304+
assert_eq!(run.call(&mut store, (3,))?, (F::A | F::B,));
1305+
assert_eq!(run.call(&mut store, (4,))?, (F::empty(),));
1306+
assert_eq!(run.call(&mut store, (5,))?, (F::A,));
1307+
Ok(())
1308+
}

0 commit comments

Comments
 (0)