wasmtime_wasi/ctx.rs
1use crate::cli::{StdinStream, StdoutStream, WasiCliCtx};
2use crate::clocks::{HostMonotonicClock, HostWallClock, WasiClocksCtx};
3use crate::filesystem::WasiFilesystemCtx;
4use crate::p2::filesystem::Dir;
5use crate::random::WasiRandomCtx;
6use crate::sockets::{SocketAddrCheck, SocketAddrUse, WasiSocketsCtx};
7use crate::{DirPerms, FilePerms, OpenMode};
8use anyhow::Result;
9use cap_rand::RngCore;
10use cap_std::ambient_authority;
11use std::future::Future;
12use std::mem;
13use std::net::SocketAddr;
14use std::path::Path;
15use std::pin::Pin;
16use tokio::io::{stderr, stdin, stdout};
17
18/// Builder-style structure used to create a [`WasiCtx`].
19///
20/// This type is used to create a [`WasiCtx`] that is considered per-[`Store`]
21/// state. The [`build`][WasiCtxBuilder::build] method is used to finish the
22/// building process and produce a finalized [`WasiCtx`].
23///
24/// # Examples
25///
26/// ```
27/// use wasmtime_wasi::WasiCtx;
28///
29/// let mut wasi = WasiCtx::builder();
30/// wasi.arg("./foo.wasm");
31/// wasi.arg("--help");
32/// wasi.env("FOO", "bar");
33///
34/// let wasi: WasiCtx = wasi.build();
35/// ```
36///
37/// [`Store`]: wasmtime::Store
38#[derive(Default)]
39pub struct WasiCtxBuilder {
40 cli: WasiCliCtx,
41 clocks: WasiClocksCtx,
42 filesystem: WasiFilesystemCtx,
43 random: WasiRandomCtx,
44 sockets: WasiSocketsCtx,
45 built: bool,
46}
47
48impl WasiCtxBuilder {
49 /// Creates a builder for a new context with default parameters set.
50 ///
51 /// The current defaults are:
52 ///
53 /// * stdin is closed
54 /// * stdout and stderr eat all input and it doesn't go anywhere
55 /// * no env vars
56 /// * no arguments
57 /// * no preopens
58 /// * clocks use the host implementation of wall/monotonic clocks
59 /// * RNGs are all initialized with random state and suitable generator
60 /// quality to satisfy the requirements of WASI APIs.
61 /// * TCP/UDP are allowed but all addresses are denied by default.
62 /// * `wasi:sockets/ip-name-lookup` is denied by default.
63 ///
64 /// These defaults can all be updated via the various builder configuration
65 /// methods below.
66 pub fn new() -> Self {
67 Self::default()
68 }
69
70 /// Provides a custom implementation of stdin to use.
71 ///
72 /// By default stdin is closed but an example of using the host's native
73 /// stdin looks like:
74 ///
75 /// ```
76 /// use wasmtime_wasi::WasiCtx;
77 /// use wasmtime_wasi::cli::stdin;
78 ///
79 /// let mut wasi = WasiCtx::builder();
80 /// wasi.stdin(stdin());
81 /// ```
82 ///
83 /// Note that inheriting the process's stdin can also be done through
84 /// [`inherit_stdin`](WasiCtxBuilder::inherit_stdin).
85 pub fn stdin(&mut self, stdin: impl StdinStream + 'static) -> &mut Self {
86 self.cli.stdin = Box::new(stdin);
87 self
88 }
89
90 /// Same as [`stdin`](WasiCtxBuilder::stdin), but for stdout.
91 pub fn stdout(&mut self, stdout: impl StdoutStream + 'static) -> &mut Self {
92 self.cli.stdout = Box::new(stdout);
93 self
94 }
95
96 /// Same as [`stdin`](WasiCtxBuilder::stdin), but for stderr.
97 pub fn stderr(&mut self, stderr: impl StdoutStream + 'static) -> &mut Self {
98 self.cli.stderr = Box::new(stderr);
99 self
100 }
101
102 /// Configures this context's stdin stream to read the host process's
103 /// stdin.
104 ///
105 /// Note that concurrent reads of stdin can produce surprising results so
106 /// when using this it's typically best to have a single wasm instance in
107 /// the process using this.
108 pub fn inherit_stdin(&mut self) -> &mut Self {
109 self.stdin(stdin())
110 }
111
112 /// Configures this context's stdout stream to write to the host process's
113 /// stdout.
114 ///
115 /// Note that unlike [`inherit_stdin`](WasiCtxBuilder::inherit_stdin)
116 /// multiple instances printing to stdout works well.
117 pub fn inherit_stdout(&mut self) -> &mut Self {
118 self.stdout(stdout())
119 }
120
121 /// Configures this context's stderr stream to write to the host process's
122 /// stderr.
123 ///
124 /// Note that unlike [`inherit_stdin`](WasiCtxBuilder::inherit_stdin)
125 /// multiple instances printing to stderr works well.
126 pub fn inherit_stderr(&mut self) -> &mut Self {
127 self.stderr(stderr())
128 }
129
130 /// Configures all of stdin, stdout, and stderr to be inherited from the
131 /// host process.
132 ///
133 /// See [`inherit_stdin`](WasiCtxBuilder::inherit_stdin) for some rationale
134 /// on why this should only be done in situations of
135 /// one-instance-per-process.
136 pub fn inherit_stdio(&mut self) -> &mut Self {
137 self.inherit_stdin().inherit_stdout().inherit_stderr()
138 }
139
140 /// Configures whether or not blocking operations made through this
141 /// `WasiCtx` are allowed to block the current thread.
142 ///
143 /// WASI is currently implemented on top of the Rust
144 /// [Tokio](https://tokio.rs/) library. While most WASI APIs are
145 /// non-blocking some are instead blocking from the perspective of
146 /// WebAssembly. For example opening a file is a blocking operation with
147 /// respect to WebAssembly but it's implemented as an asynchronous operation
148 /// on the host. This is currently done with Tokio's
149 /// [`spawn_blocking`](https://docs.rs/tokio/latest/tokio/task/fn.spawn_blocking.html).
150 ///
151 /// When WebAssembly is used in a synchronous context, for example when
152 /// [`Config::async_support`] is disabled, then this asynchronous operation
153 /// is quickly turned back into a synchronous operation with a `block_on` in
154 /// Rust. This switching back-and-forth between a blocking a non-blocking
155 /// context can have overhead, and this option exists to help alleviate this
156 /// overhead.
157 ///
158 /// This option indicates that for WASI functions that are blocking from the
159 /// perspective of WebAssembly it's ok to block the native thread as well.
160 /// This means that this back-and-forth between async and sync won't happen
161 /// and instead blocking operations are performed on-thread (such as opening
162 /// a file). This can improve the performance of WASI operations when async
163 /// support is disabled.
164 ///
165 /// [`Config::async_support`]: https://docs.rs/wasmtime/latest/wasmtime/struct.Config.html#method.async_support
166 pub fn allow_blocking_current_thread(&mut self, enable: bool) -> &mut Self {
167 self.filesystem.allow_blocking_current_thread = enable;
168 self
169 }
170
171 /// Appends multiple environment variables at once for this builder.
172 ///
173 /// All environment variables are appended to the list of environment
174 /// variables that this builder will configure.
175 ///
176 /// At this time environment variables are not deduplicated and if the same
177 /// key is set twice then the guest will see two entries for the same key.
178 ///
179 /// # Examples
180 ///
181 /// ```
182 /// use wasmtime_wasi::WasiCtxBuilder;
183 ///
184 /// let mut wasi = WasiCtxBuilder::new();
185 /// wasi.envs(&[
186 /// ("FOO", "bar"),
187 /// ("HOME", "/somewhere"),
188 /// ]);
189 /// ```
190 pub fn envs(&mut self, env: &[(impl AsRef<str>, impl AsRef<str>)]) -> &mut Self {
191 self.cli.environment.extend(
192 env.iter()
193 .map(|(k, v)| (k.as_ref().to_owned(), v.as_ref().to_owned())),
194 );
195 self
196 }
197
198 /// Appends a single environment variable for this builder.
199 ///
200 /// At this time environment variables are not deduplicated and if the same
201 /// key is set twice then the guest will see two entries for the same key.
202 ///
203 /// # Examples
204 ///
205 /// ```
206 /// use wasmtime_wasi::WasiCtxBuilder;
207 ///
208 /// let mut wasi = WasiCtxBuilder::new();
209 /// wasi.env("FOO", "bar");
210 /// ```
211 pub fn env(&mut self, k: impl AsRef<str>, v: impl AsRef<str>) -> &mut Self {
212 self.cli
213 .environment
214 .push((k.as_ref().to_owned(), v.as_ref().to_owned()));
215 self
216 }
217
218 /// Configures all environment variables to be inherited from the calling
219 /// process into this configuration.
220 ///
221 /// This will use [`envs`](WasiCtxBuilder::envs) to append all host-defined
222 /// environment variables.
223 pub fn inherit_env(&mut self) -> &mut Self {
224 self.envs(&std::env::vars().collect::<Vec<(String, String)>>())
225 }
226
227 /// Appends a list of arguments to the argument array to pass to wasm.
228 pub fn args(&mut self, args: &[impl AsRef<str>]) -> &mut Self {
229 self.cli
230 .arguments
231 .extend(args.iter().map(|a| a.as_ref().to_owned()));
232 self
233 }
234
235 /// Appends a single argument to get passed to wasm.
236 pub fn arg(&mut self, arg: impl AsRef<str>) -> &mut Self {
237 self.cli.arguments.push(arg.as_ref().to_owned());
238 self
239 }
240
241 /// Appends all host process arguments to the list of arguments to get
242 /// passed to wasm.
243 pub fn inherit_args(&mut self) -> &mut Self {
244 self.args(&std::env::args().collect::<Vec<String>>())
245 }
246
247 /// Configures a "preopened directory" to be available to WebAssembly.
248 ///
249 /// By default WebAssembly does not have access to the filesystem because
250 /// there are no preopened directories. All filesystem operations, such as
251 /// opening a file, are done through a preexisting handle. This means that
252 /// to provide WebAssembly access to a directory it must be configured
253 /// through this API.
254 ///
255 /// WASI will also prevent access outside of files provided here. For
256 /// example `..` can't be used to traverse up from the `host_path` provided here
257 /// to the containing directory.
258 ///
259 /// * `host_path` - a path to a directory on the host to open and make
260 /// accessible to WebAssembly. Note that the name of this directory in the
261 /// guest is configured with `guest_path` below.
262 /// * `guest_path` - the name of the preopened directory from WebAssembly's
263 /// perspective. Note that this does not need to match the host's name for
264 /// the directory.
265 /// * `dir_perms` - this is the permissions that wasm will have to operate on
266 /// `guest_path`. This can be used, for example, to provide readonly access to a
267 /// directory.
268 /// * `file_perms` - similar to `dir_perms` but corresponds to the maximum set
269 /// of permissions that can be used for any file in this directory.
270 ///
271 /// # Errors
272 ///
273 /// This method will return an error if `host_path` cannot be opened.
274 ///
275 /// # Examples
276 ///
277 /// ```
278 /// use wasmtime_wasi::WasiCtxBuilder;
279 /// use wasmtime_wasi::{DirPerms, FilePerms};
280 ///
281 /// # fn main() {}
282 /// # fn foo() -> wasmtime::Result<()> {
283 /// let mut wasi = WasiCtxBuilder::new();
284 ///
285 /// // Make `./host-directory` available in the guest as `.`
286 /// wasi.preopened_dir("./host-directory", ".", DirPerms::all(), FilePerms::all());
287 ///
288 /// // Make `./readonly` available in the guest as `./ro`
289 /// wasi.preopened_dir("./readonly", "./ro", DirPerms::READ, FilePerms::READ);
290 /// # Ok(())
291 /// # }
292 /// ```
293 pub fn preopened_dir(
294 &mut self,
295 host_path: impl AsRef<Path>,
296 guest_path: impl AsRef<str>,
297 dir_perms: DirPerms,
298 file_perms: FilePerms,
299 ) -> Result<&mut Self> {
300 let dir = cap_std::fs::Dir::open_ambient_dir(host_path.as_ref(), ambient_authority())?;
301 let mut open_mode = OpenMode::empty();
302 if dir_perms.contains(DirPerms::READ) {
303 open_mode |= OpenMode::READ;
304 }
305 if dir_perms.contains(DirPerms::MUTATE) {
306 open_mode |= OpenMode::WRITE;
307 }
308 self.filesystem.preopens.push((
309 Dir::new(
310 dir,
311 dir_perms,
312 file_perms,
313 open_mode,
314 self.filesystem.allow_blocking_current_thread,
315 ),
316 guest_path.as_ref().to_owned(),
317 ));
318 Ok(self)
319 }
320
321 /// Set the generator for the `wasi:random/random` number generator to the
322 /// custom generator specified.
323 ///
324 /// Note that contexts have a default RNG configured which is a suitable
325 /// generator for WASI and is configured with a random seed per-context.
326 ///
327 /// Guest code may rely on this random number generator to produce fresh
328 /// unpredictable random data in order to maintain its security invariants,
329 /// and ideally should use the insecure random API otherwise, so using any
330 /// prerecorded or otherwise predictable data may compromise security.
331 pub fn secure_random(&mut self, random: impl RngCore + Send + 'static) -> &mut Self {
332 self.random.random = Box::new(random);
333 self
334 }
335
336 /// Configures the generator for `wasi:random/insecure`.
337 ///
338 /// The `insecure_random` generator provided will be used for all randomness
339 /// requested by the `wasi:random/insecure` interface.
340 pub fn insecure_random(&mut self, insecure_random: impl RngCore + Send + 'static) -> &mut Self {
341 self.random.insecure_random = Box::new(insecure_random);
342 self
343 }
344
345 /// Configures the seed to be returned from `wasi:random/insecure-seed` to
346 /// the specified custom value.
347 ///
348 /// By default this number is randomly generated when a builder is created.
349 pub fn insecure_random_seed(&mut self, insecure_random_seed: u128) -> &mut Self {
350 self.random.insecure_random_seed = insecure_random_seed;
351 self
352 }
353
354 /// Configures `wasi:clocks/wall-clock` to use the `clock` specified.
355 ///
356 /// By default the host's wall clock is used.
357 pub fn wall_clock(&mut self, clock: impl HostWallClock + 'static) -> &mut Self {
358 self.clocks.wall_clock = Box::new(clock);
359 self
360 }
361
362 /// Configures `wasi:clocks/monotonic-clock` to use the `clock` specified.
363 ///
364 /// By default the host's monotonic clock is used.
365 pub fn monotonic_clock(&mut self, clock: impl HostMonotonicClock + 'static) -> &mut Self {
366 self.clocks.monotonic_clock = Box::new(clock);
367 self
368 }
369
370 /// Allow all network addresses accessible to the host.
371 ///
372 /// This method will inherit all network addresses meaning that any address
373 /// can be bound by the guest or connected to by the guest using any
374 /// protocol.
375 ///
376 /// See also [`WasiCtxBuilder::socket_addr_check`].
377 pub fn inherit_network(&mut self) -> &mut Self {
378 self.socket_addr_check(|_, _| Box::pin(async { true }))
379 }
380
381 /// A check that will be called for each socket address that is used.
382 ///
383 /// Returning `true` will permit socket connections to the `SocketAddr`,
384 /// while returning `false` will reject the connection.
385 pub fn socket_addr_check<F>(&mut self, check: F) -> &mut Self
386 where
387 F: Fn(SocketAddr, SocketAddrUse) -> Pin<Box<dyn Future<Output = bool> + Send + Sync>>
388 + Send
389 + Sync
390 + 'static,
391 {
392 self.sockets.socket_addr_check = SocketAddrCheck::new(check);
393 self
394 }
395
396 /// Allow usage of `wasi:sockets/ip-name-lookup`
397 ///
398 /// By default this is disabled.
399 pub fn allow_ip_name_lookup(&mut self, enable: bool) -> &mut Self {
400 self.sockets.allowed_network_uses.ip_name_lookup = enable;
401 self
402 }
403
404 /// Allow usage of UDP.
405 ///
406 /// This is enabled by default, but can be disabled if UDP should be blanket
407 /// disabled.
408 pub fn allow_udp(&mut self, enable: bool) -> &mut Self {
409 self.sockets.allowed_network_uses.udp = enable;
410 self
411 }
412
413 /// Allow usage of TCP
414 ///
415 /// This is enabled by default, but can be disabled if TCP should be blanket
416 /// disabled.
417 pub fn allow_tcp(&mut self, enable: bool) -> &mut Self {
418 self.sockets.allowed_network_uses.tcp = enable;
419 self
420 }
421
422 /// Uses the configured context so far to construct the final [`WasiCtx`].
423 ///
424 /// Note that each `WasiCtxBuilder` can only be used to "build" once, and
425 /// calling this method twice will panic.
426 ///
427 /// # Panics
428 ///
429 /// Panics if this method is called twice. Each [`WasiCtxBuilder`] can be
430 /// used to create only a single [`WasiCtx`]. Repeated usage of this method
431 /// is not allowed and should use a second builder instead.
432 pub fn build(&mut self) -> WasiCtx {
433 assert!(!self.built);
434
435 let Self {
436 cli,
437 clocks,
438 filesystem,
439 random,
440 sockets,
441 built: _,
442 } = mem::replace(self, Self::new());
443 self.built = true;
444
445 WasiCtx {
446 cli,
447 clocks,
448 filesystem,
449 random,
450 sockets,
451 }
452 }
453 /// Builds a WASIp1 context instead of a [`WasiCtx`].
454 ///
455 /// This method is the same as [`build`](WasiCtxBuilder::build) but it
456 /// creates a [`WasiP1Ctx`] instead. This is intended for use with the
457 /// [`p1`] module of this crate
458 ///
459 /// [`WasiP1Ctx`]: crate::p1::WasiP1Ctx
460 /// [`p1`]: crate::p1
461 ///
462 /// # Panics
463 ///
464 /// Panics if this method is called twice. Each [`WasiCtxBuilder`] can be
465 /// used to create only a single [`WasiCtx`] or [`WasiP1Ctx`]. Repeated
466 /// usage of this method is not allowed and should use a second builder
467 /// instead.
468 #[cfg(feature = "p1")]
469 pub fn build_p1(&mut self) -> crate::p1::WasiP1Ctx {
470 let wasi = self.build();
471 crate::p1::WasiP1Ctx::new(wasi)
472 }
473}
474
475/// Per-[`Store`] state which holds state necessary to implement WASI from this
476/// crate.
477///
478/// This structure is created through [`WasiCtxBuilder`] and is stored within
479/// the `T` of [`Store<T>`][`Store`]. Access to the structure is provided
480/// through the [`WasiView`](crate::WasiView) trait as an implementation on `T`.
481///
482/// Note that this structure itself does not have any accessors, it's here for
483/// internal use within the `wasmtime-wasi` crate's implementation of
484/// bindgen-generated traits.
485///
486/// [`Store`]: wasmtime::Store
487///
488/// # Example
489///
490/// ```
491/// use wasmtime_wasi::{ResourceTable, WasiCtx, WasiCtxView, WasiView, WasiCtxBuilder};
492///
493/// struct MyState {
494/// ctx: WasiCtx,
495/// table: ResourceTable,
496/// }
497///
498/// impl WasiView for MyState {
499/// fn ctx(&mut self) -> WasiCtxView<'_> {
500/// WasiCtxView { ctx: &mut self.ctx, table: &mut self.table }
501/// }
502/// }
503///
504/// impl MyState {
505/// fn new() -> MyState {
506/// let mut wasi = WasiCtxBuilder::new();
507/// wasi.arg("./foo.wasm");
508/// wasi.arg("--help");
509/// wasi.env("FOO", "bar");
510///
511/// MyState {
512/// ctx: wasi.build(),
513/// table: ResourceTable::new(),
514/// }
515/// }
516/// }
517/// ```
518#[derive(Default)]
519pub struct WasiCtx {
520 pub(crate) cli: WasiCliCtx,
521 pub(crate) clocks: WasiClocksCtx,
522 pub(crate) filesystem: WasiFilesystemCtx,
523 pub(crate) random: WasiRandomCtx,
524 pub(crate) sockets: WasiSocketsCtx,
525}
526
527impl WasiCtx {
528 /// Convenience function for calling [`WasiCtxBuilder::new`].
529 pub fn builder() -> WasiCtxBuilder {
530 WasiCtxBuilder::new()
531 }
532}