Skip to content

Commit e0e8f90

Browse files
committed
feat: extract common WasiCtxBuilder
Signed-off-by: Roman Volosatovs <rvolosatovs@riseup.net>
1 parent 72a7abb commit e0e8f90

File tree

3 files changed

+351
-119
lines changed

3 files changed

+351
-119
lines changed

crates/wasi/src/ctx.rs

Lines changed: 301 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,301 @@
1+
use crate::clocks::host::{monotonic_clock, wall_clock};
2+
use crate::clocks::{HostMonotonicClock, HostWallClock};
3+
use crate::net::{SocketAddrCheck, SocketAddrUse};
4+
use crate::random;
5+
use cap_rand::{Rng, RngCore, SeedableRng};
6+
use std::future::Future;
7+
use std::net::SocketAddr;
8+
use std::pin::Pin;
9+
use std::sync::Arc;
10+
11+
/// Builder-style structure used to create a WASI context.
12+
///
13+
/// This type is used to create a WASI context that is considered per-[`Store`]
14+
/// state.
15+
/// This is a low-level abstraction, users of this crate are expected to use it via
16+
/// builders specific to WASI version used, for example,
17+
/// [p2::WasiCtxBuilder](crate::p2::WasiCtxBuilder)
18+
///
19+
/// [`Store`]: wasmtime::Store
20+
pub(crate) struct WasiCtxBuilder {
21+
pub(crate) env: Vec<(String, String)>,
22+
pub(crate) args: Vec<String>,
23+
pub(crate) socket_addr_check: SocketAddrCheck,
24+
pub(crate) random: Box<dyn RngCore + Send>,
25+
pub(crate) insecure_random: Box<dyn RngCore + Send>,
26+
pub(crate) insecure_random_seed: u128,
27+
pub(crate) wall_clock: Box<dyn HostWallClock + Send>,
28+
pub(crate) monotonic_clock: Box<dyn HostMonotonicClock + Send>,
29+
pub(crate) allowed_network_uses: AllowedNetworkUses,
30+
pub(crate) allow_blocking_current_thread: bool,
31+
}
32+
33+
impl WasiCtxBuilder {
34+
/// Creates a builder for a new context with default parameters set.
35+
///
36+
/// The current defaults are:
37+
///
38+
/// * stdin is closed
39+
/// * stdout and stderr eat all input and it doesn't go anywhere
40+
/// * no env vars
41+
/// * no arguments
42+
/// * no preopens
43+
/// * clocks use the host implementation of wall/monotonic clocks
44+
/// * RNGs are all initialized with random state and suitable generator
45+
/// quality to satisfy the requirements of WASI APIs.
46+
/// * TCP/UDP are allowed but all addresses are denied by default.
47+
/// * IP name lookup is denied by default.
48+
///
49+
/// These defaults can all be updated via the various builder configuration
50+
/// methods below.
51+
pub(crate) fn new() -> Self {
52+
// For the insecure random API, use `SmallRng`, which is fast. It's
53+
// also insecure, but that's the deal here.
54+
let insecure_random = Box::new(
55+
cap_rand::rngs::SmallRng::from_rng(cap_rand::thread_rng(cap_rand::ambient_authority()))
56+
.unwrap(),
57+
);
58+
59+
// For the insecure random seed, use a `u128` generated from
60+
// `thread_rng()`, so that it's not guessable from the insecure_random
61+
// API.
62+
let insecure_random_seed =
63+
cap_rand::thread_rng(cap_rand::ambient_authority()).r#gen::<u128>();
64+
Self {
65+
env: Vec::new(),
66+
args: Vec::new(),
67+
socket_addr_check: SocketAddrCheck::default(),
68+
random: random::thread_rng(),
69+
insecure_random,
70+
insecure_random_seed,
71+
wall_clock: wall_clock(),
72+
monotonic_clock: monotonic_clock(),
73+
allowed_network_uses: AllowedNetworkUses::default(),
74+
allow_blocking_current_thread: false,
75+
}
76+
}
77+
78+
/// Configures whether or not blocking operations made through this
79+
/// `WasiCtx` are allowed to block the current thread.
80+
///
81+
/// WASI is currently implemented on top of the Rust
82+
/// [Tokio](https://tokio.rs/) library. While most WASI APIs are
83+
/// non-blocking some are instead blocking from the perspective of
84+
/// WebAssembly. For example opening a file is a blocking operation with
85+
/// respect to WebAssembly but it's implemented as an asynchronous operation
86+
/// on the host. This is currently done with Tokio's
87+
/// [`spawn_blocking`](https://docs.rs/tokio/latest/tokio/task/fn.spawn_blocking.html).
88+
///
89+
/// When WebAssembly is used in a synchronous context, for example when
90+
/// [`Config::async_support`] is disabled, then this asynchronous operation
91+
/// is quickly turned back into a synchronous operation with a `block_on` in
92+
/// Rust. This switching back-and-forth between a blocking a non-blocking
93+
/// context can have overhead, and this option exists to help alleviate this
94+
/// overhead.
95+
///
96+
/// This option indicates that for WASI functions that are blocking from the
97+
/// perspective of WebAssembly it's ok to block the native thread as well.
98+
/// This means that this back-and-forth between async and sync won't happen
99+
/// and instead blocking operations are performed on-thread (such as opening
100+
/// a file). This can improve the performance of WASI operations when async
101+
/// support is disabled.
102+
///
103+
/// [`Config::async_support`]: https://docs.rs/wasmtime/latest/wasmtime/struct.Config.html#method.async_support
104+
pub fn allow_blocking_current_thread(&mut self, enable: bool) -> &mut Self {
105+
self.allow_blocking_current_thread = enable;
106+
self
107+
}
108+
109+
/// Appends multiple environment variables at once for this builder.
110+
///
111+
/// All environment variables are appended to the list of environment
112+
/// variables that this builder will configure.
113+
///
114+
/// At this time environment variables are not deduplicated and if the same
115+
/// key is set twice then the guest will see two entries for the same key.
116+
pub fn envs(&mut self, env: &[(impl AsRef<str>, impl AsRef<str>)]) -> &mut Self {
117+
self.env.extend(
118+
env.iter()
119+
.map(|(k, v)| (k.as_ref().to_owned(), v.as_ref().to_owned())),
120+
);
121+
self
122+
}
123+
124+
/// Appends a single environment variable for this builder.
125+
///
126+
/// At this time environment variables are not deduplicated and if the same
127+
/// key is set twice then the guest will see two entries for the same key.
128+
pub fn env(&mut self, k: impl AsRef<str>, v: impl AsRef<str>) -> &mut Self {
129+
self.env
130+
.push((k.as_ref().to_owned(), v.as_ref().to_owned()));
131+
self
132+
}
133+
134+
/// Configures all environment variables to be inherited from the calling
135+
/// process into this configuration.
136+
///
137+
/// This will use [`envs`](WasiCtxBuilder::envs) to append all host-defined
138+
/// environment variables.
139+
pub fn inherit_env(&mut self) -> &mut Self {
140+
self.envs(&std::env::vars().collect::<Vec<(String, String)>>())
141+
}
142+
143+
/// Appends a list of arguments to the argument array to pass to wasm.
144+
pub fn args(&mut self, args: &[impl AsRef<str>]) -> &mut Self {
145+
self.args.extend(args.iter().map(|a| a.as_ref().to_owned()));
146+
self
147+
}
148+
149+
/// Appends a single argument to get passed to wasm.
150+
pub fn arg(&mut self, arg: impl AsRef<str>) -> &mut Self {
151+
self.args.push(arg.as_ref().to_owned());
152+
self
153+
}
154+
155+
/// Appends all host process arguments to the list of arguments to get
156+
/// passed to wasm.
157+
pub fn inherit_args(&mut self) -> &mut Self {
158+
self.args(&std::env::args().collect::<Vec<String>>())
159+
}
160+
161+
/// Set the generator for the `wasi:random/random` number generator to the
162+
/// custom generator specified.
163+
///
164+
/// Note that contexts have a default RNG configured which is a suitable
165+
/// generator for WASI and is configured with a random seed per-context.
166+
///
167+
/// Guest code may rely on this random number generator to produce fresh
168+
/// unpredictable random data in order to maintain its security invariants,
169+
/// and ideally should use the insecure random API otherwise, so using any
170+
/// prerecorded or otherwise predictable data may compromise security.
171+
pub fn secure_random(&mut self, random: impl RngCore + Send + 'static) -> &mut Self {
172+
self.random = Box::new(random);
173+
self
174+
}
175+
176+
/// Configures the generator for `wasi:random/insecure`.
177+
///
178+
/// The `insecure_random` generator provided will be used for all randomness
179+
/// requested by the `wasi:random/insecure` interface.
180+
pub fn insecure_random(&mut self, insecure_random: impl RngCore + Send + 'static) -> &mut Self {
181+
self.insecure_random = Box::new(insecure_random);
182+
self
183+
}
184+
185+
/// Configures the seed to be returned from `wasi:random/insecure-seed` to
186+
/// the specified custom value.
187+
///
188+
/// By default this number is randomly generated when a builder is created.
189+
pub fn insecure_random_seed(&mut self, insecure_random_seed: u128) -> &mut Self {
190+
self.insecure_random_seed = insecure_random_seed;
191+
self
192+
}
193+
194+
/// Configures `wasi:clocks/wall-clock` to use the `clock` specified.
195+
///
196+
/// By default the host's wall clock is used.
197+
pub fn wall_clock(&mut self, clock: impl HostWallClock + 'static) -> &mut Self {
198+
self.wall_clock = Box::new(clock);
199+
self
200+
}
201+
202+
/// Configures `wasi:clocks/monotonic-clock` to use the `clock` specified.
203+
///
204+
/// By default the host's monotonic clock is used.
205+
pub fn monotonic_clock(&mut self, clock: impl HostMonotonicClock + 'static) -> &mut Self {
206+
self.monotonic_clock = Box::new(clock);
207+
self
208+
}
209+
210+
/// Allow all network addresses accessible to the host.
211+
///
212+
/// This method will inherit all network addresses meaning that any address
213+
/// can be bound by the guest or connected to by the guest using any
214+
/// protocol.
215+
///
216+
/// See also [`WasiCtxBuilder::socket_addr_check`].
217+
pub fn inherit_network(&mut self) -> &mut Self {
218+
self.socket_addr_check(|_, _| Box::pin(async { true }))
219+
}
220+
221+
/// A check that will be called for each socket address that is used.
222+
///
223+
/// Returning `true` will permit socket connections to the `SocketAddr`,
224+
/// while returning `false` will reject the connection.
225+
pub fn socket_addr_check<F>(&mut self, check: F) -> &mut Self
226+
where
227+
F: Fn(SocketAddr, SocketAddrUse) -> Pin<Box<dyn Future<Output = bool> + Send + Sync>>
228+
+ Send
229+
+ Sync
230+
+ 'static,
231+
{
232+
self.socket_addr_check = SocketAddrCheck(Arc::new(check));
233+
self
234+
}
235+
236+
/// Allow usage of `wasi:sockets/ip-name-lookup`
237+
///
238+
/// By default this is disabled.
239+
pub fn allow_ip_name_lookup(&mut self, enable: bool) -> &mut Self {
240+
self.allowed_network_uses.ip_name_lookup = enable;
241+
self
242+
}
243+
244+
/// Allow usage of UDP.
245+
///
246+
/// This is enabled by default, but can be disabled if UDP should be blanket
247+
/// disabled.
248+
pub fn allow_udp(&mut self, enable: bool) -> &mut Self {
249+
self.allowed_network_uses.udp = enable;
250+
self
251+
}
252+
253+
/// Allow usage of TCP
254+
///
255+
/// This is enabled by default, but can be disabled if TCP should be blanket
256+
/// disabled.
257+
pub fn allow_tcp(&mut self, enable: bool) -> &mut Self {
258+
self.allowed_network_uses.tcp = enable;
259+
self
260+
}
261+
}
262+
263+
pub struct AllowedNetworkUses {
264+
pub ip_name_lookup: bool,
265+
pub udp: bool,
266+
pub tcp: bool,
267+
}
268+
269+
impl Default for AllowedNetworkUses {
270+
fn default() -> Self {
271+
Self {
272+
ip_name_lookup: false,
273+
udp: true,
274+
tcp: true,
275+
}
276+
}
277+
}
278+
279+
impl AllowedNetworkUses {
280+
pub(crate) fn check_allowed_udp(&self) -> std::io::Result<()> {
281+
if !self.udp {
282+
return Err(std::io::Error::new(
283+
std::io::ErrorKind::PermissionDenied,
284+
"UDP is not allowed",
285+
));
286+
}
287+
288+
Ok(())
289+
}
290+
291+
pub(crate) fn check_allowed_tcp(&self) -> std::io::Result<()> {
292+
if !self.tcp {
293+
return Err(std::io::Error::new(
294+
std::io::ErrorKind::PermissionDenied,
295+
"TCP is not allowed",
296+
));
297+
}
298+
299+
Ok(())
300+
}
301+
}

crates/wasi/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
//! For WASIp1 and core modules, see the [`preview1`] module documentation.
1212
1313
mod clocks;
14+
mod ctx;
1415
mod error;
1516
mod fs;
1617
mod net;
@@ -23,6 +24,7 @@ mod random;
2324
pub mod runtime;
2425

2526
pub use self::clocks::{HostMonotonicClock, HostWallClock};
27+
pub(crate) use self::ctx::WasiCtxBuilder;
2628
pub use self::error::{I32Exit, TrappableError};
2729
pub use self::fs::{DirPerms, FilePerms, OpenMode};
2830
pub use self::net::{Network, SocketAddrUse};

0 commit comments

Comments
 (0)