Skip to content

Commit 301dc71

Browse files
alexcrichtonmbundpchickeydicej
authored
Fix two security advisories. (bytecodealliance#12652)
* Fix two security advisories. This commit contains merged fixes 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. Unlike backports to 41.0.x-and-prior these knobs all have default values which are considered reasonable for hosts if they don't further tune them. 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> Co-authored-by: Joel Dice <joel.dice@akamai.com> * CI fixes * Run rustfmt * Fix wasi-common build * Fix tests on 32-bit * Fix nightly test expectations prtest:full --------- Co-authored-by: Mark Bundschuh <mark@mbund.dev> Co-authored-by: Pat Hickey <p.hickey@f5.com> Co-authored-by: Joel Dice <joel.dice@akamai.com>
1 parent 332b4ce commit 301dc71

Some content is hidden

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

51 files changed

+1864
-321
lines changed

crates/cli-flags/src/lib.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -497,6 +497,18 @@ wasmtime_option_group! {
497497
pub keyvalue_in_memory_data: Vec<KeyValuePair>,
498498
/// Enable support for WASIp3 APIs.
499499
pub p3: Option<bool>,
500+
/// Maximum resources the guest is allowed to create simultaneously.
501+
pub max_resources: Option<usize>,
502+
/// Fuel to use for all hostcalls to limit guest<->host data transfer.
503+
pub hostcall_fuel: Option<usize>,
504+
/// Maximum value, in bytes, for a wasi-random 0.2
505+
/// `get{,-insecure}-random-bytes` `len` parameter. Calls with a value
506+
/// exceeding this limit will trap.
507+
pub max_random_size: Option<u64>,
508+
/// Maximum value, in bytes, for the contents of a wasi-http 0.2
509+
/// `fields` resource (aka `headers` and `trailers`). `fields` methods
510+
/// which cause the contents to exceed this size limit will trap.
511+
pub max_http_fields_size: Option<usize>,
500512
}
501513

502514
enum Wasi {

crates/test-programs/artifacts/build.rs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -78,21 +78,26 @@ impl Artifacts {
7878
// Bucket, based on the name of the test, into a "kind" which
7979
// generates a `foreach_*` macro below.
8080
let kind = match test.name.as_str() {
81+
s if s.starts_with("p1_cli_")
82+
|| s.starts_with("p2_cli_")
83+
|| s.starts_with("p3_cli_") =>
84+
{
85+
"cli"
86+
}
8187
s if s.starts_with("p1_") => "p1",
8288
s if s.starts_with("p2_http_") => "p2_http",
83-
s if s.starts_with("p2_cli_") => "p2_cli",
8489
s if s.starts_with("p2_api_") => "p2_api",
8590
s if s.starts_with("p2_") => "p2",
91+
s if s.starts_with("p3_http_") => "p3_http",
92+
s if s.starts_with("p3_api_") => "p3_api",
93+
s if s.starts_with("p3_") => "p3",
8694
s if s.starts_with("nn_") => "nn",
8795
s if s.starts_with("piped_") => "piped",
8896
s if s.starts_with("dwarf_") => "dwarf",
8997
s if s.starts_with("config_") => "config",
9098
s if s.starts_with("keyvalue_") => "keyvalue",
9199
s if s.starts_with("tls_") => "tls",
92100
s if s.starts_with("async_") => "async",
93-
s if s.starts_with("p3_http_") => "p3_http",
94-
s if s.starts_with("p3_api_") => "p3_api",
95-
s if s.starts_with("p3_") => "p3",
96101
s if s.starts_with("fuzz_") => "fuzz",
97102
// If you're reading this because you hit this panic, either add
98103
// it to a test suite above or add a new "suite". The purpose of
@@ -283,7 +288,7 @@ impl Artifacts {
283288
// Prevent stray files for now that we don't understand.
284289
Some(_) => panic!("unknown file extension on {path:?}"),
285290

286-
None => unreachable!(),
291+
None => unreachable!("no extension in path {path:?}"),
287292
}
288293
}
289294
}
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+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
use std::process;
2+
use test_programs::preview1::open_scratch_directory;
3+
4+
fn main() {
5+
let arg = std::env::args().nth(1).unwrap();
6+
let dir_fd = match open_scratch_directory(&arg) {
7+
Ok(dir_fd) => dir_fd,
8+
Err(err) => {
9+
eprintln!("{err}");
10+
process::exit(1)
11+
}
12+
};
13+
14+
// Wait for just one timeout (maybe hitting a fast path)
15+
let subs = [subscribe_timeout(0)];
16+
for _ in 0..1000 {
17+
let mut events = [empty_event()];
18+
unsafe {
19+
wasip1::poll_oneoff(subs.as_ptr(), events.as_mut_ptr(), 1).unwrap();
20+
}
21+
}
22+
23+
// Wait for two timeouts
24+
let subs = [subscribe_timeout(0), subscribe_timeout(0)];
25+
for _ in 0..1000 {
26+
let mut events = [empty_event(), empty_event()];
27+
unsafe {
28+
wasip1::poll_oneoff(subs.as_ptr(), events.as_mut_ptr(), 2).unwrap();
29+
}
30+
}
31+
32+
let file_fd = unsafe {
33+
wasip1::path_open(
34+
dir_fd,
35+
0,
36+
"hello.txt",
37+
wasip1::OFLAGS_CREAT,
38+
wasip1::RIGHTS_FD_WRITE | wasip1::RIGHTS_FD_READ,
39+
0,
40+
0,
41+
)
42+
.expect("creating a file for writing")
43+
};
44+
45+
// Wait for a timeout fd operations
46+
let subs = [
47+
subscribe_timeout(0),
48+
subscribe_fd(wasip1::EVENTTYPE_FD_READ, file_fd),
49+
subscribe_fd(wasip1::EVENTTYPE_FD_WRITE, file_fd),
50+
];
51+
for _ in 0..1000 {
52+
let mut events = [empty_event(), empty_event(), empty_event()];
53+
unsafe {
54+
wasip1::poll_oneoff(subs.as_ptr(), events.as_mut_ptr(), 3).unwrap();
55+
}
56+
}
57+
}
58+
59+
fn subscribe_timeout(timeout: u64) -> wasip1::Subscription {
60+
wasip1::Subscription {
61+
userdata: 0,
62+
u: wasip1::SubscriptionU {
63+
tag: wasip1::EVENTTYPE_CLOCK.raw(),
64+
u: wasip1::SubscriptionUU {
65+
clock: wasip1::SubscriptionClock {
66+
id: wasip1::CLOCKID_MONOTONIC,
67+
timeout,
68+
precision: 0,
69+
flags: 0,
70+
},
71+
},
72+
},
73+
}
74+
}
75+
76+
fn subscribe_fd(ty: wasip1::Eventtype, file_descriptor: wasip1::Fd) -> wasip1::Subscription {
77+
wasip1::Subscription {
78+
userdata: 0,
79+
u: wasip1::SubscriptionU {
80+
tag: ty.raw(),
81+
u: wasip1::SubscriptionUU {
82+
fd_read: wasip1::SubscriptionFdReadwrite { file_descriptor },
83+
},
84+
},
85+
}
86+
}
87+
88+
fn empty_event() -> wasip1::Event {
89+
wasip1::Event {
90+
error: wasip1::ERRNO_SUCCESS,
91+
fd_readwrite: wasip1::EventFdReadwrite {
92+
nbytes: 0,
93+
flags: 0,
94+
},
95+
type_: wasip1::EVENTTYPE_CLOCK,
96+
userdata: 0,
97+
}
98+
}

crates/test-programs/src/bin/p2_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+
}

0 commit comments

Comments
 (0)