1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
use anyhow::{Context, Result};
use std::env;
use std::fs;
use std::io::Write;
use std::os::unix::ffi::OsStrExt;
use std::os::unix::fs::MetadataExt;
use std::path::{Path, PathBuf};
use structopt::StructOpt;
use Action::*;

#[derive(StructOpt, Clone, Copy)]
enum Action {
    /// Register wasmer as binfmt interpreter
    Register,
    /// Unregister a binfmt interpreter for wasm32
    Unregister,
    /// Soft unregister, and register
    Reregister,
}

/// Unregister and/or register wasmer as binfmt interpreter
///
/// Check the wasmer repository for a systemd service definition example
/// to automate the process at start-up.
#[derive(StructOpt)]
pub struct Binfmt {
    // Might be better to traverse the mount list
    /// Mount point of binfmt_misc fs
    #[structopt(long, default_value = "/proc/sys/fs/binfmt_misc/")]
    binfmt_misc: PathBuf,

    #[structopt(subcommand)]
    action: Action,
}

// Quick safety check:
// This folder isn't world writeable (or else its sticky bit is set), and neither are its parents.
//
// If somebody mounted /tmp wrong, this might result in a TOCTOU problem.
fn seccheck(path: &Path) -> Result<()> {
    if let Some(parent) = path.parent() {
        seccheck(parent)?;
    }
    let m = std::fs::metadata(path)
        .with_context(|| format!("Can't check permissions of {}", path.to_string_lossy()))?;
    anyhow::ensure!(
        m.mode() & 0o2 == 0 || m.mode() & 0o1000 != 0,
        "{} is world writeable and not sticky",
        path.to_string_lossy()
    );
    Ok(())
}

impl Binfmt {
    /// execute [Binfmt]
    pub fn execute(&self) -> Result<()> {
        if !self.binfmt_misc.exists() {
            panic!("{} does not exist", self.binfmt_misc.to_string_lossy());
        }
        let temp_dir;
        let specs = match self.action {
            Register | Reregister => {
                temp_dir = tempfile::tempdir().context("Make temporary directory")?;
                seccheck(temp_dir.path())?;
                let bin_path_orig: PathBuf = env::args_os()
                    .nth(0)
                    .map(Into::into)
                    .filter(|p: &PathBuf| p.exists())
                    .context("Cannot get path to wasmer executable")?;
                let bin_path = temp_dir.path().join("wasmer-binfmt-interpreter");
                fs::copy(&bin_path_orig, &bin_path).context("Copy wasmer binary to temp folder")?;
                let bin_path = fs::canonicalize(&bin_path).with_context(|| {
                    format!(
                        "Couldn't get absolute path for {}",
                        bin_path.to_string_lossy()
                    )
                })?;
                Some([
                    [
                        b":wasm32:M::\\x00asm\\x01\\x00\\x00::".as_ref(),
                        bin_path.as_os_str().as_bytes(),
                        b":PFC",
                    ]
                    .concat(),
                    [
                        b":wasm32-wat:E::wat::".as_ref(),
                        bin_path.as_os_str().as_bytes(),
                        b":PFC",
                    ]
                    .concat(),
                ])
            }
            _ => None,
        };
        let wasm_registration = self.binfmt_misc.join("wasm32");
        let wat_registration = self.binfmt_misc.join("wasm32-wat");
        match self.action {
            Reregister | Unregister => {
                let unregister = [wasm_registration, wat_registration]
                    .iter()
                    .map(|registration| {
                        if registration.exists() {
                            let mut registration = fs::OpenOptions::new()
                                .write(true)
                                .open(registration)
                                .context("Open existing binfmt entry to remove")?;
                            registration
                                .write_all(b"-1")
                                .context("Couldn't write binfmt unregister request")?;
                            Ok(true)
                        } else {
                            eprintln!(
                                "Warning: {} does not exist, not unregistered.",
                                registration.to_string_lossy()
                            );
                            Ok(false)
                        }
                    })
                    .collect::<Vec<_>>()
                    .into_iter()
                    .collect::<Result<Vec<_>>>()?;
                match (self.action, unregister.into_iter().any(|b| b)) {
                    (Unregister, false) => bail!("Nothing unregistered"),
                    _ => (),
                }
            }
            _ => (),
        };
        if let Some(specs) = specs {
            if cfg!(target_env = "gnu") {
                // Approximate. ELF parsing for a proper check feels like overkill here.
                eprintln!("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.");
            }
            specs
                .iter()
                .map(|spec| {
                    let register = self.binfmt_misc.join("register");
                    let mut register = fs::OpenOptions::new()
                        .write(true)
                        .open(register)
                        .context("Open binfmt misc for registration")?;
                    register
                        .write_all(&spec)
                        .context("Couldn't register binfmt")?;
                    Ok(())
                })
                .collect::<Vec<_>>()
                .into_iter()
                .collect::<Result<Vec<_>>>()?;
        }
        Ok(())
    }
}