wasmtime_wasi/ctx.rs
1use crate::{
2 clocks::{
3 host::{monotonic_clock, wall_clock},
4 HostMonotonicClock, HostWallClock,
5 },
6 filesystem::{Dir, OpenMode},
7 network::{SocketAddrCheck, SocketAddrUse},
8 pipe, random, stdio,
9 stdio::{StdinStream, StdoutStream},
10 DirPerms, FilePerms,
11};
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::{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::{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::{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::{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::{WasiCtxBuilder, DirPerms, FilePerms};
313 ///
314 /// # fn main() {}
315 /// # fn foo() -> wasmtime::Result<()> {
316 /// let mut wasi = WasiCtxBuilder::new();
317 ///
318 /// // Make `./host-directory` available in the guest as `.`
319 /// wasi.preopened_dir("./host-directory", ".", DirPerms::all(), FilePerms::all());
320 ///
321 /// // Make `./readonly` available in the guest as `./ro`
322 /// wasi.preopened_dir("./readonly", "./ro", DirPerms::READ, FilePerms::READ);
323 /// # Ok(())
324 /// # }
325 /// ```
326 pub fn preopened_dir(
327 &mut self,
328 host_path: impl AsRef<Path>,
329 guest_path: impl AsRef<str>,
330 dir_perms: DirPerms,
331 file_perms: FilePerms,
332 ) -> Result<&mut Self> {
333 let dir = cap_std::fs::Dir::open_ambient_dir(host_path.as_ref(), ambient_authority())?;
334 let mut open_mode = OpenMode::empty();
335 if dir_perms.contains(DirPerms::READ) {
336 open_mode |= OpenMode::READ;
337 }
338 if dir_perms.contains(DirPerms::MUTATE) {
339 open_mode |= OpenMode::WRITE;
340 }
341 self.preopens.push((
342 Dir::new(
343 dir,
344 dir_perms,
345 file_perms,
346 open_mode,
347 self.allow_blocking_current_thread,
348 ),
349 guest_path.as_ref().to_owned(),
350 ));
351 Ok(self)
352 }
353
354 /// Set the generator for the `wasi:random/random` number generator to the
355 /// custom generator specified.
356 ///
357 /// Note that contexts have a default RNG configured which is a suitable
358 /// generator for WASI and is configured with a random seed per-context.
359 ///
360 /// Guest code may rely on this random number generator to produce fresh
361 /// unpredictable random data in order to maintain its security invariants,
362 /// and ideally should use the insecure random API otherwise, so using any
363 /// prerecorded or otherwise predictable data may compromise security.
364 pub fn secure_random(&mut self, random: impl RngCore + Send + 'static) -> &mut Self {
365 self.random = Box::new(random);
366 self
367 }
368
369 /// Configures the generator for `wasi:random/insecure`.
370 ///
371 /// The `insecure_random` generator provided will be used for all randomness
372 /// requested by the `wasi:random/insecure` interface.
373 pub fn insecure_random(&mut self, insecure_random: impl RngCore + Send + 'static) -> &mut Self {
374 self.insecure_random = Box::new(insecure_random);
375 self
376 }
377
378 /// Configures the seed to be returned from `wasi:random/insecure-seed` to
379 /// the specified custom value.
380 ///
381 /// By default this number is randomly generated when a builder is created.
382 pub fn insecure_random_seed(&mut self, insecure_random_seed: u128) -> &mut Self {
383 self.insecure_random_seed = insecure_random_seed;
384 self
385 }
386
387 /// Configures `wasi:clocks/wall-clock` to use the `clock` specified.
388 ///
389 /// By default the host's wall clock is used.
390 pub fn wall_clock(&mut self, clock: impl HostWallClock + 'static) -> &mut Self {
391 self.wall_clock = Box::new(clock);
392 self
393 }
394
395 /// Configures `wasi:clocks/monotonic-clock` to use the `clock` specified.
396 ///
397 /// By default the host's monotonic clock is used.
398 pub fn monotonic_clock(&mut self, clock: impl HostMonotonicClock + 'static) -> &mut Self {
399 self.monotonic_clock = Box::new(clock);
400 self
401 }
402
403 /// Allow all network addresses accessible to the host.
404 ///
405 /// This method will inherit all network addresses meaning that any address
406 /// can be bound by the guest or connected to by the guest using any
407 /// protocol.
408 ///
409 /// See also [`WasiCtxBuilder::socket_addr_check`].
410 pub fn inherit_network(&mut self) -> &mut Self {
411 self.socket_addr_check(|_, _| Box::pin(async { true }))
412 }
413
414 /// A check that will be called for each socket address that is used.
415 ///
416 /// Returning `true` will permit socket connections to the `SocketAddr`,
417 /// while returning `false` will reject the connection.
418 pub fn socket_addr_check<F>(&mut self, check: F) -> &mut Self
419 where
420 F: Fn(SocketAddr, SocketAddrUse) -> Pin<Box<dyn Future<Output = bool> + Send + Sync>>
421 + Send
422 + Sync
423 + 'static,
424 {
425 self.socket_addr_check = SocketAddrCheck(Arc::new(check));
426 self
427 }
428
429 /// Allow usage of `wasi:sockets/ip-name-lookup`
430 ///
431 /// By default this is disabled.
432 pub fn allow_ip_name_lookup(&mut self, enable: bool) -> &mut Self {
433 self.allowed_network_uses.ip_name_lookup = enable;
434 self
435 }
436
437 /// Allow usage of UDP.
438 ///
439 /// This is enabled by default, but can be disabled if UDP should be blanket
440 /// disabled.
441 pub fn allow_udp(&mut self, enable: bool) -> &mut Self {
442 self.allowed_network_uses.udp = enable;
443 self
444 }
445
446 /// Allow usage of TCP
447 ///
448 /// This is enabled by default, but can be disabled if TCP should be blanket
449 /// disabled.
450 pub fn allow_tcp(&mut self, enable: bool) -> &mut Self {
451 self.allowed_network_uses.tcp = enable;
452 self
453 }
454
455 /// Uses the configured context so far to construct the final [`WasiCtx`].
456 ///
457 /// Note that each `WasiCtxBuilder` can only be used to "build" once, and
458 /// calling this method twice will panic.
459 ///
460 /// # Panics
461 ///
462 /// Panics if this method is called twice. Each [`WasiCtxBuilder`] can be
463 /// used to create only a single [`WasiCtx`]. Repeated usage of this method
464 /// is not allowed and should use a second builder instead.
465 pub fn build(&mut self) -> WasiCtx {
466 assert!(!self.built);
467
468 let Self {
469 stdin,
470 stdout,
471 stderr,
472 env,
473 args,
474 preopens,
475 socket_addr_check,
476 random,
477 insecure_random,
478 insecure_random_seed,
479 wall_clock,
480 monotonic_clock,
481 allowed_network_uses,
482 allow_blocking_current_thread,
483 built: _,
484 } = mem::replace(self, Self::new());
485 self.built = true;
486
487 WasiCtx {
488 stdin,
489 stdout,
490 stderr,
491 env,
492 args,
493 preopens,
494 socket_addr_check,
495 random,
496 insecure_random,
497 insecure_random_seed,
498 wall_clock,
499 monotonic_clock,
500 allowed_network_uses,
501 allow_blocking_current_thread,
502 }
503 }
504
505 /// Builds a WASIp1 context instead of a [`WasiCtx`].
506 ///
507 /// This method is the same as [`build`](WasiCtxBuilder::build) but it
508 /// creates a [`WasiP1Ctx`] instead. This is intended for use with the
509 /// [`preview1`] module of this crate
510 ///
511 /// [`WasiP1Ctx`]: crate::preview1::WasiP1Ctx
512 /// [`preview1`]: crate::preview1
513 ///
514 /// # Panics
515 ///
516 /// Panics if this method is called twice. Each [`WasiCtxBuilder`] can be
517 /// used to create only a single [`WasiCtx`] or [`WasiP1Ctx`]. Repeated
518 /// usage of this method is not allowed and should use a second builder
519 /// instead.
520 #[cfg(feature = "preview1")]
521 pub fn build_p1(&mut self) -> crate::preview1::WasiP1Ctx {
522 let wasi = self.build();
523 crate::preview1::WasiP1Ctx::new(wasi)
524 }
525}
526
527/// Per-[`Store`] state which holds state necessary to implement WASI from this
528/// crate.
529///
530/// This structure is created through [`WasiCtxBuilder`] and is stored within
531/// the `T` of [`Store<T>`][`Store`]. Access to the structure is provided
532/// through the [`WasiView`](crate::WasiView) trait as an implementation on `T`.
533///
534/// Note that this structure itself does not have any accessors, it's here for
535/// internal use within the `wasmtime-wasi` crate's implementation of
536/// bindgen-generated traits.
537///
538/// [`Store`]: wasmtime::Store
539///
540/// # Example
541///
542/// ```
543/// use wasmtime_wasi::{WasiCtx, ResourceTable, WasiView, IoView, WasiCtxBuilder};
544///
545/// struct MyState {
546/// ctx: WasiCtx,
547/// table: ResourceTable,
548/// }
549///
550/// impl IoView for MyState {
551/// fn table(&mut self) -> &mut ResourceTable { &mut self.table }
552/// }
553/// impl WasiView for MyState {
554/// fn ctx(&mut self) -> &mut WasiCtx { &mut self.ctx }
555/// }
556///
557/// impl MyState {
558/// fn new() -> MyState {
559/// let mut wasi = WasiCtxBuilder::new();
560/// wasi.arg("./foo.wasm");
561/// wasi.arg("--help");
562/// wasi.env("FOO", "bar");
563///
564/// MyState {
565/// ctx: wasi.build(),
566/// table: ResourceTable::new(),
567/// }
568/// }
569/// }
570/// ```
571pub struct WasiCtx {
572 pub(crate) random: Box<dyn RngCore + Send>,
573 pub(crate) insecure_random: Box<dyn RngCore + Send>,
574 pub(crate) insecure_random_seed: u128,
575 pub(crate) wall_clock: Box<dyn HostWallClock + Send>,
576 pub(crate) monotonic_clock: Box<dyn HostMonotonicClock + Send>,
577 pub(crate) env: Vec<(String, String)>,
578 pub(crate) args: Vec<String>,
579 pub(crate) preopens: Vec<(Dir, String)>,
580 pub(crate) stdin: Box<dyn StdinStream>,
581 pub(crate) stdout: Box<dyn StdoutStream>,
582 pub(crate) stderr: Box<dyn StdoutStream>,
583 pub(crate) socket_addr_check: SocketAddrCheck,
584 pub(crate) allowed_network_uses: AllowedNetworkUses,
585 pub(crate) allow_blocking_current_thread: bool,
586}
587
588impl WasiCtx {
589 /// Convenience function for calling [`WasiCtxBuilder::new`].
590 pub fn builder() -> WasiCtxBuilder {
591 WasiCtxBuilder::new()
592 }
593}
594
595pub struct AllowedNetworkUses {
596 pub ip_name_lookup: bool,
597 pub udp: bool,
598 pub tcp: bool,
599}
600
601impl Default for AllowedNetworkUses {
602 fn default() -> Self {
603 Self {
604 ip_name_lookup: false,
605 udp: true,
606 tcp: true,
607 }
608 }
609}
610
611impl AllowedNetworkUses {
612 pub(crate) fn check_allowed_udp(&self) -> std::io::Result<()> {
613 if !self.udp {
614 return Err(std::io::Error::new(
615 std::io::ErrorKind::PermissionDenied,
616 "UDP is not allowed",
617 ));
618 }
619
620 Ok(())
621 }
622
623 pub(crate) fn check_allowed_tcp(&self) -> std::io::Result<()> {
624 if !self.tcp {
625 return Err(std::io::Error::new(
626 std::io::ErrorKind::PermissionDenied,
627 "TCP is not allowed",
628 ));
629 }
630
631 Ok(())
632 }
633}