Skip to content

Commit eb9f5c7

Browse files
committed
refactor(isolation/filemap): Improve API
This shifts usages of `CStr` and `OsStr` onto API consumers. All places where guest paths are used now use `&str`, and all places where host paths are used now use `&Path` (or the owned variants, respectively).
1 parent 926a50e commit eb9f5c7

File tree

5 files changed

+57
-73
lines changed

5 files changed

+57
-73
lines changed

src/hypercall.rs

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -121,19 +121,35 @@ fn translate_last_errno() -> Option<i32> {
121121
None
122122
}
123123

124+
/// Decodes a guest path given at address `path_addr` in `mem`.
125+
///
126+
/// # Safety
127+
///
128+
/// The calling convention of hypercalls ensures that the given address doesn't alias with anything mutable.
129+
/// The return value is only valid for the duration of the hypercall.
130+
unsafe fn decode_guest_path(mem: &MmapMemory, path_addr: GuestPhysAddr) -> Option<&str> {
131+
let requested_path_ptr = mem.host_address(path_addr).unwrap() as *const i8;
132+
unsafe { CStr::from_ptr(requested_path_ptr) }.to_str().ok()
133+
}
134+
124135
/// unlink deletes a name from the filesystem. This is used to handle `unlink` syscalls from the guest.
125136
///
126-
/// Note for when using Landlock: Unlinking files results in them being veiled. If a text
137+
/// Note for when using Landlock: Unlinking files results in them being veiled. If a
127138
/// file (that existed during initialization) called `log.txt` is unlinked, attempting to
128139
/// open `log.txt` again will result in an error.
129140
pub fn unlink(mem: &MmapMemory, sysunlink: &mut UnlinkParams, file_map: &mut UhyveFileMap) {
130-
let requested_path_ptr = mem.host_address(sysunlink.name).unwrap() as *const i8;
131-
let guest_path = unsafe { CStr::from_ptr(requested_path_ptr) };
141+
let guest_path = if let Some(guest_path) = unsafe { decode_guest_path(mem, sysunlink.name) } {
142+
guest_path
143+
} else {
144+
error!("The kernel requested to unlink() an non-UTF8 path: Rejecting...");
145+
sysunlink.ret = -EINVAL;
146+
return;
147+
};
132148
sysunlink.ret = match file_map.unlink(guest_path) {
133149
Ok(Some(host_path)) => {
134150
// We can safely unwrap here, as host_path.as_bytes will never contain internal \0 bytes
135151
// As host_path_c_string is a valid CString, this implementation is presumed to be safe.
136-
let host_path_c_string = CString::new(host_path.as_bytes()).unwrap();
152+
let host_path_c_string = CString::new(host_path.as_os_str().as_bytes()).unwrap();
137153
if unsafe { libc::unlink(host_path_c_string.as_c_str().as_ptr()) } < 0 {
138154
-translate_last_errno().unwrap_or(1)
139155
} else {
@@ -155,9 +171,14 @@ pub fn unlink(mem: &MmapMemory, sysunlink: &mut UnlinkParams, file_map: &mut Uhy
155171

156172
/// Handles an open syscall by opening a file on the host.
157173
pub fn open(mem: &MmapMemory, sysopen: &mut OpenParams, file_map: &mut UhyveFileMap) {
158-
let requested_path_ptr = mem.host_address(sysopen.name).unwrap() as *const i8;
174+
let guest_path = if let Some(guest_path) = unsafe { decode_guest_path(mem, sysopen.name) } {
175+
guest_path
176+
} else {
177+
error!("The kernel requested to open() an non-UTF8 path: Rejecting...");
178+
sysopen.ret = -EINVAL;
179+
return;
180+
};
159181
let mut flags = sysopen.flags & ALLOWED_OPEN_FLAGS;
160-
let guest_path = unsafe { CStr::from_ptr(requested_path_ptr) };
161182
// See: https://lwn.net/Articles/926782/
162183
// See: https://github.com/hermit-os/kernel/commit/71bc629
163184
if (flags & (O_DIRECTORY | O_CREAT)) == (O_DIRECTORY | O_CREAT) {
@@ -240,10 +261,7 @@ pub fn open(mem: &MmapMemory, sysopen: &mut OpenParams, file_map: &mut UhyveFile
240261

241262
/// Handles an close syscall by closing the file on the host.
242263
pub fn close(sysclose: &mut CloseParams, file_map: &mut UhyveFileMap) {
243-
sysclose.ret = if file_map
244-
.fdmap
245-
.is_fd_present(GuestFd(sysclose.fd.into_raw_fd()))
246-
{
264+
sysclose.ret = if file_map.fdmap.is_fd_present(GuestFd(sysclose.fd)) {
247265
match file_map.fdmap.remove(GuestFd(sysclose.fd)) {
248266
Some(FdData::Raw(fd)) => {
249267
if unsafe { libc::close(fd) } < 0 {

src/isolation/filemap/mod.rs

Lines changed: 12 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,4 @@
1-
use std::{
2-
ffi::{CStr, CString, OsString},
3-
os::unix::ffi::OsStrExt,
4-
path::PathBuf,
5-
sync::Arc,
6-
};
1+
use std::{ffi::CString, os::unix::ffi::OsStrExt, path::PathBuf, sync::Arc};
72

83
#[cfg(target_os = "linux")]
94
use libc::{O_DIRECT, O_SYNC};
@@ -124,10 +119,7 @@ impl UhyveFileMap {
124119
let data: Arc<[u8]> = i.data().to_vec().into();
125120

126121
// UNWRAP: `tar_no_std::ArchiveEntry::filename` already truncates at the first null byte.
127-
if !self.create_file(
128-
&CString::new(filename).unwrap(),
129-
UhyveTreeFile::Virtual(data),
130-
) {
122+
if !self.create_file(filename, UhyveTreeFile::Virtual(data)) {
131123
return Err(HermitImageError::CreateFile(filename.to_string()));
132124
}
133125
}
@@ -138,17 +130,17 @@ impl UhyveFileMap {
138130
/// Returns the host_path on the host filesystem given a requested guest_path, if it exists.
139131
///
140132
/// * `guest_path` - The guest path that is to be looked up in the map.
141-
pub fn get_host_path(&self, guest_path: &CStr) -> Option<UhyveTreeFile> {
142-
tree::resolve_guest_path(&self.root, guest_path.to_bytes())
133+
pub fn get_host_path(&self, guest_path: &str) -> Option<UhyveTreeFile> {
134+
tree::resolve_guest_path(&self.root, guest_path.as_bytes())
143135
}
144136

145137
/// Returns an array of all host paths (for Landlock).
146138
#[cfg(target_os = "linux")]
147-
pub(crate) fn get_all_host_paths(&self) -> impl Iterator<Item = &std::ffi::OsStr> {
148-
tree::get_all_host_paths(&self.root).map(|i| i.as_os_str())
139+
pub(crate) fn get_all_host_paths(&self) -> impl Iterator<Item = &std::path::Path> {
140+
tree::get_all_host_paths(&self.root)
149141
}
150142

151-
/// Returns an iterator (non-unique) over all mountable guest directories.
143+
/// Returns an iterator over all mountable guest directories.
152144
pub(crate) fn get_all_guest_dirs(&self) -> impl Iterator<Item = String> {
153145
tree::get_all_guest_dirs(&self.root)
154146
}
@@ -178,15 +170,15 @@ impl UhyveFileMap {
178170
///
179171
/// Note that this is also used for the entire setup of the uhyve file tree,
180172
/// and this also called for the entire initial mapping.
181-
pub fn create_file(&mut self, guest_path: &CStr, file: UhyveTreeFile) -> bool {
182-
tree::create_file(&mut self.root, guest_path.to_bytes(), file)
173+
pub fn create_file(&mut self, guest_path: &str, file: UhyveTreeFile) -> bool {
174+
tree::create_file(&mut self.root, guest_path.as_bytes(), file)
183175
}
184176

185177
/// Inserts an opened temporary file into the file map. Returns a CString so that
186178
/// the file can be directly used by [crate::hypercall::open].
187179
///
188180
/// * `guest_path` - The requested guest path.
189-
pub fn create_temporary_file(&mut self, guest_path: &CStr) -> Option<CString> {
181+
pub fn create_temporary_file(&mut self, guest_path: &str) -> Option<CString> {
190182
let host_path = self.tempdir.path().join(Uuid::new_v4().to_string());
191183
trace!("create_temporary_file (host_path): {host_path:#?}");
192184
let ret = CString::new(host_path.as_os_str().as_bytes()).unwrap();
@@ -198,7 +190,7 @@ impl UhyveFileMap {
198190
}
199191

200192
/// Attempt to remove a file. Note that this will fail on non-empty directories.
201-
pub fn unlink(&mut self, guest_path: &CStr) -> Result<Option<OsString>, ()> {
202-
tree::unlink(&mut self.root, guest_path.to_bytes()).map(|i| i.map(|j| j.into_os_string()))
193+
pub fn unlink(&mut self, guest_path: &str) -> Result<Option<PathBuf>, ()> {
194+
tree::unlink(&mut self.root, guest_path.as_bytes())
203195
}
204196
}

src/isolation/filemap/tests.rs

Lines changed: 11 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -43,39 +43,37 @@ fn test_uhyvefilemap() {
4343
);
4444

4545
assert_eq!(
46-
map.get_host_path(c"readme_file.md")
46+
map.get_host_path("readme_file.md")
4747
.unwrap()
4848
.unwrap_on_host(),
4949
OsString::from(&map_results[0])
5050
);
5151
assert_eq!(
52-
map.get_host_path(c"guest_folder").unwrap().unwrap_on_host(),
52+
map.get_host_path("guest_folder").unwrap().unwrap_on_host(),
5353
OsString::from(&map_results[1])
5454
);
5555
assert_eq!(
56-
map.get_host_path(c"guest_symlink")
57-
.unwrap()
58-
.unwrap_on_host(),
56+
map.get_host_path("guest_symlink").unwrap().unwrap_on_host(),
5957
OsString::from(&map_results[2])
6058
);
6159
assert_eq!(
62-
map.get_host_path(c"guest_dangling_symlink")
60+
map.get_host_path("guest_dangling_symlink")
6361
.unwrap()
6462
.unwrap_on_host(),
6563
OsString::from(&map_results[3])
6664
);
6765
assert_eq!(
68-
map.get_host_path(c"guest_file").unwrap().unwrap_on_host(),
66+
map.get_host_path("guest_file").unwrap().unwrap_on_host(),
6967
OsString::from(&map_results[4])
7068
);
7169
assert_eq!(
72-
map.get_host_path(c"guest_file_symlink")
70+
map.get_host_path("guest_file_symlink")
7371
.unwrap()
7472
.unwrap_on_host(),
7573
OsString::from(&map_results[5])
7674
);
7775

78-
assert!(map.get_host_path(c"this_file_is_not_mapped").is_none());
76+
assert!(map.get_host_path("this_file_is_not_mapped").is_none());
7977
}
8078

8179
#[test]
@@ -110,12 +108,7 @@ fn test_uhyvefilemap_directory() {
110108
},
111109
);
112110

113-
let mut found_host_path = map.get_host_path(
114-
CString::new(target_guest_path.as_os_str().as_bytes())
115-
.unwrap()
116-
.as_c_str(),
117-
);
118-
111+
let mut found_host_path = map.get_host_path(target_guest_path.to_str().unwrap());
119112
assert_eq!(found_host_path.unwrap().unwrap_on_host(), target_host_path);
120113

121114
// Tests successful directory traversal of the child directory.
@@ -124,11 +117,7 @@ fn test_uhyvefilemap_directory() {
124117
target_host_path.pop();
125118
target_guest_path.pop();
126119

127-
found_host_path = map.get_host_path(
128-
CString::new(target_guest_path.as_os_str().as_bytes())
129-
.unwrap()
130-
.as_c_str(),
131-
);
120+
found_host_path = map.get_host_path(target_guest_path.to_str().unwrap());
132121
assert_eq!(found_host_path.unwrap().unwrap_on_host(), target_host_path);
133122

134123
// Tests directory traversal leading to valid symbolic link with an
@@ -154,11 +143,7 @@ fn test_uhyvefilemap_directory() {
154143
target_guest_path = PathBuf::from("/root/this_symlink_leads_to_a_file");
155144
target_host_path = fixture_path.clone();
156145
target_host_path.push("this_folder_exists/file_in_folder.txt");
157-
found_host_path = map.get_host_path(
158-
CString::new(target_guest_path.as_os_str().as_bytes())
159-
.unwrap()
160-
.as_c_str(),
161-
);
146+
found_host_path = map.get_host_path(target_guest_path.to_str().unwrap());
162147
assert_eq!(found_host_path.unwrap().unwrap_on_host(), target_host_path);
163148

164149
// Tests directory traversal with no maps
@@ -172,11 +157,7 @@ fn test_uhyvefilemap_directory() {
172157
sync: false,
173158
},
174159
);
175-
found_host_path = map.get_host_path(
176-
CString::new(target_guest_path.as_os_str().as_bytes())
177-
.unwrap()
178-
.as_c_str(),
179-
);
160+
found_host_path = map.get_host_path(target_guest_path.to_str().unwrap());
180161
assert!(found_host_path.is_none());
181162
}
182163

src/lib.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,6 @@ pub enum HypervisorError {
4949
#[error("Invalid kernel path ({0})")]
5050
InvalidKernelPath(PathBuf),
5151

52-
#[error("Path for {0} contained null bytes")]
53-
PathContainedNullBytes(&'static str),
54-
5552
#[error(transparent)]
5653
HermitImageError(#[from] crate::isolation::filemap::HermitImageError),
5754

src/vm.rs

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
use std::{
2-
env,
3-
ffi::CString,
4-
fmt, fs, io,
2+
env, fmt, fs, io,
53
mem::{drop, take},
64
num::NonZero,
75
os::unix::prelude::JoinHandleExt,
@@ -175,7 +173,7 @@ impl<VirtBackend: VirtualizationBackend> UhyveVm<VirtBackend> {
175173

176174
// TODO: make `hermit_entry::config::DEFAULT_CONFIG_NAME` public and use that here instead.
177175
let config_data = if let Some(UhyveTreeFile::Virtual(data)) =
178-
file_mapping.get_host_path(&CString::new("/hermit.toml").unwrap())
176+
file_mapping.get_host_path("/hermit.toml")
179177
{
180178
data
181179
} else {
@@ -249,9 +247,8 @@ impl<VirtBackend: VirtualizationBackend> UhyveVm<VirtBackend> {
249247

250248
// .kernel
251249
if let Some(UhyveTreeFile::Virtual(data)) =
252-
file_mapping.get_host_path(&CString::new(kernel.into_owned()).map_err(
253-
|_| HypervisorError::PathContainedNullBytes("hermit image kernel"),
254-
)?) {
250+
file_mapping.get_host_path(&kernel)
251+
{
255252
data.to_vec()
256253
} else {
257254
error!("Unable to find kernel in Hermit image");
@@ -324,8 +321,7 @@ impl<VirtBackend: VirtualizationBackend> UhyveVm<VirtBackend> {
324321
let mut mem = MmapMemory::new(memory_size, guest_address, false, false);
325322

326323
// TODO: file_mapping not in kernel_info
327-
let mut mounts: Vec<_> = file_mapping.get_all_guest_dirs().collect();
328-
mounts.dedup();
324+
let mounts: Vec<_> = file_mapping.get_all_guest_dirs().collect();
329325
let file_mapping = Mutex::new(file_mapping);
330326

331327
let serial = UhyveSerial::from_params(&params.output)?;
@@ -470,7 +466,7 @@ impl<VirtBackend: VirtualizationBackend> UhyveVm<VirtBackend> {
470466
file_sandbox_mode,
471467
kernel_path.into(),
472468
output,
473-
host_paths,
469+
host_paths.map(|i| i.as_os_str()),
474470
temp_dir,
475471
#[cfg(feature = "instrument")]
476472
trace,

0 commit comments

Comments
 (0)