Skip to content

Commit 0bfe983

Browse files
committed
Added selective environment passing for VM
1 parent 08a6e75 commit 0bfe983

File tree

8 files changed

+202
-15
lines changed

8 files changed

+202
-15
lines changed

src/bin/uhyve.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use env_logger::Builder;
99
use log::LevelFilter;
1010
use thiserror::Error;
1111
use uhyvelib::{
12-
params::{CpuCount, GuestMemorySize, Output, Params},
12+
params::{CpuCount, EnvVars, GuestMemorySize, Output, Params},
1313
vm::UhyveVm,
1414
};
1515

@@ -75,6 +75,14 @@ struct Args {
7575
#[cfg(target_os = "linux")]
7676
gdb_port: Option<u16>,
7777

78+
/// Environment variables of the guest as env=value paths
79+
///
80+
/// `-e host` passes all variables of the parent process to the kernel (discarding any other passed environment variables).
81+
///
82+
/// Example: --env_vars ASDF=jlk -e TERM=uhyveterm2000
83+
#[clap(short, long)]
84+
env_vars: Vec<String>,
85+
7886
#[clap(flatten, next_help_heading = "Memory OPTIONS")]
7987
memory_args: MemoryArgs,
8088

@@ -274,6 +282,7 @@ impl From<Args> for Params {
274282
kernel_args,
275283
output,
276284
stats,
285+
env_vars,
277286
} = args;
278287
Self {
279288
memory_size,
@@ -298,6 +307,7 @@ impl From<Args> for Params {
298307
Output::StdIo
299308
},
300309
stats,
310+
env: EnvVars::try_from(env_vars.as_slice()).unwrap(),
301311
}
302312
}
303313
}

src/hypercall.rs

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use crate::{
1010
consts::BOOT_PML4,
1111
isolation::filemap::UhyveFileMap,
1212
mem::{MemoryError, MmapMemory},
13+
params::EnvVars,
1314
virt_to_phys,
1415
vm::VmPeripherals,
1516
};
@@ -259,25 +260,34 @@ pub fn copy_argv(path: &OsStr, argv: &[String], syscmdval: &CmdvalParams, mem: &
259260
}
260261

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

269-
// Copy the environment variables into the vm memory
270-
for (counter, (key, value)) in std::env::vars_os().enumerate() {
271-
if counter >= MAX_ARGC_ENVC.try_into().unwrap() {
272-
warn!("Environment is larger than the maximum that can be copied to the VM. Remaining environment is ignored");
273-
break;
274-
}
268+
let env: Vec<(String, String)> = match env {
269+
EnvVars::Host => std::env::vars_os()
270+
.map(|(a, b)| (a.into_string().unwrap(), b.into_string().unwrap()))
271+
.collect(),
272+
EnvVars::Set(map) => map
273+
.iter()
274+
.map(|(a, b)| (a.to_owned(), b.to_owned()))
275+
.collect(),
276+
};
277+
if env.len() >= MAX_ARGC_ENVC.try_into().unwrap() {
278+
warn!("Environment is larger than the maximum that can be copied to the VM. Remaining environment is ignored");
279+
}
280+
let env_addrs = unsafe { std::slice::from_raw_parts(envp, env.len()) };
275281

282+
// Copy the environment variables into the vm memory
283+
for (counter, (key, value)) in env.iter().enumerate().take(MAX_ARGC_ENVC) {
276284
let len = key.len() + value.len() + 1;
277285
let env_dest = unsafe {
278286
mem.slice_at_mut(env_addrs[counter], len + 1)
279287
.expect("Systemcall parameters for Cmdval are invalid")
280288
};
289+
//write_env_into_mem(env_dest, key.as_bytes(), value.as_bytes());
290+
let len = key.len() + value.len() + 1;
281291
env_dest[0..key.len()].copy_from_slice(key.as_bytes());
282292
env_dest[key.len()] = b'=';
283293
env_dest[key.len() + 1..len].copy_from_slice(value.as_bytes());

src/linux/x86_64/kvm_cpu.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -452,7 +452,11 @@ impl VirtualCPU for KvmCpu {
452452
syscmdval,
453453
&self.peripherals.mem,
454454
);
455-
hypercall::copy_env(syscmdval, &self.peripherals.mem);
455+
hypercall::copy_env(
456+
&self.kernel_info.params.env,
457+
syscmdval,
458+
&self.peripherals.mem,
459+
);
456460
}
457461
Hypercall::Exit(sysexit) => {
458462
return Ok(VcpuStopReason::Exit(sysexit.arg));

src/macos/aarch64/vcpu.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,11 @@ impl VirtualCPU for XhyveCpu {
256256
syscmdval,
257257
&self.peripherals.mem,
258258
);
259-
copy_env(syscmdval, &self.peripherals.mem);
259+
copy_env(
260+
&self.kernel_info.params.env,
261+
syscmdval,
262+
&self.peripherals.mem,
263+
);
260264
}
261265
Hypercall::FileClose(sysclose) => hypercall::close(sysclose),
262266
Hypercall::FileLseek(syslseek) => hypercall::lseek(syslseek),

src/params.rs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use std::{
2+
collections::HashMap,
23
convert::Infallible,
34
fmt,
45
num::{NonZeroU32, ParseIntError, TryFromIntError},
@@ -46,6 +47,9 @@ pub struct Params {
4647

4748
/// Collect run statistics
4849
pub stats: bool,
50+
51+
/// Environment variables of the kernel
52+
pub env: EnvVars,
4953
}
5054

5155
#[allow(clippy::derivable_impls)]
@@ -66,6 +70,7 @@ impl Default for Params {
6670
kernel_args: Default::default(),
6771
output: Default::default(),
6872
stats: false,
73+
env: EnvVars::default(),
6974
}
7075
}
7176
}
@@ -207,3 +212,65 @@ impl FromStr for GuestMemorySize {
207212
Ok(memory_size)
208213
}
209214
}
215+
216+
/// Configure the kernels environment variables.
217+
#[derive(Debug, Clone, PartialEq)]
218+
pub enum EnvVars {
219+
/// Pass all env vars of the host to the kernel.
220+
Host,
221+
/// Pass a certain set of env vars to the kernel.
222+
Set(HashMap<String, String>),
223+
}
224+
impl Default for EnvVars {
225+
fn default() -> Self {
226+
Self::Set(HashMap::new())
227+
}
228+
}
229+
impl<S: AsRef<str> + std::fmt::Debug + PartialEq<S> + From<&'static str>> TryFrom<&[S]>
230+
for EnvVars
231+
{
232+
type Error = &'static str;
233+
234+
fn try_from(v: &[S]) -> Result<Self, Self::Error> {
235+
if v.contains(&S::from("host")) {
236+
if v.len() != 1 {
237+
warn!(
238+
"Specifying -e host discards all other explicitly specified environment vars"
239+
);
240+
}
241+
return Ok(Self::Host);
242+
}
243+
244+
Ok(Self::Set(v.iter().try_fold(
245+
HashMap::new(),
246+
|mut acc, s| {
247+
if let Some(split) = s.as_ref().split_once("=") {
248+
acc.insert(split.0.to_owned(), split.1.to_owned());
249+
Ok(acc)
250+
} else {
251+
Err("Invalid environment variables parameter format: Must be -e var=value")
252+
}
253+
},
254+
)?))
255+
}
256+
}
257+
258+
#[cfg(test)]
259+
mod tests {
260+
use super::*;
261+
262+
#[test]
263+
fn test_env_vars() {
264+
let strings = [String::from("ASDF=asdf"), String::from("EMOJI=🤷")];
265+
266+
let env_vars = EnvVars::try_from(strings.as_slice()).unwrap();
267+
let EnvVars::Set(map) = env_vars else {
268+
panic!();
269+
};
270+
assert_eq!(map.get("ASDF").unwrap(), "asdf");
271+
assert_eq!(map.get("EMOJI").unwrap(), "🤷");
272+
273+
let env_vars = EnvVars::try_from(&["host", "OTHER=asdf"] as &[&str]).unwrap();
274+
assert_eq!(env_vars, EnvVars::Host);
275+
}
276+
}

src/vm.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ use crate::{
2626
isolation::filemap::UhyveFileMap,
2727
mem::MmapMemory,
2828
os::KickSignal,
29-
params::Params,
29+
params::{EnvVars, Params},
3030
serial::{Destination, UhyveSerial},
3131
stats::{CpuStats, VmStats},
3232
vcpu::VirtualCPU,
@@ -348,8 +348,13 @@ fn write_fdt_into_mem(mem: &MmapMemory, params: &Params, cpu_freq: Option<NonZer
348348
.memory(mem.guest_address..mem.guest_address + mem.memory_size as u64)
349349
.unwrap()
350350
.kernel_args(&params.kernel_args[..sep])
351-
.app_args(params.kernel_args.get(sep + 1..).unwrap_or_default())
352-
.envs(env::vars());
351+
.app_args(params.kernel_args.get(sep + 1..).unwrap_or_default());
352+
353+
fdt = match &params.env {
354+
EnvVars::Host => fdt.envs(env::vars()),
355+
EnvVars::Set(map) => fdt.envs(map.iter().map(|(a, b)| (a.as_str(), b.as_str()))),
356+
};
357+
353358
if let Some(tsc_khz) = cpu_freq {
354359
fdt = fdt.tsc_khz(tsc_khz.into()).unwrap();
355360
}

tests/env.rs

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
mod common;
2+
3+
use std::collections::HashMap;
4+
5+
use byte_unit::{Byte, Unit};
6+
use common::{build_hermit_bin, check_result};
7+
use uhyvelib::{
8+
params::{EnvVars, Output, Params},
9+
vm::UhyveVm,
10+
};
11+
12+
#[test]
13+
fn selective_env_test() {
14+
env_logger::try_init().ok();
15+
let bin_path = build_hermit_bin("env");
16+
17+
let env_vars = [
18+
("ASDF".to_string(), "ASDF".to_string()),
19+
("a0978gbsdf".to_string(), ";3254jgnsadfg".to_string()),
20+
("EMOJI".to_string(), "🙂".to_string()),
21+
];
22+
23+
println!("Launching kernel {}", bin_path.display());
24+
let params = Params {
25+
cpu_count: 2.try_into().unwrap(),
26+
memory_size: Byte::from_u64_with_unit(64, Unit::MiB)
27+
.unwrap()
28+
.try_into()
29+
.unwrap(),
30+
output: Output::Buffer,
31+
env: EnvVars::Set(HashMap::from(env_vars.clone())),
32+
..Default::default()
33+
};
34+
let vm = UhyveVm::new(bin_path, params).unwrap();
35+
let res = vm.run(None);
36+
check_result(&res);
37+
println!("{:?}", res.output.as_ref().unwrap());
38+
for (key, value) in env_vars.iter() {
39+
assert!(res
40+
.output
41+
.as_ref()
42+
.unwrap()
43+
.contains(&format!("ENVIRONMENT: {key}: {value}")));
44+
}
45+
}
46+
47+
#[test]
48+
fn host_env_test() {
49+
env_logger::try_init().ok();
50+
let bin_path = build_hermit_bin("env");
51+
52+
println!("Launching kernel {}", bin_path.display());
53+
let params = Params {
54+
cpu_count: 2.try_into().unwrap(),
55+
memory_size: Byte::from_u64_with_unit(64, Unit::MiB)
56+
.unwrap()
57+
.try_into()
58+
.unwrap(),
59+
output: Output::Buffer,
60+
env: EnvVars::Host,
61+
..Default::default()
62+
};
63+
let vm = UhyveVm::new(bin_path, params).unwrap();
64+
let res = vm.run(None);
65+
check_result(&res);
66+
println!("{:?}", res.output.as_ref().unwrap());
67+
68+
let common_env_vars = ["PWD", "CARGO_MANIFEST_DIR"];
69+
for env in common_env_vars.iter() {
70+
assert!(res
71+
.output
72+
.as_ref()
73+
.unwrap()
74+
.contains(&format!("ENVIRONMENT: {env}:")));
75+
}
76+
}

tests/test-kernels/src/bin/env.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
use std::env;
2+
3+
#[cfg(target_os = "hermit")]
4+
use hermit as _;
5+
6+
fn main() {
7+
println!("Environment Test");
8+
for (key, value) in env::vars() {
9+
println!("ENVIRONMENT: {key}: {value}");
10+
}
11+
}

0 commit comments

Comments
 (0)