use core::cmp;
use std::{
ffi::{CStr, CString, OsStr},
io,
os::{fd::IntoRawFd, unix::ffi::OsStrExt},
};
use uhyve_interface::{
GuestPhysAddr,
v1::{self, MAX_ARGC_ENVC},
v2::{self, parameters::*},
};
use crate::{
HypervisorResult,
isolation::{
fd::{FdData, GuestFd, UhyveFileDescriptorLayer},
filemap::{UhyveFileMap, UhyveMapLeaf},
},
mem::MmapMemory,
net::NetworkBackend,
params::EnvVars,
vcpu::VcpuStopReason,
virt_to_phys,
vm::{KernelInfo, VmPeripherals},
};
pub unsafe fn address_to_hypercall_v1(
mem: &MmapMemory,
addr: u16,
data: GuestPhysAddr,
) -> Option<v1::Hypercall<'_>> {
use v1::{Hypercall, HypercallAddress};
macro_rules! get_data {
() => {{ unsafe { mem.get_ref_mut(data).unwrap() } }};
}
Some(match HypercallAddress::try_from(addr).ok()? {
HypercallAddress::FileClose => Hypercall::FileClose(get_data!()),
HypercallAddress::FileLseek => Hypercall::FileLseek(get_data!()),
HypercallAddress::FileOpen => Hypercall::FileOpen(get_data!()),
HypercallAddress::FileRead => Hypercall::FileRead(get_data!()),
HypercallAddress::FileWrite => Hypercall::FileWrite(get_data!()),
HypercallAddress::FileUnlink => Hypercall::FileUnlink(get_data!()),
HypercallAddress::Exit => Hypercall::Exit(get_data!()),
HypercallAddress::Cmdsize => Hypercall::Cmdsize(get_data!()),
HypercallAddress::Cmdval => Hypercall::Cmdval(get_data!()),
HypercallAddress::Uart => Hypercall::SerialWriteByte(data.as_u64() as u8),
HypercallAddress::SerialBufferWrite => Hypercall::SerialWriteBuffer(get_data!()),
_ => return None,
})
}
pub unsafe fn address_to_hypercall_v2(
mem: &MmapMemory,
addr: u64,
data: GuestPhysAddr,
) -> Option<v2::Hypercall<'_>> {
use v2::{Hypercall, HypercallAddress};
macro_rules! get_data {
() => {{ unsafe { mem.get_ref_mut(data).unwrap() } }};
}
Some(match HypercallAddress::try_from(addr).ok()? {
HypercallAddress::FileClose => Hypercall::FileClose(get_data!()),
HypercallAddress::FileLseek => Hypercall::FileLseek(get_data!()),
HypercallAddress::FileOpen => Hypercall::FileOpen(get_data!()),
HypercallAddress::FileRead => Hypercall::FileRead(get_data!()),
HypercallAddress::FileWrite => Hypercall::FileWrite(get_data!()),
HypercallAddress::FileUnlink => Hypercall::FileUnlink(get_data!()),
HypercallAddress::Exit => Hypercall::Exit(data.as_u64() as i32),
HypercallAddress::SerialReadBuffer => Hypercall::SerialReadBuffer(get_data!()),
HypercallAddress::SerialWriteBuffer => Hypercall::SerialWriteBuffer(get_data!()),
HypercallAddress::SerialWriteByte => Hypercall::SerialWriteByte(data.as_u64() as u8),
_ => return None,
})
}
pub fn handle_hypercall_v2<N: NetworkBackend>(
peripherals: &VmPeripherals<N>,
hypercall: v2::Hypercall<'_>,
) -> Option<VcpuStopReason> {
let file_mapping = || peripherals.file_mapping.lock().unwrap();
match hypercall {
v2::Hypercall::Exit(sysexit) => {
return Some(VcpuStopReason::Exit(sysexit));
}
v2::Hypercall::FileClose(sysclose) => close(sysclose, &mut file_mapping()),
v2::Hypercall::FileLseek(syslseek) => lseek(syslseek, &mut file_mapping()),
v2::Hypercall::FileOpen(sysopen) => open(&peripherals.mem, sysopen, &mut file_mapping()),
v2::Hypercall::FileRead(sysread) => read(&peripherals.mem, sysread, &mut file_mapping()),
v2::Hypercall::FileWrite(syswrite) => {
write(peripherals, syswrite, &mut file_mapping()).unwrap();
}
v2::Hypercall::FileUnlink(sysunlink) => {
unlink(&peripherals.mem, sysunlink, &mut file_mapping())
}
v2::Hypercall::SerialWriteByte(buf) => peripherals
.serial
.output(&[buf])
.unwrap_or_else(|e| error!("{e:?}")),
v2::Hypercall::SerialWriteBuffer(sysserialwrite) => {
let buf = unsafe {
peripherals
.mem
.slice_at(sysserialwrite.buf, sysserialwrite.len as usize)
.unwrap_or_else(|e| {
panic!(
"Error {e}: Systemcall parameters for SerialWriteBuffer are invalid: {sysserialwrite:?}"
)
})
};
peripherals
.serial
.output(buf)
.unwrap_or_else(|e| error!("{e:?}"))
}
_ => panic!("Got unknown hypercall {hypercall:?}"),
}
None
}
pub fn handle_hypercall_v1<N: NetworkBackend>(
peripherals: &VmPeripherals<N>,
kernel_info: &KernelInfo,
root_pt: impl FnOnce() -> HypervisorResult<GuestPhysAddr>,
hypercall: v1::Hypercall<'_>,
) -> Option<HypervisorResult<VcpuStopReason>> {
let file_mapping = || peripherals.file_mapping.lock().unwrap();
match hypercall {
v1::Hypercall::Cmdsize(syssize) => {
syssize.update(&kernel_info.path, &kernel_info.params.kernel_args)
}
v1::Hypercall::Cmdval(syscmdval) => {
copy_argv(
kernel_info.path.as_os_str(),
&kernel_info.params.kernel_args,
syscmdval,
&peripherals.mem,
);
copy_env(&kernel_info.params.env, syscmdval, &peripherals.mem);
}
v1::Hypercall::Exit(sysexit) => {
return Some(Ok(VcpuStopReason::Exit(sysexit.arg)));
}
v1::Hypercall::FileClose(sysclose) => close(sysclose, &mut file_mapping()),
v1::Hypercall::FileLseek(syslseek) => lseek_v1(syslseek, &mut file_mapping()),
v1::Hypercall::FileOpen(sysopen) => open(&peripherals.mem, sysopen, &mut file_mapping()),
v1::Hypercall::FileRead(sysread) => read_v1(
&peripherals.mem,
sysread,
match root_pt() {
Ok(root_pt) => root_pt,
Err(e) => return Some(Err(e)),
},
&mut file_mapping(),
),
v1::Hypercall::FileWrite(syswrite) => write_v1(
peripherals,
syswrite,
match root_pt() {
Ok(root_pt) => root_pt,
Err(e) => return Some(Err(e)),
},
&mut file_mapping(),
)
.unwrap(),
v1::Hypercall::FileUnlink(sysunlink) => {
unlink(&peripherals.mem, sysunlink, &mut file_mapping())
}
v1::Hypercall::SerialWriteByte(buf) => peripherals
.serial
.output(&[buf])
.unwrap_or_else(|e| error!("{e:?}")),
v1::Hypercall::SerialWriteBuffer(sysserialwrite) => {
let buf = unsafe {
peripherals
.mem
.slice_at(sysserialwrite.buf, sysserialwrite.len)
.unwrap_or_else(|e| {
panic!(
"Error {e}: Systemcall parameters for SerialWriteBuffer are invalid: {sysserialwrite:?}"
)
})
};
peripherals
.serial
.output(buf)
.unwrap_or_else(|e| error!("{e:?}"))
}
_ => panic!("Got unknown hypercall {hypercall:?}"),
}
None
}
fn translate_last_errno() -> Option<i32> {
let errno = io::Error::last_os_error().raw_os_error()?;
macro_rules! error_pairs {
($($x:ident),*) => {{[ $((libc::$x, hermit_abi::errno::$x)),* ]}}
}
for (e_host, e_guest) in error_pairs!(
EBADF, EEXIST, EFAULT, EINVAL, EIO, EISDIR, EOVERFLOW, EPERM, ENOENT, EROFS
) {
if errno == e_host {
return Some(e_guest);
}
}
warn!(
"No Hermit equivalent of host error {} (errno: {errno}), returning default to guest...",
io::Error::from_raw_os_error(errno)
);
None
}
unsafe fn decode_guest_path(mem: &MmapMemory, path_addr: GuestPhysAddr) -> Option<&str> {
let requested_path_ptr = mem.host_address(path_addr).unwrap() as *const i8;
unsafe { CStr::from_ptr(requested_path_ptr) }.to_str().ok()
}
fn unlink(mem: &MmapMemory, sysunlink: &mut UnlinkParams, file_map: &mut UhyveFileMap) {
let guest_path = if let Some(guest_path) = unsafe { decode_guest_path(mem, sysunlink.name) } {
guest_path
} else {
error!("The kernel requested to unlink() an non-UTF8 path: Rejecting...");
sysunlink.ret = -EINVAL;
return;
};
sysunlink.ret = match file_map.unlink(guest_path) {
Ok(Some(host_path)) => {
let host_path_c_string = CString::new(host_path.as_os_str().as_bytes()).unwrap();
if unsafe { libc::unlink(host_path_c_string.as_c_str().as_ptr()) } < 0 {
-translate_last_errno().unwrap_or(1)
} else {
0
}
}
Ok(None) => {
0
}
Err(()) => {
error!(
"The kernel requested to unlink() an unknown path ({guest_path:?}): Rejecting..."
);
-ENOENT
}
};
}
fn open(mem: &MmapMemory, sysopen: &mut OpenParams, file_map: &mut UhyveFileMap) {
let guest_path = if let Some(guest_path) = unsafe { decode_guest_path(mem, sysopen.name) } {
guest_path
} else {
error!("The kernel requested to open() an non-UTF8 path: Rejecting...");
sysopen.ret = -EINVAL;
return;
};
let mut flags = sysopen.flags & ALLOWED_OPEN_FLAGS;
if (flags & (O_DIRECTORY | O_CREAT)) == (O_DIRECTORY | O_CREAT) {
error!("An open() call used O_DIRECTORY and O_CREAT at the same time. Aborting...");
sysopen.ret = -EINVAL;
return;
}
fn do_open(
fdmap: &mut UhyveFileDescriptorLayer,
host_path_c_string: CString,
flags: i32,
mode: i32,
) -> i32 {
let host_fd = unsafe { libc::open(host_path_c_string.as_c_str().as_ptr(), flags, mode) };
if host_fd < 0 {
let errno = translate_last_errno().unwrap_or(1);
if host_fd != -1 {
warn!("Unexpected return value {host_fd} from open(2)");
}
-errno
} else if let Some(guest_fd) = fdmap.insert(FdData::Raw(host_fd)) {
guest_fd.0
} else {
-ENOENT
}
}
sysopen.ret = if let Some(host_path) = file_map.get_host_path(guest_path) {
debug!("{guest_path:#?} found in file map.");
match host_path {
UhyveMapLeaf::OnHost(host_path) => {
let host_path_c_string = CString::new(host_path.as_os_str().as_bytes()).unwrap();
do_open(&mut file_map.fdmap, host_path_c_string, flags, sysopen.mode)
}
UhyveMapLeaf::Virtual(data) => {
if let Some(guest_fd) = file_map.fdmap.insert(FdData::Virtual {
data: data.clone(),
offset: 0,
}) {
guest_fd.0
} else {
-ENOENT
}
}
}
} else {
debug!("{guest_path:#?} not found in file map.");
if (flags & O_CREAT) == O_CREAT {
debug!("Attempting to open a temp file for {guest_path:#?}...");
flags |= O_EXCL;
#[cfg(target_os = "linux")]
{
flags |= file_map.get_io_mode_flags();
}
match file_map.create_temporary_file(guest_path) {
Some(host_path_c_string) => {
do_open(&mut file_map.fdmap, host_path_c_string, flags, sysopen.mode)
}
None => {
debug!("Returning -EINVAL for {guest_path:#?}");
-EINVAL
}
}
} else {
debug!("Returning -ENOENT for {guest_path:#?}");
-ENOENT
}
}
}
fn close(sysclose: &mut CloseParams, file_map: &mut UhyveFileMap) {
let gfd = GuestFd(sysclose.fd);
debug!(
"Guest tries to close fd {gfd} from fdmap {:?}",
file_map.fdmap
);
sysclose.ret = if gfd.is_standard() {
warn!("Guest tried to close stdio fd: {gfd}");
0
} else if let Some(fddata) = file_map.fdmap.remove(gfd) {
if let FdData::Raw(fd) = fddata
&& unsafe { libc::close(fd) } < 0
{
-translate_last_errno().unwrap_or(1)
} else {
0
}
} else {
warn!("Guest tried to close unknown fd: {gfd}");
-EBADF
};
}
fn read_v1(
mem: &MmapMemory,
sysread: &mut v1::parameters::ReadParams,
root_pt: GuestPhysAddr,
file_map: &mut UhyveFileMap,
) {
sysread.ret = if let Ok(guest_phys_addr) = virt_to_phys(sysread.buf, mem, root_pt) {
let mut tmp = v2::parameters::ReadParams {
fd: sysread.fd,
buf: guest_phys_addr,
len: sysread.len as u64,
ret: 0i64,
};
read(mem, &mut tmp, file_map);
tmp.ret
.try_into()
.unwrap_or_else(|ret| panic!("Unable to fit return value {} in read_v1.", ret))
} else {
warn!("Unable to convert guest virtual address into guest physical address");
-EFAULT as isize
}
}
fn read(mem: &MmapMemory, sysread: &mut v2::parameters::ReadParams, file_map: &mut UhyveFileMap) {
sysread.ret = if let Some(fdata) = file_map.fdmap.get_mut(GuestFd(sysread.fd.into_raw_fd())) {
if let Ok(host_address) = mem.host_address(sysread.buf) {
match fdata {
FdData::Raw(rfd) => {
let bytes_read = unsafe {
libc::read(
*rfd,
host_address as *mut libc::c_void,
sysread.len as usize,
)
};
if bytes_read < 0 {
-translate_last_errno().unwrap_or(1) as i64
} else {
bytes_read as i64
}
}
FdData::Virtual { data, offset } => {
let remaining = {
let pos = cmp::min(*offset, data.len() as u64);
&data[pos as usize..]
};
let amt = cmp::min(remaining.len() as u64, sysread.len) as usize;
assert!(amt <= isize::MAX as usize);
unsafe {
core::ptr::copy_nonoverlapping(
remaining.as_ptr(),
host_address as *mut u8,
amt,
)
};
amt as i64
}
}
} else {
warn!("Unable to get host address for read buffer");
-EFAULT as i64
}
} else {
-EBADF as i64
};
}
fn write_v1<N: NetworkBackend>(
peripherals: &VmPeripherals<N>,
syswrite: &v1::parameters::WriteParams,
root_pt: GuestPhysAddr,
file_map: &mut UhyveFileMap,
) -> io::Result<()> {
let guest_phys_addr = virt_to_phys(syswrite.buf, &peripherals.mem, root_pt).map_err(|e| {
io::Error::new(
io::ErrorKind::InvalidInput,
format!("invalid syswrite buffer: {e:?}"),
)
})?;
let mut tmp = v2::parameters::WriteParams {
fd: syswrite.fd,
buf: guest_phys_addr,
len: syswrite.len as u64,
ret: 0i64,
};
write(peripherals, &mut tmp, file_map)
}
fn write<N: NetworkBackend>(
peripherals: &VmPeripherals<N>,
syswrite: &mut v2::parameters::WriteParams,
file_map: &mut UhyveFileMap,
) -> io::Result<()> {
let mut bytes = unsafe {
let guest_phys_addr = syswrite.buf;
peripherals
.mem
.slice_at(guest_phys_addr, syswrite.len.try_into().unwrap())
.map_err(|e| {
io::Error::new(
io::ErrorKind::InvalidInput,
format!("invalid syswrite buffer: {e:?}"),
)
})?
};
match file_map.fdmap.get_mut(GuestFd(syswrite.fd.into_raw_fd())) {
None => {
syswrite.ret = -EBADF as i64;
Err(io::Error::other("Bad file descriptor"))
}
Some(FdData::Virtual { .. }) => {
syswrite.ret = -EROFS as i64;
Err(io::Error::new(
io::ErrorKind::ReadOnlyFilesystem,
format!(
"Unable to write to virtual file {}",
GuestFd(syswrite.fd.into_raw_fd())
),
))
}
Some(FdData::Raw(1 | 2)) => {
syswrite.ret = bytes.len().try_into().unwrap();
peripherals.serial.output(bytes)
}
Some(FdData::Raw(r)) => {
syswrite.ret = 0;
while !bytes.is_empty() {
let step = unsafe {
libc::write(
*r,
&bytes[0] as *const u8 as *const libc::c_void,
bytes.len(),
)
};
if step >= 0 {
syswrite.ret += step as i64;
bytes = &bytes[step as usize..];
} else {
syswrite.ret = -translate_last_errno().unwrap_or(1) as i64;
return Err(io::Error::last_os_error());
}
}
Ok(())
}
}
}
fn lseek_v1(syslseek: &mut v1::parameters::LseekParams, file_map: &mut UhyveFileMap) {
let mut tmp = LseekParams {
offset: syslseek.offset as i64,
whence: syslseek.whence as u32,
fd: syslseek.fd,
};
lseek(&mut tmp, file_map);
if tmp.offset < 0 {
tmp.offset = -1;
}
syslseek.offset = tmp
.offset
.try_into()
.unwrap_or_else(|ret| panic!("Unable to fit return value {} in lseek_v1.", ret));
}
fn lseek(syslseek: &mut LseekParams, file_map: &mut UhyveFileMap) {
syslseek.offset = match file_map.fdmap.get_mut(GuestFd(syslseek.fd.into_raw_fd())) {
Some(FdData::Raw(r)) => {
let ret = unsafe { libc::lseek(*r, syslseek.offset, syslseek.whence as i32) };
if ret < 0 {
-translate_last_errno().unwrap_or(1) as i64
} else {
ret
}
}
Some(FdData::Virtual { data, offset }) => {
#[forbid(unused_variables, unreachable_patterns)]
let tmp: i64 = match syslseek.whence as i32 {
SEEK_SET => 0,
SEEK_CUR => *offset as i64,
SEEK_END => data.len() as i64,
_ => -EINVAL as i64,
};
if tmp >= 0 {
let tmp2 = tmp + syslseek.offset;
match tmp2.try_into() {
Ok(tmp3) => {
*offset = tmp3;
tmp2
}
_ => -EOVERFLOW as i64,
}
} else {
tmp
}
}
None => {
warn!("lseek attempted to use an unknown file descriptor");
-EBADF as i64
}
};
}
fn copy_argv(
path: &OsStr,
argv: &[String],
syscmdval: &v1::parameters::CmdvalParams,
mem: &MmapMemory,
) {
let argvp = mem
.host_address(syscmdval.argv)
.expect("Systemcall parameters for Cmdval are invalid") as *const GuestPhysAddr;
let arg_addrs = unsafe { std::slice::from_raw_parts(argvp, argv.len() + 1) };
{
let len = path.len();
let path_dest = unsafe {
mem.slice_at_mut(arg_addrs[0], len + 1)
.expect("Systemcall parameters for Cmdval are invalid")
};
path_dest[0..len].copy_from_slice(path.as_bytes());
path_dest[len] = 0; }
for (argument, &arg_dest_addr) in argv.iter().zip(arg_addrs.iter()) {
let len = argument.len();
let arg_dest = unsafe {
mem.slice_at_mut(arg_dest_addr, len + 1)
.expect("Systemcall parameters for Cmdval are invalid")
};
arg_dest[0..len].copy_from_slice(argument.as_bytes());
arg_dest[len] = 0;
}
}
fn copy_env(env: &EnvVars, syscmdval: &v1::parameters::CmdvalParams, mem: &MmapMemory) {
let envp = mem
.host_address(syscmdval.envp)
.expect("Systemcall parameters for Cmdval are invalid") as *const GuestPhysAddr;
let env: Vec<(String, String)> = match env {
EnvVars::Host => std::env::vars_os()
.map(|(a, b)| (a.into_string().unwrap(), b.into_string().unwrap()))
.collect(),
EnvVars::Set(map) => map
.iter()
.map(|(a, b)| (a.to_owned(), b.to_owned()))
.collect(),
};
if env.len() >= MAX_ARGC_ENVC {
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()) };
for ((key, value), &env_dest_addr) in env.iter().take(MAX_ARGC_ENVC).zip(env_addrs.iter()) {
let len = key.len() + value.len() + 1;
let env_dest = unsafe {
mem.slice_at_mut(env_dest_addr, len + 1)
.expect("Systemcall parameters for Cmdval are invalid")
};
let len = key.len() + value.len() + 1;
env_dest[0..key.len()].copy_from_slice(key.as_bytes());
env_dest[key.len()] = b'=';
env_dest[key.len() + 1..len].copy_from_slice(value.as_bytes());
env_dest[len] = 0;
}
}