wasmer_cli/commands/
binfmt.rs

1use std::{
2    env,
3    fs::{self, Permissions},
4    io::Write,
5    os::unix::{
6        ffi::OsStrExt,
7        fs::{MetadataExt, PermissionsExt},
8    },
9    path::{Path, PathBuf},
10};
11
12use Action::*;
13use anyhow::{Context, Result, bail};
14use clap::Parser;
15
16#[derive(Debug, Parser, Clone, Copy)]
17enum Action {
18    /// Register wasmer as binfmt interpreter
19    Register,
20    /// Unregister a binfmt interpreter for wasm32
21    Unregister,
22    /// Soft unregister, and register
23    Reregister,
24}
25
26/// Unregister and/or register wasmer as binfmt interpreter
27///
28/// Check the wasmer repository for a systemd service definition example
29/// to automate the process at start-up.
30#[derive(Debug, Parser)]
31pub struct Binfmt {
32    // Might be better to traverse the mount list
33    /// Mount point of binfmt_misc fs
34    #[clap(long, default_value = "/proc/sys/fs/binfmt_misc/")]
35    binfmt_misc: PathBuf,
36
37    #[clap(subcommand)]
38    action: Action,
39}
40
41// Quick safety check:
42// This folder isn't world writeable (or else its sticky bit is set), and neither are its parents.
43//
44// If somebody mounted /tmp wrong, this might result in a TOCTOU problem.
45fn seccheck(path: &Path) -> Result<()> {
46    if let Some(parent) = path.parent() {
47        seccheck(parent)?;
48    }
49    let m = std::fs::metadata(path)
50        .with_context(|| format!("Can't check permissions of {}", path.to_string_lossy()))?;
51    use unix_mode::*;
52    anyhow::ensure!(
53        !is_allowed(Accessor::Other, Access::Write, m.mode()) || is_sticky(m.mode()),
54        "{} is world writeable and not sticky ({m:?})",
55        path.to_string_lossy()
56    );
57    Ok(())
58}
59
60impl Binfmt {
61    /// The filename used to register the wasmer CLI as a binfmt interpreter.
62    pub const FILENAME: &'static str = "wasmer-binfmt-interpreter";
63
64    /// execute [Binfmt]
65    pub fn execute(&self) -> Result<()> {
66        if !self.binfmt_misc.exists() {
67            panic!("{} does not exist", self.binfmt_misc.to_string_lossy());
68        }
69        let temp_dir;
70        let specs = match self.action {
71            Register | Reregister => {
72                temp_dir = tempfile::Builder::new()
73                    .permissions(Permissions::from_mode(0o1755))
74                    .tempdir()
75                    .context("Make temporary directory")?;
76                seccheck(temp_dir.path())?;
77                let bin_path_orig: PathBuf = env::current_exe()
78                    .and_then(|p| p.canonicalize())
79                    .context("Cannot get path to wasmer executable")?;
80                let bin_path = temp_dir.path().join(Binfmt::FILENAME);
81                fs::copy(bin_path_orig, &bin_path).context("Copy wasmer binary to temp folder")?;
82                let bin_path = fs::canonicalize(&bin_path).with_context(|| {
83                    format!(
84                        "Couldn't get absolute path for {}",
85                        bin_path.to_string_lossy()
86                    )
87                })?;
88                // The binfmt flags are documented here:
89                // https://docs.kernel.org/admin-guide/binfmt-misc.html
90                // We use the 'F' flag to guarantee the binary exists at registration time,
91                // not necessarily at execution time (hence the temporary folder).
92                Some([
93                    [
94                        b":wasm32:M::\\x00asm\\x01\\x00\\x00::".as_ref(),
95                        bin_path.as_os_str().as_bytes(),
96                        b":PFC",
97                    ]
98                    .concat(),
99                    [
100                        b":wasm32-wat:E::wat::".as_ref(),
101                        bin_path.as_os_str().as_bytes(),
102                        b":PFC",
103                    ]
104                    .concat(),
105                    [
106                        b":wasm32-webc:M::\\x00webc::".as_ref(),
107                        bin_path.as_os_str().as_bytes(),
108                        b":PFC",
109                    ]
110                    .concat(),
111                ])
112            }
113            _ => None,
114        };
115        let wasm_registration = self.binfmt_misc.join("wasm32");
116        let wat_registration = self.binfmt_misc.join("wasm32-wat");
117        let webc_registration = self.binfmt_misc.join("wasm32-webc");
118        match self.action {
119            Register | Reregister | Unregister => {
120                let unregister = [wasm_registration, wat_registration, webc_registration]
121                    .iter()
122                    .map(|registration| {
123                        if registration.exists() {
124                            let mut registration = fs::OpenOptions::new()
125                                .write(true)
126                                .open(registration)
127                                .context("Open existing binfmt entry to remove")?;
128                            registration
129                                .write_all(b"-1")
130                                .context("Couldn't write binfmt unregister request")?;
131                            Ok(true)
132                        } else {
133                            Ok(false)
134                        }
135                    })
136                    .collect::<Vec<_>>()
137                    .into_iter()
138                    .collect::<Result<Vec<_>>>()?;
139                if let (Unregister, false) = (self.action, unregister.into_iter().any(|b| b)) {
140                    bail!("Nothing unregistered");
141                }
142            }
143        };
144        if let Some(specs) = specs {
145            specs
146                .iter()
147                .map(|spec| {
148                    let register = self.binfmt_misc.join("register");
149                    let mut register = fs::OpenOptions::new()
150                        .write(true)
151                        .open(register)
152                        .context("Open binfmt misc for registration")?;
153                    register
154                        .write_all(spec)
155                        .context("Couldn't register binfmt")?;
156                    Ok(())
157                })
158                .collect::<Vec<_>>()
159                .into_iter()
160                .collect::<Result<Vec<_>>>()?;
161        }
162        Ok(())
163    }
164}