wasmtime_wasi/p2/
ctx.rs

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