Skip to content

Commit 48b427b

Browse files
alexcrichtonmbundpchickey
authored
[36.0.x] Backport fixes for security advisories (#12648)
* Backport fixes for security advisories. This commit contains merged backports for two security advisories in Wasmtime: * GHSA-852m-cvvp-9p4w * GHSA-243v-98vx-264h This introduces new knobs to Wasmtime to limit the scope of resources that WASI implementations will allocate on behalf of guests. To preserve backwards-compatible behavior all knobs are set quite high (e.g. 2GiB). Embeddings can turn these knobs as appropriate to limit the amount of data the host will allocate for a guest. The following CLI knobs have been added: * `-Smax-resources` - limits the total component-model resources a guest can allocate in a table * `-Shostcall-fuel` - a broad limit which enforces that at most this amount of data will be copied from the guest to the host in any one API call (e.g. `string` values can't be too big, `list<string>` can't be quadratic, etc). This fuel is reset on each host function call. * `-Smax-random-size` - the maximal size of the return value of the `get-random-bytes` and `get-insecure-random-bytes` WASI functions. * `-Smax-http-fields-size` - a limit on the size of `wasi:http` `fields` values to avoid infinitely buffering data within the host. The `http` crate has additionally been updated to avoid a panic when adding too many headers to a `fields` object. Co-authored-by: Mark Bundschuh <mark@mbund.dev> Co-authored-by: Pat Hickey <p.hickey@f5.com> * CI fixes * Run rustfmt * Fix wasi-common build * Fix vets with cargo-vet@0.10.0 --------- Co-authored-by: Mark Bundschuh <mark@mbund.dev> Co-authored-by: Pat Hickey <p.hickey@f5.com>
1 parent 06ef143 commit 48b427b

54 files changed

Lines changed: 1832 additions & 247 deletions

Some content is hidden

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

Cargo.lock

Lines changed: 2 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,7 @@ libc = { version = "0.2.112", default-features = true }
384384
file-per-thread-logger = "0.2.0"
385385
tokio = { version = "1.43.0", features = [ "rt", "time" ] }
386386
hyper = "1.0.1"
387-
http = "1.0.0"
387+
http = "1.4.0"
388388
http-body = "1.0.0"
389389
http-body-util = "0.1.0"
390390
bytes = { version = "1.4", default-features = false }

RELEASES.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,29 @@
1+
## 36.0.6
2+
3+
Released 2026-02-24.
4+
5+
### Changed
6+
7+
* Wasmtime's implementation of WASI now has the ability to limit resource
8+
consumption on behalf of the guest, such as host-allocated memory. This means
9+
that some behaviors previously allowed by Wasmtime can now disallowed, such as
10+
transferring excessive data from the guest to the host. Additionally calls to
11+
`wasi:random/random.get-random-bytes`, for example, can have limits in place
12+
to avoid allocating too much memory on the host. To preserve
13+
backwards-compatible behavior these limits are NOT set by default. Embedders
14+
must opt-in to configuring these knobs as appropriate for their embeddings.
15+
For more information on this see the related security advisory with further
16+
details on knobs added and what behaviors can be restricted.
17+
[GHSA-852m-cvvp-9p4w](https://github.com/bytecodealliance/wasmtime/security/advisories/GHSA-852m-cvvp-9p4w)
18+
19+
### Fixed
20+
21+
* Panics when adding too many headers to a `wasi:http/types.fields` has been
22+
resolved
23+
[GHSA-243v-98vx-264h](https://github.com/bytecodealliance/wasmtime/security/advisories/GHSA-243v-98vx-264h)
24+
25+
--------------------------------------------------------------------------------
26+
127
## 36.0.5
228

329
Released 2026-01-26.

crates/cli-flags/src/lib.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,18 @@ wasmtime_option_group! {
477477
/// Preset data for the In-Memory provider of WASI key-value API.
478478
#[serde(skip)]
479479
pub keyvalue_in_memory_data: Vec<KeyValuePair>,
480+
/// Maximum resources the guest is allowed to create simultaneously.
481+
pub max_resources: Option<usize>,
482+
/// Fuel to use for all hostcalls to limit guest<->host data transfer.
483+
pub hostcall_fuel: Option<usize>,
484+
/// Maximum value, in bytes, for a wasi-random 0.2
485+
/// `get{,-insecure}-random-bytes` `len` parameter. Calls with a value
486+
/// exceeding this limit will trap.
487+
pub max_random_size: Option<u64>,
488+
/// Maximum value, in bytes, for the contents of a wasi-http 0.2
489+
/// `fields` resource (aka `headers` and `trailers`). `fields` methods
490+
/// which cause the contents to exceed this size limit will trap.
491+
pub max_http_fields_size: Option<usize>,
480492
}
481493

482494
enum Wasi {

crates/test-programs/artifacts/build.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ impl Artifacts {
8282
s if s.starts_with("p3_http_") => "p3_http",
8383
s if s.starts_with("p3_api_") => "p3_api",
8484
s if s.starts_with("p3_") => "p3",
85+
8586
// If you're reading this because you hit this panic, either add
8687
// it to a test suite above or add a new "suite". The purpose of
8788
// the categorization above is to have a static assertion that
@@ -256,7 +257,7 @@ impl Artifacts {
256257
// Prevent stray files for now that we don't understand.
257258
Some(_) => panic!("unknown file extension on {path:?}"),
258259

259-
None => unreachable!(),
260+
None => unreachable!("no extension in path {path:?}"),
260261
}
261262
}
262263
}

crates/test-programs/src/bin/api_proxy.rs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use anyhow::{Context, Result};
12
use test_programs::wasi::http::types::{
23
Headers, IncomingRequest, Method, OutgoingBody, OutgoingResponse, ResponseOutparam,
34
};
@@ -29,6 +30,16 @@ impl test_programs::proxy::exports::wasi::http::incoming_handler::Guest for T {
2930

3031
return;
3132
}
33+
(Method::Get, Some(p)) if p.starts_with("/modify_fields/") => {
34+
let r = modify_fields_handler(request);
35+
response_for(r, outparam);
36+
return;
37+
}
38+
(Method::Get, Some(p)) if p.starts_with("/new_fields/") => {
39+
let r = new_fields_handler(request);
40+
response_for(r, outparam);
41+
return;
42+
}
3243

3344
_ => {}
3445
}
@@ -69,10 +80,64 @@ impl test_programs::proxy::exports::wasi::http::incoming_handler::Guest for T {
6980
}
7081
}
7182

83+
fn response_for(r: Result<()>, outparam: ResponseOutparam) {
84+
let resp = OutgoingResponse::new(Headers::new());
85+
resp.set_status_code(if r.is_ok() { 200 } else { 500 })
86+
.unwrap();
87+
let body = resp.body().expect("outgoing response");
88+
ResponseOutparam::set(outparam, Ok(resp));
89+
let _ = body.write().and_then(|out| {
90+
let _ = out.blocking_write_and_flush(format!("{r:?}").as_bytes());
91+
drop(out);
92+
Ok(())
93+
});
94+
let _ = OutgoingBody::finish(body, None);
95+
}
96+
7297
// Technically this should not be here for a proxy, but given the current
7398
// framework for tests it's required since this file is built as a `bin`
7499
fn main() {}
75100

76101
fn test_filesystem() {
77102
assert!(std::fs::File::open(".").is_err());
78103
}
104+
105+
fn add_bytes_to_headers(headers: Headers, size: usize) {
106+
if size == 0 {
107+
return;
108+
} else if size < 10 {
109+
headers.append("k", &b"abcdefghi"[0..size - 1]).unwrap()
110+
} else {
111+
for chunk in 0..(size / 10) {
112+
let k = format!("g{chunk:04}");
113+
let mut v = format!("h{chunk:04}");
114+
if chunk == 0 {
115+
for _ in 0..(size % 10) {
116+
v.push('#');
117+
}
118+
}
119+
headers.append(k.as_str(), v.as_bytes()).unwrap()
120+
}
121+
}
122+
}
123+
124+
fn modify_fields_handler(request: IncomingRequest) -> Result<()> {
125+
let path = request.path_with_query().unwrap();
126+
let rest = path.trim_start_matches("/modify_fields/");
127+
let added_field_bytes: usize = rest
128+
.parse()
129+
.context("expect remainder of url to parse as number")?;
130+
add_bytes_to_headers(request.headers().clone(), added_field_bytes);
131+
132+
Ok(())
133+
}
134+
fn new_fields_handler(request: IncomingRequest) -> Result<()> {
135+
let path = request.path_with_query().unwrap();
136+
let rest = path.trim_start_matches("/new_fields/");
137+
let added_field_bytes: usize = rest
138+
.parse()
139+
.context("expect remainder of url to parse as number")?;
140+
add_bytes_to_headers(Headers::new(), added_field_bytes);
141+
142+
Ok(())
143+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
fn main() {
2+
let fields = wasip2::http::types::Fields::new();
3+
4+
match std::env::args().nth(1).as_deref() {
5+
Some("append") => {
6+
for i in 0.. {
7+
if fields.append(&format!("a{i}"), b"a").is_err() {
8+
break;
9+
}
10+
}
11+
}
12+
Some("append-empty") => {
13+
for i in 0.. {
14+
if fields.append(&format!("a{i}"), b"").is_err() {
15+
break;
16+
}
17+
}
18+
}
19+
Some("append-same") => loop {
20+
if fields.append("a", b"b").is_err() {
21+
break;
22+
}
23+
},
24+
Some("append-same-empty") => loop {
25+
if fields.append("a", b"").is_err() {
26+
break;
27+
}
28+
},
29+
other => panic!("unknown test {other:?}"),
30+
}
31+
32+
unreachable!();
33+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
use test_programs::wasi::clocks::monotonic_clock::subscribe_duration;
2+
3+
fn main() {
4+
loop {
5+
std::mem::forget(subscribe_duration(1_000_000));
6+
}
7+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
fn main() {
2+
let mut buf = Vec::new();
3+
for _ in 0..100 {
4+
buf.push(wasip2::clocks::monotonic_clock::subscribe_duration(0));
5+
}
6+
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
use std::ptr;
2+
3+
fn main() {
4+
big_poll();
5+
big_string();
6+
big_iovecs();
7+
}
8+
9+
fn big_string() {
10+
let mut s = String::new();
11+
for _ in 0..10_000 {
12+
s.push_str("hello world");
13+
}
14+
let dir_fd = test_programs::preview1::open_scratch_directory(".").unwrap();
15+
assert_eq!(
16+
unsafe { wasip1::path_create_directory(dir_fd, &s) },
17+
Err(wasip1::ERRNO_NOMEM)
18+
);
19+
}
20+
21+
fn big_iovecs() {
22+
let mut iovs = Vec::new();
23+
let mut ciovs = Vec::new();
24+
for _ in 0..10_000 {
25+
iovs.push(wasip1::Iovec {
26+
buf: ptr::null_mut(),
27+
buf_len: 0,
28+
});
29+
ciovs.push(wasip1::Ciovec {
30+
buf: ptr::null(),
31+
buf_len: 0,
32+
});
33+
}
34+
let dir_fd = test_programs::preview1::open_scratch_directory(".").unwrap();
35+
let fd = unsafe {
36+
wasip1::path_open(
37+
dir_fd,
38+
0,
39+
"hi",
40+
wasip1::OFLAGS_CREAT,
41+
wasip1::RIGHTS_FD_WRITE | wasip1::RIGHTS_FD_READ,
42+
0,
43+
0,
44+
)
45+
.unwrap()
46+
};
47+
48+
unsafe {
49+
assert_eq!(wasip1::fd_write(fd, &ciovs), Err(wasip1::ERRNO_NOMEM));
50+
assert_eq!(wasip1::fd_read(fd, &iovs), Err(wasip1::ERRNO_NOMEM));
51+
assert_eq!(wasip1::fd_pwrite(fd, &ciovs, 0), Err(wasip1::ERRNO_NOMEM));
52+
assert_eq!(wasip1::fd_pread(fd, &iovs, 0), Err(wasip1::ERRNO_NOMEM));
53+
}
54+
55+
ciovs.truncate(1);
56+
iovs.truncate(1);
57+
iovs.push(wasip1::Iovec {
58+
buf: ptr::null_mut(),
59+
buf_len: 10_000,
60+
});
61+
ciovs.push(wasip1::Ciovec {
62+
buf: ptr::null(),
63+
buf_len: 10_000,
64+
});
65+
unsafe {
66+
assert_eq!(wasip1::fd_write(fd, &ciovs), Err(wasip1::ERRNO_NOMEM));
67+
assert_eq!(wasip1::fd_read(fd, &iovs), Err(wasip1::ERRNO_NOMEM));
68+
assert_eq!(wasip1::fd_pwrite(fd, &ciovs, 0), Err(wasip1::ERRNO_NOMEM));
69+
assert_eq!(wasip1::fd_pread(fd, &iovs, 0), Err(wasip1::ERRNO_NOMEM));
70+
}
71+
}
72+
73+
fn big_poll() {
74+
let mut huge_poll = Vec::new();
75+
let mut huge_events = Vec::new();
76+
for _ in 0..10_000 {
77+
huge_poll.push(subscribe_timeout(0));
78+
huge_events.push(empty_event());
79+
}
80+
let err = unsafe {
81+
wasip1::poll_oneoff(
82+
huge_poll.as_ptr(),
83+
huge_events.as_mut_ptr(),
84+
huge_poll.len(),
85+
)
86+
.unwrap_err()
87+
};
88+
assert_eq!(err, wasip1::ERRNO_NOMEM);
89+
90+
fn subscribe_timeout(timeout: u64) -> wasip1::Subscription {
91+
wasip1::Subscription {
92+
userdata: 0,
93+
u: wasip1::SubscriptionU {
94+
tag: wasip1::EVENTTYPE_CLOCK.raw(),
95+
u: wasip1::SubscriptionUU {
96+
clock: wasip1::SubscriptionClock {
97+
id: wasip1::CLOCKID_MONOTONIC,
98+
timeout,
99+
precision: 0,
100+
flags: 0,
101+
},
102+
},
103+
},
104+
}
105+
}
106+
107+
fn empty_event() -> wasip1::Event {
108+
wasip1::Event {
109+
error: wasip1::ERRNO_SUCCESS,
110+
fd_readwrite: wasip1::EventFdReadwrite {
111+
nbytes: 0,
112+
flags: 0,
113+
},
114+
type_: wasip1::EVENTTYPE_CLOCK,
115+
userdata: 0,
116+
}
117+
}
118+
}

0 commit comments

Comments
 (0)