Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Register binfmt #2499

Merged
merged 11 commits into from
Nov 5, 2021
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ Looking for changes that affect our C API? See the [C API Changelog](lib/c-api/C
- [#2535](https://github.com/wasmerio/wasmer/pull/2435) Added iOS support for Wasmer. This relies on the `dylib-engine`.
- [#2427](https://github.com/wasmerio/wasmer/pull/2427) Wasmer can now compile to Javascript via `wasm-bindgen`. Use the `js-default` (and no default features) feature to try it!.
- [#2436](https://github.com/wasmerio/wasmer/pull/2436) Added the x86-32 bit variant support to LLVM compiler.
- [#2499](https://github.com/wasmerio/wasmer/pull/2499) Added a subcommand to linux wasmer-cli to register wasmer with binfmt_misc


### Changed
- [#2460](https://github.com/wasmerio/wasmer/pull/2460) **breaking change** `wasmer` API usage with `no-default-features` requires now the `sys` feature to preserve old behavior.
Expand Down
6 changes: 6 additions & 0 deletions PACKAGING.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,9 @@
* `libwasmer-static`, containing `libwasmer.a`.

The Wasmer distro packaging story is still in its infancy, so feedback is very welcome.

## Miscellaneous: binfmt_misc

Wasmer can be registered as a binfmt interpreter for wasm binaries.
An example systemd [.service](./scripts/wasmer-binfmt.service.example) is included here.
Please consider statically linking the wasmer binary so that this capability is also available in mount namespaces.
43 changes: 30 additions & 13 deletions lib/cli/src/cli.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
//! The logic for the Wasmer CLI tool.

#[cfg(target_os = "linux")]
use crate::commands::Binfmt;
#[cfg(feature = "compiler")]
use crate::commands::Compile;
#[cfg(all(feature = "staticlib", feature = "compiler"))]
Expand Down Expand Up @@ -66,6 +68,11 @@ enum WasmerCLIOptions {
#[cfg(feature = "wast")]
#[structopt(name = "wast")]
Wast(Wast),

/// Unregister and/or register wasmer as binfmt interpreter
#[cfg(target_os = "linux")]
#[structopt(name = "binfmt")]
Binfmt(Binfmt),
}

impl WasmerCLIOptions {
Expand All @@ -83,6 +90,8 @@ impl WasmerCLIOptions {
Self::Inspect(inspect) => inspect.execute(),
#[cfg(feature = "wast")]
Self::Wast(wast) => wast.execute(),
#[cfg(target_os = "linux")]
Self::Binfmt(binfmt) => binfmt.execute(),
}
}
}
Expand All @@ -97,21 +106,29 @@ pub fn wasmer_main() {
// Eg. `wasmer <SUBCOMMAND>`
// In case that fails, we fallback trying the Run subcommand directly.
// Eg. `wasmer myfile.wasm --dir=.`
//
// In case we've been run as wasmer-binfmt-interpreter myfile.wasm args,
// we assume that we're registered via binfmt_misc
let args = std::env::args().collect::<Vec<_>>();
let binpath = args.get(0).map(|s| s.as_ref()).unwrap_or("");
let command = args.get(1);
let options = match command.unwrap_or(&"".to_string()).as_ref() {
"cache" | "compile" | "config" | "create-exe" | "help" | "inspect" | "run"
| "self-update" | "validate" | "wast" => WasmerCLIOptions::from_args(),
_ => {
WasmerCLIOptions::from_iter_safe(args.iter()).unwrap_or_else(|e| {
match e.kind {
// This fixes a issue that:
// 1. Shows the version twice when doing `wasmer -V`
// 2. Shows the run help (instead of normal help) when doing `wasmer --help`
ErrorKind::VersionDisplayed | ErrorKind::HelpDisplayed => e.exit(),
_ => WasmerCLIOptions::Run(Run::from_args()),
}
})
let options = if cfg!(target_os = "linux") && binpath.ends_with("wasmer-binfmt-interpreter") {
WasmerCLIOptions::Run(Run::from_binfmt_args())
} else {
match command.unwrap_or(&"".to_string()).as_ref() {
"cache" | "compile" | "config" | "create-exe" | "help" | "inspect" | "run"
| "self-update" | "validate" | "wast" | "binfmt" => WasmerCLIOptions::from_args(),
_ => {
WasmerCLIOptions::from_iter_safe(args.iter()).unwrap_or_else(|e| {
match e.kind {
// This fixes a issue that:
// 1. Shows the version twice when doing `wasmer -V`
// 2. Shows the run help (instead of normal help) when doing `wasmer --help`
ErrorKind::VersionDisplayed | ErrorKind::HelpDisplayed => e.exit(),
_ => WasmerCLIOptions::Run(Run::from_args()),
}
})
}
}
};

Expand Down
4 changes: 4 additions & 0 deletions lib/cli/src/commands.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
//! The commands available in the Wasmer binary.
#[cfg(target_os = "linux")]
mod binfmt;
mod cache;
#[cfg(feature = "compiler")]
mod compile;
Expand All @@ -12,6 +14,8 @@ mod validate;
#[cfg(feature = "wast")]
mod wast;

#[cfg(target_os = "linux")]
pub use binfmt::*;
#[cfg(feature = "compiler")]
pub use compile::*;
#[cfg(all(feature = "staticlib", feature = "compiler"))]
Expand Down
153 changes: 153 additions & 0 deletions lib/cli/src/commands/binfmt.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,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")?;
ptitSeb marked this conversation as resolved.
Show resolved Hide resolved
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(())
}
}
57 changes: 56 additions & 1 deletion lib/cli/src/commands/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ mod wasi;
#[cfg(feature = "wasi")]
use wasi::Wasi;

#[derive(Debug, StructOpt, Clone)]
#[derive(Debug, StructOpt, Clone, Default)]
/// The options for the `wasmer run` subcommand
pub struct Run {
/// Disable the cache
Expand Down Expand Up @@ -421,4 +421,59 @@ impl Run {
.collect::<Result<Vec<_>>>()?;
Ok(func.call(&invoke_args)?)
}

/// Create Run instance for arguments/env,
/// assuming we're being run from a CFP binfmt interpreter.
pub fn from_binfmt_args() -> Run {
Self::from_binfmt_args_fallible().unwrap_or_else(|e| {
crate::error::PrettyError::report::<()>(
Err(e).context("Failed to set up wasmer binfmt invocation"),
)
})
}

#[cfg(target_os = "linux")]
fn from_binfmt_args_fallible() -> Result<Run> {
let argv = std::env::args_os().collect::<Vec<_>>();
let (_interpreter, executable, original_executable, args) = match &argv[..] {
[a, b, c, d @ ..] => (a, b, c, d),
_ => {
bail!("Wasmer binfmt interpreter needs at least three arguments (including $0) - must be registered as binfmt interpreter with the CFP flags. (Got arguments: {:?})", argv);
}
};
// TODO: Optimally, args and env would be passed as an UTF-8 Vec.
// (Can be pulled out of std::os::unix::ffi::OsStrExt)
// But I don't want to duplicate or rewrite run.rs today.
let args = args
.iter()
.enumerate()
.map(|(i, s)| {
s.clone().into_string().map_err(|s| {
anyhow!(
"Cannot convert argument {} ({:?}) to UTF-8 string",
i + 1,
s
)
})
})
.collect::<Result<Vec<_>>>()?;
let original_executable = original_executable
.clone()
.into_string()
.map_err(|s| anyhow!("Cannot convert executable name {:?} to UTF-8 string", s))?;
let store = StoreOptions::default();
// TODO: store.compiler.features.all = true; ?
Ok(Self {
args,
path: executable.into(),
command_name: Some(original_executable),
store: store,
wasi: Wasi::for_binfmt_interpreter()?,
..Self::default()
})
}
#[cfg(not(target_os = "linux"))]
fn from_binfmt_args_fallible() -> Result<Run> {
bail!("binfmt_misc is only available on linux.")
}
}
15 changes: 14 additions & 1 deletion lib/cli/src/commands/run/wasi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use wasmer_wasi::{get_wasi_versions, WasiError, WasiState, WasiVersion};

use structopt::StructOpt;

#[derive(Debug, StructOpt, Clone)]
#[derive(Debug, StructOpt, Clone, Default)]
/// WASI Options
pub struct Wasi {
/// WASI pre-opened directory
Expand Down Expand Up @@ -111,4 +111,17 @@ impl Wasi {
}
.with_context(|| "failed to run WASI `_start` function")
}

pub fn for_binfmt_interpreter() -> Result<Self> {
use std::env;
let dir = env::var_os("WASMER_BINFMT_MISC_PREOPEN")
.map(Into::into)
.unwrap_or(PathBuf::from("."));
Ok(Self {
deny_multiple_wasi_versions: true,
env_vars: env::vars().collect(),
pre_opened_directories: vec![dir],
..Self::default()
})
}
}
2 changes: 1 addition & 1 deletion lib/cli/src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use std::env;
use std::path::PathBuf;
use structopt::StructOpt;

#[derive(Debug, StructOpt, Clone)]
#[derive(Debug, StructOpt, Clone, Default)]
/// The WebAssembly features that can be passed through the
/// Command Line args.
pub struct WasmFeatures {
Expand Down
4 changes: 2 additions & 2 deletions lib/cli/src/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use wasmer::*;
#[cfg(feature = "compiler")]
use wasmer_compiler::CompilerConfig;

#[derive(Debug, Clone, StructOpt)]
#[derive(Debug, Clone, StructOpt, Default)]
/// The compiler and engine options
pub struct StoreOptions {
#[structopt(flatten)]
Expand Down Expand Up @@ -43,7 +43,7 @@ pub struct StoreOptions {
object_file: bool,
}

#[derive(Debug, Clone, StructOpt)]
#[derive(Debug, Clone, StructOpt, Default)]
/// The compiler options
pub struct CompilerOptions {
/// Use Singlepass compiler.
Expand Down
15 changes: 15 additions & 0 deletions scripts/wasmer-binfmt.service.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[Unit]
Description=Set up wasmer to handle execution of wasm binaries
DefaultDependencies=no
Conflicts=shutdown.target
After=proc-sys-fs-binfmt_misc.automount
After=proc-sys-fs-binfmt_misc.mount
Before=sysinit.target shutdown.target
ConditionPathIsReadWrite=/proc/sys/

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/bin/wasmer binfmt reregister
ExecStop=/usr/bin/wasmer binfmt unregister
TimeoutSec=10s
Empty file modified tests/examples/memory.wat
100644 → 100755
Empty file.