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