use std::env;
use std::fs;
use std::fs::File;
use std::io::Read as _;
use std::io::Seek as _;
use std::io::SeekFrom;
use std::os::unix::fs::PermissionsExt as _;
use std::os::unix::process::CommandExt as _;
use std::path::Path;
use std::path::PathBuf;
use std::process;
use std::process::Command;
use anyhow::Context as _;
use anyhow::Result;
use anyhow::ensure;
use crate::CleanupGuard;
const MAGIC: &[u8; 8] = b"VMSHKRNL";
const TRAILER_SIZE: u64 = 16;
const CLEANUP_ENV: &str = "VMSH_EMBED_CLEANUP";
pub(crate) fn embed_kernel(kernel: &Path, output: Option<&Path>) -> Result<()> {
let self_path = env::current_exe().context("failed to locate own executable")?;
if output.is_none() {
let tmp = env::temp_dir().join(format!("vmsh-embed-{}", process::id()));
let () = fs::copy(&self_path, &tmp)
.map(|_| ())
.with_context(|| format!("failed to copy self to `{}`", tmp.display()))?;
let () = fs::set_permissions(&tmp, fs::Permissions::from_mode(0o755))
.with_context(|| format!("failed to set permissions on `{}`", tmp.display()))?;
let err = Command::new(&tmp)
.arg("embed")
.arg(kernel)
.arg("-o")
.arg(&self_path)
.env(CLEANUP_ENV, &tmp)
.exec();
return Err(err).with_context(|| format!("failed to re-exec via `{}`", tmp.display()));
}
let mut binary = fs::read(&self_path).context("failed to read own executable")?;
let () = strip_trailer(&mut binary);
let kernel_data =
fs::read(kernel).with_context(|| format!("failed to read kernel at `{}`", kernel.display()))?;
ensure!(!kernel_data.is_empty(), "kernel file is empty");
let kernel_size = kernel_data.len() as u64;
let () = binary.extend_from_slice(&kernel_data);
let () = binary.extend_from_slice(&kernel_size.to_le_bytes());
let () = binary.extend_from_slice(MAGIC);
let out_path = output.unwrap();
let () = fs::write(out_path, &binary)
.with_context(|| format!("failed to write output to `{}`", out_path.display()))?;
let () = fs::set_permissions(out_path, fs::Permissions::from_mode(0o755))
.with_context(|| format!("failed to set permissions on `{}`", out_path.display()))?;
let kernel_kib = kernel_size / 1024;
eprintln!(
"embedded {kernel_kib} KiB kernel from `{}` into `{}`",
kernel.display(),
out_path.display(),
);
if let Some(temp) = env::var_os(CLEANUP_ENV) {
let () = fs::remove_file(&temp)
.with_context(|| format!("failed to clean up temporary file `{}`", temp.display()))?;
}
Ok(())
}
fn strip_trailer(binary: &mut Vec<u8>) {
if binary.len() < TRAILER_SIZE as usize {
return;
}
let len = binary.len();
let magic_start = len - MAGIC.len();
if &binary[magic_start..] != MAGIC {
return;
}
let size_start = magic_start - 8;
let mut size_bytes = [0u8; 8];
let () = size_bytes.copy_from_slice(&binary[size_start..magic_start]);
let kernel_size = u64::from_le_bytes(size_bytes) as usize;
let payload_end = size_start;
if kernel_size <= payload_end {
let () = binary.truncate(payload_end - kernel_size);
}
}
pub(crate) fn extract_embedded_kernel() -> Result<CleanupGuard> {
let mut file = File::open("/proc/self/exe").context("failed to open /proc/self/exe")?;
let file_len = file
.seek(SeekFrom::End(0))
.context("failed to seek to end of /proc/self/exe")?;
ensure!(
file_len >= TRAILER_SIZE,
"binary too small to contain an embedded kernel"
);
let () = file
.seek(SeekFrom::End(-(TRAILER_SIZE as i64)))
.map(|_| ())
.context("failed to seek to trailer")?;
let mut trailer = [0u8; TRAILER_SIZE as usize];
let () = file
.read_exact(&mut trailer)
.context("failed to read trailer")?;
let magic = &trailer[8..];
ensure!(
magic == MAGIC,
"no embedded kernel found (missing VMSHKRNL trailer)"
);
let mut size_bytes = [0u8; 8];
let () = size_bytes.copy_from_slice(&trailer[..8]);
let kernel_size = u64::from_le_bytes(size_bytes);
let kernel_start = file_len - TRAILER_SIZE - kernel_size;
let () = file
.seek(SeekFrom::Start(kernel_start))
.map(|_| ())
.context("failed to seek to kernel data")?;
let mut kernel_data = vec![0u8; kernel_size as usize];
let () = file
.read_exact(&mut kernel_data)
.context("failed to read embedded kernel")?;
let path = PathBuf::from(format!("/tmp/vmsh-kernel-{}", process::id(),));
let () = fs::write(&path, &kernel_data)
.with_context(|| format!("failed to write embedded kernel to `{}`", path.display()))?;
Ok(CleanupGuard(Some(path)))
}
#[cfg(test)]
mod tests {
use super::*;
fn blob_with_trailer(base: &[u8], kernel: &[u8]) -> Vec<u8> {
let mut buf = Vec::from(base);
let size = kernel.len() as u64;
let () = buf.extend_from_slice(kernel);
let () = buf.extend_from_slice(&size.to_le_bytes());
let () = buf.extend_from_slice(MAGIC);
buf
}
#[test]
fn strip_no_trailer() {
let original = vec![1, 2, 3, 4, 5];
let mut data = original.clone();
let () = strip_trailer(&mut data);
assert_eq!(data, original);
}
#[test]
fn strip_with_trailer() {
let base = vec![0xAA; 32];
let kernel = vec![0xBB; 64];
let mut data = blob_with_trailer(&base, &kernel);
assert_eq!(data.len(), 32 + 64 + 16);
let () = strip_trailer(&mut data);
assert_eq!(data, base);
}
#[test]
fn strip_too_small() {
let mut data = vec![1, 2, 3];
let original = data.clone();
let () = strip_trailer(&mut data);
assert_eq!(data, original);
}
#[test]
fn strip_idempotent() {
let base = vec![0xCC; 16];
let kernel = vec![0xDD; 8];
let mut data = blob_with_trailer(&base, &kernel);
let () = strip_trailer(&mut data);
let after_first = data.clone();
let () = strip_trailer(&mut data);
assert_eq!(data, after_first);
}
#[test]
fn strip_corrupt_size() {
let mut data = vec![0xEE; 4];
let () = data.extend_from_slice(&[0xFF; 2]);
let () = data.extend_from_slice(&9999u64.to_le_bytes());
let () = data.extend_from_slice(MAGIC);
let original = data.clone();
let () = strip_trailer(&mut data);
assert_eq!(data, original);
}
}