Coverage Report

Created: 2026-01-22 08:11

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