From 494039e2c7fc50b316513d64c895a0a8f33c300f Mon Sep 17 00:00:00 2001 From: "Panagiotis \"Ivory\" Vasilopoulos" Date: Tue, 26 Nov 2024 09:16:29 +0100 Subject: [PATCH] feat(isolation): add directory support for UhyveFileMap If the guest path is part of a directory that is not mapped, we'll recursively look for the parent directories and check if the parent directories are mapped. If that's the case, we'll use the file in the mapped host directory instead. This feature also comes with a unit test. We partially rely on PathBuf to prevent any funny behavior from taking place. A current flaw of this approach is that the filename that can be found in the OpenParams struct is "relative" instead of absolute, and providing paths that are otherwise whitelisted (e.g. "/root/file.txt" instead of "file.txt") will result in the map not return a result. --- src/isolation.rs | 110 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 109 insertions(+), 1 deletion(-) diff --git a/src/isolation.rs b/src/isolation.rs index c418e9d8..bdc44c7f 100644 --- a/src/isolation.rs +++ b/src/isolation.rs @@ -75,7 +75,44 @@ impl UhyveFileMap { /// /// * `guest_path` - The guest path that is to be looked up in the map. pub fn get_host_path(&mut self, guest_path: &str) -> Option { - self.files.get(guest_path).map(OsString::from) + let host_path = self.files.get(guest_path).map(OsString::from); + if host_path.is_some() { + host_path + } else { + info!("Guest requested to open a path that was not mapped."); + if self.files.is_empty() { + info!("UhyveFileMap is empty, returning None..."); + return None; + } + + let requested_guest_pathbuf = PathBuf::from(guest_path); + if let Some(parent_of_guest_path) = requested_guest_pathbuf.parent() { + info!("The file is in a child directory, searching for a parent directory..."); + let ancestors = parent_of_guest_path.ancestors(); + for searched_parent_guest in ancestors { + // If one of the guest paths' parent directories (parent_host) is mapped, + // use the mapped host path and push the "remainder" (the path's components + // that come after the mapped guest path) onto the host path. + let parent_host: Option<&OsString> = + self.files.get(searched_parent_guest.to_str().unwrap()); + if let Some(parent_host) = parent_host { + let mut host_path = PathBuf::from(parent_host); + let guest_path_remainder = requested_guest_pathbuf + .strip_prefix(searched_parent_guest) + .unwrap(); + + host_path.push(guest_path_remainder); + + // Handles symbolic links. + return fs::canonicalize(&host_path) + .map_or(host_path.into_os_string(), PathBuf::into_os_string) + .into(); + } + } + } + info!("The file is not in a child directory, returning None..."); + None + } } /// Inserts an opened temporary file into the file map. Returns a CString so that @@ -197,4 +234,75 @@ mod tests { assert!(map.get_host_path("this_file_is_not_mapped").is_none()); } + + #[test] + fn test_uhyvefilemap_directory() { + let mut fixture_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + fixture_path.push("tests/data/fixtures/fs"); + assert!(fixture_path.is_dir()); + + // Tests successful directory traversal starting from file in child + // directory of a mapped directory. + let mut guest_path_map = PathBuf::from("this_folder_exists"); + let mut host_path_map = fixture_path.clone(); + host_path_map.push("this_folder_exists"); + + let mut target_guest_path = + PathBuf::from("this_folder_exists/folder_in_folder/file_in_second_folder.txt"); + let mut target_host_path = fixture_path.clone(); + target_host_path.push(target_guest_path.clone()); + + let mut uhyvefilemap_params = [format!( + "{}:{}", + host_path_map.to_str().unwrap(), + guest_path_map.to_str().unwrap() + )]; + let mut map = UhyveFileMap::new(&uhyvefilemap_params); + + let mut found_host_path = map.get_host_path(target_guest_path.clone().to_str().unwrap()); + + assert_eq!( + found_host_path.unwrap(), + target_host_path.as_os_str().to_str().unwrap() + ); + + // Tests successful directory traversal of the child directory. + // The pop() just removes the text file. + // guest_path.pop(); + target_host_path.pop(); + target_guest_path.pop(); + + found_host_path = map.get_host_path(target_guest_path.to_str().unwrap()); + assert_eq!( + found_host_path.unwrap(), + target_host_path.as_os_str().to_str().unwrap() + ); + + // Tests directory traversal leading to valid symbolic link with an + // empty guest_path_map. + host_path_map = fixture_path.clone(); + guest_path_map = PathBuf::from(""); + uhyvefilemap_params = [format!( + "{}:{}", + host_path_map.to_str().unwrap(), + guest_path_map.to_str().unwrap() + )]; + + map = UhyveFileMap::new(&uhyvefilemap_params); + + target_guest_path = PathBuf::from("this_symlink_leads_to_a_file"); + target_host_path = fixture_path.clone(); + target_host_path.push("this_folder_exists/file_in_folder.txt"); + found_host_path = map.get_host_path(target_guest_path.to_str().unwrap()); + assert_eq!( + found_host_path.unwrap(), + target_host_path.as_os_str().to_str().unwrap() + ); + + // Tests directory traversal with no maps + let empty_array: [String; 0] = []; + map = UhyveFileMap::new(&empty_array); + found_host_path = map.get_host_path(target_guest_path.to_str().unwrap()); + assert!(found_host_path.is_none()); + } }