1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 use log::warn;
18 use nix::sys::stat::Mode;
19 use std::collections::{hash_map, HashMap};
20 use std::ffi::{CString, OsString};
21 use std::io;
22 use std::os::unix::ffi::OsStringExt;
23 use std::path::{Path, PathBuf};
24 
25 use super::attr::Attr;
26 use super::remote_file::RemoteFileEditor;
27 use super::{validate_basename, VirtFdService, VirtFdServiceStatus};
28 use crate::fsverity::VerifiedFileEditor;
29 use crate::fusefs::{AuthFsDirEntry, Inode};
30 
31 const MAX_ENTRIES: u16 = 1000; // Arbitrary limit
32 
33 struct InodeInfo {
34     inode: Inode,
35 
36     // This information is duplicated since it is also available in `AuthFs::inode_table` via the
37     // type system. But it makes it simple to deal with deletion, where otherwise we need to get a
38     // mutable parent directory in the table, and query the table for directory/file type checking
39     // at the same time.
40     is_dir: bool,
41 }
42 
43 /// A remote directory backed by a remote directory FD, where the provider/fd_server is not
44 /// trusted.
45 ///
46 /// The directory is assumed empty initially without the trust to the storage. Functionally, when
47 /// the backing storage is not clean, the fd_server can fail to create a file or directory when
48 /// there is name collision. From RemoteDirEditor's perspective of security, the creation failure
49 /// is just one of possible errors that can happen, and what matters is RemoteDirEditor maintains
50 /// the integrity itself.
51 ///
52 /// When new files are created through RemoteDirEditor, the file integrity are maintained within the
53 /// VM. Similarly, integrity (namely the list of entries) of the directory, or new directories
54 /// created within such a directory, are also maintained within the VM. A compromised fd_server or
55 /// malicious client can't affect the view to the files and directories within such a directory in
56 /// the VM.
57 pub struct RemoteDirEditor {
58     service: VirtFdService,
59     remote_dir_fd: i32,
60 
61     /// Mapping of entry names to the corresponding inode. The actual file/directory is stored in
62     /// the global pool in fusefs.
63     entries: HashMap<PathBuf, InodeInfo>,
64 }
65 
66 impl RemoteDirEditor {
new(service: VirtFdService, remote_dir_fd: i32) -> Self67     pub fn new(service: VirtFdService, remote_dir_fd: i32) -> Self {
68         RemoteDirEditor { service, remote_dir_fd, entries: HashMap::new() }
69     }
70 
71     /// Returns the number of entries created.
number_of_entries(&self) -> u1672     pub fn number_of_entries(&self) -> u16 {
73         self.entries.len() as u16 // limited to MAX_ENTRIES
74     }
75 
76     /// Creates a remote file named `basename` with corresponding `inode` at the current directory.
create_file( &mut self, basename: &Path, inode: Inode, mode: libc::mode_t, ) -> io::Result<(VerifiedFileEditor<RemoteFileEditor>, Attr)>77     pub fn create_file(
78         &mut self,
79         basename: &Path,
80         inode: Inode,
81         mode: libc::mode_t,
82     ) -> io::Result<(VerifiedFileEditor<RemoteFileEditor>, Attr)> {
83         let mode = self.validate_arguments(basename, mode)?;
84         let basename_str =
85             basename.to_str().ok_or_else(|| io::Error::from_raw_os_error(libc::EINVAL))?;
86         let new_fd = self
87             .service
88             .createFileInDirectory(self.remote_dir_fd, basename_str, mode as i32)
89             .map_err(into_io_error)?;
90 
91         let new_remote_file =
92             VerifiedFileEditor::new(RemoteFileEditor::new(self.service.clone(), new_fd));
93         self.entries.insert(basename.to_path_buf(), InodeInfo { inode, is_dir: false });
94         let new_attr = Attr::new_file_with_mode(self.service.clone(), new_fd, mode);
95         Ok((new_remote_file, new_attr))
96     }
97 
98     /// Creates a remote directory named `basename` with corresponding `inode` at the current
99     /// directory.
mkdir( &mut self, basename: &Path, inode: Inode, mode: libc::mode_t, ) -> io::Result<(RemoteDirEditor, Attr)>100     pub fn mkdir(
101         &mut self,
102         basename: &Path,
103         inode: Inode,
104         mode: libc::mode_t,
105     ) -> io::Result<(RemoteDirEditor, Attr)> {
106         let mode = self.validate_arguments(basename, mode)?;
107         let basename_str =
108             basename.to_str().ok_or_else(|| io::Error::from_raw_os_error(libc::EINVAL))?;
109         let new_fd = self
110             .service
111             .createDirectoryInDirectory(self.remote_dir_fd, basename_str, mode as i32)
112             .map_err(into_io_error)?;
113 
114         let new_remote_dir = RemoteDirEditor::new(self.service.clone(), new_fd);
115         self.entries.insert(basename.to_path_buf(), InodeInfo { inode, is_dir: true });
116         let new_attr = Attr::new_dir_with_mode(self.service.clone(), new_fd, mode);
117         Ok((new_remote_dir, new_attr))
118     }
119 
120     /// Deletes a file
delete_file(&mut self, basename: &Path) -> io::Result<Inode>121     pub fn delete_file(&mut self, basename: &Path) -> io::Result<Inode> {
122         let inode = self.force_delete_entry(basename, /* expect_dir */ false)?;
123 
124         let basename_str =
125             basename.to_str().ok_or_else(|| io::Error::from_raw_os_error(libc::EINVAL))?;
126         if let Err(e) = self.service.deleteFile(self.remote_dir_fd, basename_str) {
127             // Ignore the error to honor the local state.
128             warn!("Deletion on the host is reportedly failed: {:?}", e);
129         }
130         Ok(inode)
131     }
132 
133     /// Forces to delete a directory. The caller must only call if `basename` is a directory and
134     /// empty.
force_delete_directory(&mut self, basename: &Path) -> io::Result<Inode>135     pub fn force_delete_directory(&mut self, basename: &Path) -> io::Result<Inode> {
136         let inode = self.force_delete_entry(basename, /* expect_dir */ true)?;
137 
138         let basename_str =
139             basename.to_str().ok_or_else(|| io::Error::from_raw_os_error(libc::EINVAL))?;
140         if let Err(e) = self.service.deleteDirectory(self.remote_dir_fd, basename_str) {
141             // Ignore the error to honor the local state.
142             warn!("Deletion on the host is reportedly failed: {:?}", e);
143         }
144         Ok(inode)
145     }
146 
147     /// Returns the inode number of a file or directory named `name` previously created through
148     /// `RemoteDirEditor`.
find_inode(&self, name: &Path) -> io::Result<Inode>149     pub fn find_inode(&self, name: &Path) -> io::Result<Inode> {
150         self.entries
151             .get(name)
152             .map(|entry| entry.inode)
153             .ok_or_else(|| io::Error::from_raw_os_error(libc::ENOENT))
154     }
155 
156     /// Returns whether the directory has an entry of the given name.
has_entry(&self, name: &Path) -> bool157     pub fn has_entry(&self, name: &Path) -> bool {
158         self.entries.contains_key(name)
159     }
160 
retrieve_entries(&self) -> io::Result<Vec<AuthFsDirEntry>>161     pub fn retrieve_entries(&self) -> io::Result<Vec<AuthFsDirEntry>> {
162         self.entries
163             .iter()
164             .map(|(name, InodeInfo { inode, is_dir })| {
165                 Ok(AuthFsDirEntry { inode: *inode, name: path_to_cstring(name)?, is_dir: *is_dir })
166             })
167             .collect::<io::Result<Vec<_>>>()
168     }
169 
force_delete_entry(&mut self, basename: &Path, expect_dir: bool) -> io::Result<Inode>170     fn force_delete_entry(&mut self, basename: &Path, expect_dir: bool) -> io::Result<Inode> {
171         // Kernel should only give us a basename.
172         debug_assert!(validate_basename(basename).is_ok());
173 
174         if let Some(entry) = self.entries.get(basename) {
175             match (expect_dir, entry.is_dir) {
176                 (true, false) => Err(io::Error::from_raw_os_error(libc::ENOTDIR)),
177                 (false, true) => Err(io::Error::from_raw_os_error(libc::EISDIR)),
178                 _ => {
179                     let inode = entry.inode;
180                     let _ = self.entries.remove(basename);
181                     Ok(inode)
182                 }
183             }
184         } else {
185             Err(io::Error::from_raw_os_error(libc::ENOENT))
186         }
187     }
188 
validate_arguments(&self, basename: &Path, mode: u32) -> io::Result<u32>189     fn validate_arguments(&self, basename: &Path, mode: u32) -> io::Result<u32> {
190         // Kernel should only give us a basename.
191         debug_assert!(validate_basename(basename).is_ok());
192 
193         if self.entries.contains_key(basename) {
194             return Err(io::Error::from_raw_os_error(libc::EEXIST));
195         }
196 
197         if self.entries.len() >= MAX_ENTRIES.into() {
198             return Err(io::Error::from_raw_os_error(libc::EMLINK));
199         }
200 
201         Ok(Mode::from_bits_truncate(mode).bits())
202     }
203 }
204 
205 /// An in-memory directory representation of a directory structure.
206 pub struct InMemoryDir(HashMap<PathBuf, InodeInfo>);
207 
208 impl InMemoryDir {
209     /// Creates an empty instance of `InMemoryDir`.
new() -> Self210     pub fn new() -> Self {
211         // Hash map is empty since "." and ".." are excluded in entries.
212         InMemoryDir(HashMap::new())
213     }
214 
215     /// Returns the number of entries in the directory (not including "." and "..").
number_of_entries(&self) -> u16216     pub fn number_of_entries(&self) -> u16 {
217         self.0.len() as u16 // limited to MAX_ENTRIES
218     }
219 
220     /// Adds a directory name and its inode number to the directory. Fails if already exists. The
221     /// caller is responsible for ensure the inode uniqueness.
add_dir(&mut self, basename: &Path, inode: Inode) -> io::Result<()>222     pub fn add_dir(&mut self, basename: &Path, inode: Inode) -> io::Result<()> {
223         self.add_entry(basename, InodeInfo { inode, is_dir: true })
224     }
225 
226     /// Adds a file name and its inode number to the directory. Fails if already exists. The
227     /// caller is responsible for ensure the inode uniqueness.
add_file(&mut self, basename: &Path, inode: Inode) -> io::Result<()>228     pub fn add_file(&mut self, basename: &Path, inode: Inode) -> io::Result<()> {
229         self.add_entry(basename, InodeInfo { inode, is_dir: false })
230     }
231 
add_entry(&mut self, basename: &Path, dir_entry: InodeInfo) -> io::Result<()>232     fn add_entry(&mut self, basename: &Path, dir_entry: InodeInfo) -> io::Result<()> {
233         validate_basename(basename)?;
234         if self.0.len() >= MAX_ENTRIES.into() {
235             return Err(io::Error::from_raw_os_error(libc::EMLINK));
236         }
237 
238         if let hash_map::Entry::Vacant(entry) = self.0.entry(basename.to_path_buf()) {
239             entry.insert(dir_entry);
240             Ok(())
241         } else {
242             Err(io::Error::from_raw_os_error(libc::EEXIST))
243         }
244     }
245 
246     /// Looks up an entry inode by name. `None` if not found.
lookup_inode(&self, basename: &Path) -> Option<Inode>247     pub fn lookup_inode(&self, basename: &Path) -> Option<Inode> {
248         self.0.get(basename).map(|entry| entry.inode)
249     }
250 
retrieve_entries(&self) -> io::Result<Vec<AuthFsDirEntry>>251     pub fn retrieve_entries(&self) -> io::Result<Vec<AuthFsDirEntry>> {
252         self.0
253             .iter()
254             .map(|(name, InodeInfo { inode, is_dir })| {
255                 Ok(AuthFsDirEntry { inode: *inode, name: path_to_cstring(name)?, is_dir: *is_dir })
256             })
257             .collect::<io::Result<Vec<_>>>()
258     }
259 }
260 
path_to_cstring(path: &Path) -> io::Result<CString>261 fn path_to_cstring(path: &Path) -> io::Result<CString> {
262     let bytes = OsString::from(path).into_vec();
263     CString::new(bytes).map_err(|_| io::Error::from_raw_os_error(libc::EILSEQ))
264 }
265 
into_io_error(e: VirtFdServiceStatus) -> io::Error266 fn into_io_error(e: VirtFdServiceStatus) -> io::Error {
267     let maybe_errno = e.service_specific_error();
268     if maybe_errno > 0 {
269         io::Error::from_raw_os_error(maybe_errno)
270     } else {
271         io::Error::new(io::ErrorKind::Other, e.get_description())
272     }
273 }
274