Skip to content

Commit ce3a8b5

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 b992025 commit ce3a8b5

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
@@ -175,19 +175,35 @@ fn translate_last_errno() -> Option<i32> {
175175
None
176176
}
177177

178+
/// Decodes a guest path given at address `path_addr` in `mem`.
179+
///
180+
/// # Safety
181+
///
182+
/// The calling convention of hypercalls ensures that the given address doesn't alias with anything mutable.
183+
/// The return value is only valid for the duration of the hypercall.
184+
unsafe fn decode_guest_path(mem: &MmapMemory, path_addr: GuestPhysAddr) -> Option<&str> {
185+
let requested_path_ptr = mem.host_address(path_addr).unwrap() as *const i8;
186+
unsafe { CStr::from_ptr(requested_path_ptr) }.to_str().ok()
187+
}
188+
178189
/// unlink deletes a name from the filesystem. This is used to handle `unlink` syscalls from the guest.
179190
///
180-
/// Note for when using Landlock: Unlinking files results in them being veiled. If a text
191+
/// Note for when using Landlock: Unlinking files results in them being veiled. If a
181192
/// file (that existed during initialization) called `log.txt` is unlinked, attempting to
182193
/// open `log.txt` again will result in an error.
183194
pub fn unlink(mem: &MmapMemory, sysunlink: &mut UnlinkParams, file_map: &mut UhyveFileMap) {
184-
let requested_path_ptr = mem.host_address(sysunlink.name).unwrap() as *const i8;
185-
let guest_path = unsafe { CStr::from_ptr(requested_path_ptr) };
195+
let guest_path = if let Some(guest_path) = unsafe { decode_guest_path(mem, sysunlink.name) } {
196+
guest_path
197+
} else {
198+
error!("The kernel requested to unlink() an non-UTF8 path: Rejecting...");
199+
sysunlink.ret = -EINVAL;
200+
return;
201+
};
186202
sysunlink.ret = match file_map.unlink(guest_path) {
187203
Ok(Some(host_path)) => {
188204
// We can safely unwrap here, as host_path.as_bytes will never contain internal \0 bytes
189205
// As host_path_c_string is a valid CString, this implementation is presumed to be safe.
190-
let host_path_c_string = CString::new(host_path.as_bytes()).unwrap();
206+
let host_path_c_string = CString::new(host_path.as_os_str().as_bytes()).unwrap();
191207
if unsafe { libc::unlink(host_path_c_string.as_c_str().as_ptr()) } < 0 {
192208
-translate_last_errno().unwrap_or(1)
193209
} else {
@@ -209,9 +225,14 @@ pub fn unlink(mem: &MmapMemory, sysunlink: &mut UnlinkParams, file_map: &mut Uhy
209225

210226
/// Handles an open syscall by opening a file on the host.
211227
pub fn open(mem: &MmapMemory, sysopen: &mut OpenParams, file_map: &mut UhyveFileMap) {
212-
let requested_path_ptr = mem.host_address(sysopen.name).unwrap() as *const i8;
228+
let guest_path = if let Some(guest_path) = unsafe { decode_guest_path(mem, sysopen.name) } {
229+
guest_path
230+
} else {
231+
error!("The kernel requested to open() an non-UTF8 path: Rejecting...");
232+
sysopen.ret = -EINVAL;
233+
return;
234+
};
213235
let mut flags = sysopen.flags & ALLOWED_OPEN_FLAGS;
214-
let guest_path = unsafe { CStr::from_ptr(requested_path_ptr) };
215236
// See: https://lwn.net/Articles/926782/
216237
// See: https://github.com/hermit-os/kernel/commit/71bc629
217238
if (flags & (O_DIRECTORY | O_CREAT)) == (O_DIRECTORY | O_CREAT) {
@@ -294,10 +315,7 @@ pub fn open(mem: &MmapMemory, sysopen: &mut OpenParams, file_map: &mut UhyveFile
294315

295316
/// Handles an close syscall by closing the file on the host.
296317
pub fn close(sysclose: &mut CloseParams, file_map: &mut UhyveFileMap) {
297-
sysclose.ret = if file_map
298-
.fdmap
299-
.is_fd_present(GuestFd(sysclose.fd.into_raw_fd()))
300-
{
318+
sysclose.ret = if file_map.fdmap.is_fd_present(GuestFd(sysclose.fd)) {
301319
match file_map.fdmap.remove(GuestFd(sysclose.fd)) {
302320
Some(FdData::Raw(fd)) => {
303321
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)