wasmer_cli/commands/
binfmt.rs

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