Skip to content

Commit

Permalink
Added selective environment passing for VM
Browse files Browse the repository at this point in the history
  • Loading branch information
jounathaen committed Feb 3, 2025
1 parent 6d936b1 commit 795cb35
Show file tree
Hide file tree
Showing 8 changed files with 191 additions and 21 deletions.
9 changes: 8 additions & 1 deletion src/bin/uhyve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use core_affinity::CoreId;
use either::Either;
use thiserror::Error;
use uhyvelib::{
params::{CpuCount, GuestMemorySize, Output, Params},
params::{CpuCount, EnvVars, GuestMemorySize, Output, Params},
vm::UhyveVm,
};

Expand Down Expand Up @@ -73,6 +73,11 @@ struct Args {
#[cfg(target_os = "linux")]
gdb_port: Option<u16>,

/// Environment variables of the guest as env=value paths. `-e host` passes all variables of the parent process to the kernel.
/// Example: --env_vars ASDF=jlk -e TERM=uhyveterm2000
#[clap(short = 'e', long)]
env_vars: Vec<String>,

#[clap(flatten, next_help_heading = "Memory OPTIONS")]
memory_args: MemoryArgs,

Expand Down Expand Up @@ -272,6 +277,7 @@ impl From<Args> for Params {
kernel_args,
output,
stats,
env_vars,
} = args;
Self {
memory_size,
Expand All @@ -296,6 +302,7 @@ impl From<Args> for Params {
Output::StdIo
},
stats,
env: EnvVars::from(env_vars.as_slice()),
}
}
}
Expand Down
52 changes: 37 additions & 15 deletions src/hypercall.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use crate::{
consts::BOOT_PML4,
isolation::filemap::UhyveFileMap,
mem::{MemoryError, MmapMemory},
params::EnvVars,
virt_to_phys,
vm::VmPeripherals,
};
Expand Down Expand Up @@ -259,28 +260,49 @@ pub fn copy_argv(path: &OsStr, argv: &[String], syscmdval: &CmdvalParams, mem: &
}

/// Copies the environment variables into the VM's memory to the destinations specified in `syscmdval`.
pub fn copy_env(syscmdval: &CmdvalParams, mem: &MmapMemory) {
let env_len = std::env::vars_os().count();
pub fn copy_env(env: &EnvVars, syscmdval: &CmdvalParams, mem: &MmapMemory) {
let envp = mem
.host_address(syscmdval.envp)
.expect("Systemcall parameters for Cmdval are invalid") as *const GuestPhysAddr;
let env_addrs = unsafe { std::slice::from_raw_parts(envp, env_len) };

// Copy the environment variables into the vm memory
for (counter, (key, value)) in std::env::vars_os().enumerate() {
if counter >= MAX_ARGC_ENVC.try_into().unwrap() {
warn!("Environment is larger than the maximum that can be copied to the VM. Remaining environment is ignored");
break;
}
let env_len = match env {
EnvVars::Host => std::env::vars_os().count(),
EnvVars::Set(map) => map.len(),
};
if env_len >= MAX_ARGC_ENVC.try_into().unwrap() {
warn!("Environment is larger than the maximum that can be copied to the VM. Remaining environment is ignored");
}
let env_addrs = unsafe { std::slice::from_raw_parts(envp, env_len) };

fn write_env_into_mem(env_dest: &mut [u8], key: &[u8], value: &[u8]) {
let len = key.len() + value.len() + 1;
let env_dest = unsafe {
mem.slice_at_mut(env_addrs[counter], len + 1)
.expect("Systemcall parameters for Cmdval are invalid")
};
env_dest[0..key.len()].copy_from_slice(key.as_bytes());
env_dest[0..key.len()].copy_from_slice(key);
env_dest[key.len()] = b'=';
env_dest[key.len() + 1..len].copy_from_slice(value.as_bytes());
env_dest[key.len() + 1..len].copy_from_slice(value);
env_dest[len] = 0;
}

// Copy the environment variables into the vm memory
match env {
EnvVars::Host => {
for (counter, (key, value)) in std::env::vars_os().enumerate().take(MAX_ARGC_ENVC) {
let len = key.len() + value.len() + 1;
let env_dest = unsafe {
mem.slice_at_mut(env_addrs[counter], len + 1)
.expect("Systemcall parameters for Cmdval are invalid")
};
write_env_into_mem(env_dest, key.as_bytes(), value.as_bytes());
}
}
EnvVars::Set(map) => {
for (counter, (key, value)) in map.iter().enumerate().take(MAX_ARGC_ENVC) {
let len = key.len() + value.len() + 1;
let env_dest = unsafe {
mem.slice_at_mut(env_addrs[counter], len + 1)
.expect("Systemcall parameters for Cmdval are invalid")
};
write_env_into_mem(env_dest, key.as_bytes(), value.as_bytes());
}
}
};
}
6 changes: 5 additions & 1 deletion src/linux/x86_64/kvm_cpu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -452,7 +452,11 @@ impl VirtualCPU for KvmCpu {
syscmdval,
&self.peripherals.mem,
);
hypercall::copy_env(syscmdval, &self.peripherals.mem);
hypercall::copy_env(
&self.kernel_info.params.env,
syscmdval,
&self.peripherals.mem,
);
}
Hypercall::Exit(sysexit) => {
return Ok(VcpuStopReason::Exit(sysexit.arg));
Expand Down
6 changes: 5 additions & 1 deletion src/macos/aarch64/vcpu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,11 @@ impl VirtualCPU for XhyveCpu {
syscmdval,
&self.peripherals.mem,
);
copy_env(syscmdval, &self.peripherals.mem);
copy_env(
&self.kernel_info.params.env,
syscmdval,
&self.peripherals.mem,
);
}
Hypercall::FileClose(sysclose) => hypercall::close(sysclose),
Hypercall::FileLseek(syslseek) => hypercall::lseek(syslseek),
Expand Down
41 changes: 41 additions & 0 deletions src/params.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::{
collections::HashMap,
convert::Infallible,
fmt,
num::{NonZeroU32, ParseIntError, TryFromIntError},
Expand Down Expand Up @@ -46,6 +47,9 @@ pub struct Params {

/// Collect run statistics
pub stats: bool,

/// Environment variables of the kernel
pub env: EnvVars,
}

#[allow(clippy::derivable_impls)]
Expand All @@ -66,6 +70,7 @@ impl Default for Params {
kernel_args: Default::default(),
output: Default::default(),
stats: false,
env: EnvVars::default(),
}
}
}
Expand Down Expand Up @@ -207,3 +212,39 @@ impl FromStr for GuestMemorySize {
Ok(memory_size)
}
}

/// Configure the kernels environment variables.
#[derive(Debug, Clone)]
pub enum EnvVars {
/// Pass all env vars of the host to the kernel.
Host,
/// Pass a certain set of env vars to the kernel.
Set(HashMap<String, String>),
}
impl Default for EnvVars {
fn default() -> Self {
Self::Set(HashMap::new())
}
}
impl<S: AsRef<str> + std::fmt::Debug + PartialEq<S> + for<'a> From<&'a str>> From<&[S]>
for EnvVars
{
fn from(v: &[S]) -> Self {
debug!("v: {v:?}");
if v.contains(&S::from("host")) {
return Self::Host;
}
Self::Set(
v.iter()
.map(|s| {
let sep_pos = s
.as_ref()
.find('=')
.expect("Invalid environment variable format. Must be var=value");
let split = s.as_ref().split_at(sep_pos);
(String::from(split.0), String::from(&split.1[1..]))
})
.collect(),
)
}
}
11 changes: 8 additions & 3 deletions src/vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ use crate::{
isolation::filemap::UhyveFileMap,
mem::MmapMemory,
os::KickSignal,
params::Params,
params::{EnvVars, Params},
serial::{Destination, UhyveSerial},
stats::{CpuStats, VmStats},
vcpu::VirtualCPU,
Expand Down Expand Up @@ -348,8 +348,13 @@ fn write_fdt_into_mem(mem: &MmapMemory, params: &Params, cpu_freq: Option<NonZer
.memory(mem.guest_address..mem.guest_address + mem.memory_size as u64)
.unwrap()
.kernel_args(&params.kernel_args[..sep])
.app_args(params.kernel_args.get(sep + 1..).unwrap_or_default())
.envs(env::vars());
.app_args(params.kernel_args.get(sep + 1..).unwrap_or_default());

fdt = match &params.env {
EnvVars::Host => fdt.envs(env::vars()),
EnvVars::Set(map) => fdt.envs(map.iter().map(|(a, b)| (a.as_str(), b.as_str()))),
};

if let Some(tsc_khz) = cpu_freq {
fdt = fdt.tsc_khz(tsc_khz.into()).unwrap();
}
Expand Down
76 changes: 76 additions & 0 deletions tests/env.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
mod common;

use std::collections::HashMap;

use byte_unit::{Byte, Unit};
use common::{build_hermit_bin, check_result};
use uhyvelib::{
params::{EnvVars, Output, Params},
vm::UhyveVm,
};

#[test]
fn selective_env_test() {
env_logger::try_init().ok();
let bin_path = build_hermit_bin("env");

let env_vars = [
("ASDF".to_string(), "ASDF".to_string()),
("a0978gbsdf".to_string(), ";3254jgnsadfg".to_string()),
("EMOJI".to_string(), "🙂".to_string()),
];

println!("Launching kernel {}", bin_path.display());
let params = Params {
cpu_count: 2.try_into().unwrap(),
memory_size: Byte::from_u64_with_unit(64, Unit::MiB)
.unwrap()
.try_into()
.unwrap(),
output: Output::Buffer,
env: EnvVars::Set(HashMap::from(env_vars.clone())),
..Default::default()
};
let vm = UhyveVm::new(bin_path, params).unwrap();
let res = vm.run(None);
check_result(&res);
println!("{:?}", res.output.as_ref().unwrap());
for (key, value) in env_vars.iter() {
assert!(res
.output
.as_ref()
.unwrap()
.contains(&format!("ENVIRONMENT: {key}: {value}")));
}
}

#[test]
fn host_env_test() {
env_logger::try_init().ok();
let bin_path = build_hermit_bin("env");

println!("Launching kernel {}", bin_path.display());
let params = Params {
cpu_count: 2.try_into().unwrap(),
memory_size: Byte::from_u64_with_unit(64, Unit::MiB)
.unwrap()
.try_into()
.unwrap(),
output: Output::Buffer,
env: EnvVars::Host,
..Default::default()
};
let vm = UhyveVm::new(bin_path, params).unwrap();
let res = vm.run(None);
check_result(&res);
println!("{:?}", res.output.as_ref().unwrap());

let common_env_vars = ["PWD", "CARGO_MANIFEST_DIR"];
for env in common_env_vars.iter() {
assert!(res
.output
.as_ref()
.unwrap()
.contains(&format!("ENVIRONMENT: {env}:")));
}
}
11 changes: 11 additions & 0 deletions tests/test-kernels/src/bin/env.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
use std::env;

#[cfg(target_os = "hermit")]
use hermit as _;

fn main() {
println!("Environment Test");
for (key, value) in env::vars() {
println!("ENVIRONMENT: {key}: {value}");
}
}

0 comments on commit 795cb35

Please sign in to comment.