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