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            }
96            _ => None,
97        };
98        let wasm_registration = self.binfmt_misc.join("wasm32");
99        let wat_registration = self.binfmt_misc.join("wasm32-wat");
100        match self.action {
101            Reregister | Unregister => {
102                let unregister = [wasm_registration, wat_registration]
103                    .iter()
104                    .map(|registration| {
105                        if registration.exists() {
106                            let mut registration = fs::OpenOptions::new()
107                                .write(true)
108                                .open(registration)
109                                .context("Open existing binfmt entry to remove")?;
110                            registration
111                                .write_all(b"-1")
112                                .context("Couldn't write binfmt unregister request")?;
113                            Ok(true)
114                        } else {
115                            eprintln!(
116                                "Warning: {} does not exist, not unregistered.",
117                                registration.to_string_lossy()
118                            );
119                            Ok(false)
120                        }
121                    })
122                    .collect::<Vec<_>>()
123                    .into_iter()
124                    .collect::<Result<Vec<_>>>()?;
125                if let (Unregister, false) = (self.action, unregister.into_iter().any(|b| b)) {
126                    bail!("Nothing unregistered");
127                }
128            }
129            _ => (),
130        };
131        if let Some(specs) = specs {
132            if cfg!(target_env = "gnu") {
133                // Approximate. ELF parsing for a proper check feels like overkill here.
134                eprintln!(
135                    "Warning: wasmer has been compiled for glibc, and is thus likely dynamically linked. Invoking wasm binaries in chroots or mount namespaces (lxc, docker, ...) may not work."
136                );
137            }
138            specs
139                .iter()
140                .map(|spec| {
141                    let register = self.binfmt_misc.join("register");
142                    let mut register = fs::OpenOptions::new()
143                        .write(true)
144                        .open(register)
145                        .context("Open binfmt misc for registration")?;
146                    register
147                        .write_all(spec)
148                        .context("Couldn't register binfmt")?;
149                    Ok(())
150                })
151                .collect::<Vec<_>>()
152                .into_iter()
153                .collect::<Result<Vec<_>>>()?;
154        }
155        Ok(())
156    }
157}