blob: 0fa827f95e35d8fd70ef455a4efbf151f1285b56 [file] [log] [blame]
Victor Hsieh88ac6ca2020-11-13 15:20:24 -08001/*
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
17use anyhow::Result;
Victor Hsieh45636232021-10-15 17:52:51 -070018use log::{debug, error, warn};
19use std::collections::{btree_map, BTreeMap};
Victor Hsieh88ac6ca2020-11-13 15:20:24 -080020use std::convert::TryFrom;
Victor Hsieh45636232021-10-15 17:52:51 -070021use std::ffi::{CStr, OsStr};
Victor Hsieh88ac6ca2020-11-13 15:20:24 -080022use std::fs::OpenOptions;
23use std::io;
24use std::mem::MaybeUninit;
25use std::option::Option;
Victor Hsieh45636232021-10-15 17:52:51 -070026use std::os::unix::{ffi::OsStrExt, io::AsRawFd};
Victor Hsieh88ac6ca2020-11-13 15:20:24 -080027use std::path::Path;
Victor Hsieh45636232021-10-15 17:52:51 -070028use std::sync::{Arc, Mutex};
Victor Hsieh88ac6ca2020-11-13 15:20:24 -080029use std::time::Duration;
30
Victor Hsieh6a47e7f2021-03-03 15:53:49 -080031use fuse::filesystem::{
Victor Hsieh71f10032021-08-13 11:24:02 -070032 Context, DirEntry, DirectoryIterator, Entry, FileSystem, FsOptions, GetxattrReply,
33 SetattrValid, ZeroCopyReader, ZeroCopyWriter,
Victor Hsieh6a47e7f2021-03-03 15:53:49 -080034};
Victor Hsieh88ac6ca2020-11-13 15:20:24 -080035use fuse::mount::MountOption;
36
Victor Hsiehac4f3f42021-02-26 12:35:58 -080037use crate::common::{divide_roundup, ChunkedSizeIter, CHUNK_SIZE};
Victor Hsieh6a47e7f2021-03-03 15:53:49 -080038use crate::file::{
Victor Hsieh45636232021-10-15 17:52:51 -070039 RandomWrite, ReadByChunk, RemoteDirEditor, RemoteFileEditor, RemoteFileReader,
40 RemoteMerkleTreeReader,
Victor Hsieh6a47e7f2021-03-03 15:53:49 -080041};
42use crate::fsverity::{VerifiedFileEditor, VerifiedFileReader};
Victor Hsieh88ac6ca2020-11-13 15:20:24 -080043
Victor Hsieh88ac6ca2020-11-13 15:20:24 -080044const DEFAULT_METADATA_TIMEOUT: std::time::Duration = Duration::from_secs(5);
45
46pub type Inode = u64;
47type Handle = u64;
48
Victor Hsieh1bcf4112021-03-19 14:26:57 -070049/// `FileConfig` defines the file type supported by AuthFS.
Victor Hsieh88ac6ca2020-11-13 15:20:24 -080050pub enum FileConfig {
Victor Hsieh1bcf4112021-03-19 14:26:57 -070051 /// A file type that is verified against fs-verity signature (thus read-only). The file is
Victor Hsieh1bcf4112021-03-19 14:26:57 -070052 /// served from a remote server.
Victor Hsieh88e50172021-10-15 13:27:13 -070053 VerifiedReadonly {
Victor Hsieh1bcf4112021-03-19 14:26:57 -070054 reader: VerifiedFileReader<RemoteFileReader, RemoteMerkleTreeReader>,
55 file_size: u64,
56 },
57 /// A file type that is a read-only passthrough from a file on a remote serrver.
Victor Hsieh88e50172021-10-15 13:27:13 -070058 UnverifiedReadonly { reader: RemoteFileReader, file_size: u64 },
Victor Hsieh1bcf4112021-03-19 14:26:57 -070059 /// A file type that is initially empty, and the content is stored on a remote server. File
60 /// integrity is guaranteed with private Merkle tree.
Victor Hsieh88e50172021-10-15 13:27:13 -070061 VerifiedNew { editor: VerifiedFileEditor<RemoteFileEditor> },
Victor Hsieh45636232021-10-15 17:52:51 -070062 /// A directory type that is initially empty. One can create new file (`VerifiedNew`) and new
63 /// directory (`VerifiedNewDirectory` itself) with integrity guaranteed within the VM.
64 VerifiedNewDirectory { dir: RemoteDirEditor },
Victor Hsieh88ac6ca2020-11-13 15:20:24 -080065}
66
67struct AuthFs {
68 /// Store `FileConfig`s using the `Inode` number as the search index.
69 ///
70 /// For further optimization to minimize the search cost, since Inode is integer, we may
71 /// consider storing them in a Vec if we can guarantee that the numbers are small and
72 /// consecutive.
Victor Hsieh45636232021-10-15 17:52:51 -070073 file_pool: Arc<Mutex<BTreeMap<Inode, FileConfig>>>,
Victor Hsieh88ac6ca2020-11-13 15:20:24 -080074
75 /// Maximum bytes in the write transaction to the FUSE device. This limits the maximum size to
76 /// a read request (including FUSE protocol overhead).
77 max_write: u32,
78}
79
80impl AuthFs {
Victor Hsieh45636232021-10-15 17:52:51 -070081 pub fn new(file_pool: Arc<Mutex<BTreeMap<Inode, FileConfig>>>, max_write: u32) -> AuthFs {
Victor Hsieh88ac6ca2020-11-13 15:20:24 -080082 AuthFs { file_pool, max_write }
83 }
84
Victor Hsieh45636232021-10-15 17:52:51 -070085 /// Handles the file associated with `inode` if found. This function returns whatever
86 /// `handle_fn` returns.
87 fn handle_file<F, R>(&self, inode: &Inode, handle_fn: F) -> io::Result<R>
Victor Hsiehc85e4ef2021-10-18 15:28:53 -070088 where
89 F: FnOnce(&FileConfig) -> io::Result<R>,
90 {
Victor Hsieh45636232021-10-15 17:52:51 -070091 let file_pool = self.file_pool.lock().unwrap();
Victor Hsiehc85e4ef2021-10-18 15:28:53 -070092 let config =
Victor Hsieh45636232021-10-15 17:52:51 -070093 file_pool.get(inode).ok_or_else(|| io::Error::from_raw_os_error(libc::ENOENT))?;
94 handle_fn(config)
95 }
96
97 /// Inserts a new inode and corresponding `FileConfig` created by `create_fn` to the file pool,
98 /// then returns the new inode number.
99 fn insert_new_inode<F>(&self, inode: &Inode, create_fn: F) -> io::Result<Inode>
100 where
101 F: FnOnce(&mut FileConfig) -> io::Result<(Inode, FileConfig)>,
102 {
103 let mut file_pool = self.file_pool.lock().unwrap();
104 let mut config =
105 file_pool.get_mut(inode).ok_or_else(|| io::Error::from_raw_os_error(libc::ENOENT))?;
106 let (new_inode, new_file_config) = create_fn(&mut config)?;
107 if let btree_map::Entry::Vacant(entry) = file_pool.entry(new_inode) {
108 entry.insert(new_file_config);
109 Ok(new_inode)
110 } else {
111 // We can't assume fd_server is trusted, so the returned FD may collide with existing
112 // one, even when we are creating a new file. Do not override an existing FD. In terms
113 // of security, it is better to "leak" the file created earlier, than returning an
114 // existing inode as a new file.
115 error!("Inode {} already exists, do not override", new_inode);
116 Err(io::Error::from_raw_os_error(libc::EIO))
117 }
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800118 }
119}
120
121fn check_access_mode(flags: u32, mode: libc::c_int) -> io::Result<()> {
122 if (flags & libc::O_ACCMODE as u32) == mode as u32 {
123 Ok(())
124 } else {
125 Err(io::Error::from_raw_os_error(libc::EACCES))
126 }
127}
128
129cfg_if::cfg_if! {
130 if #[cfg(all(target_arch = "aarch64", target_pointer_width = "64"))] {
Victor Hsiehda3fbc42021-02-23 16:12:49 -0800131 fn blk_size() -> libc::c_int { CHUNK_SIZE as libc::c_int }
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800132 } else {
Victor Hsiehda3fbc42021-02-23 16:12:49 -0800133 fn blk_size() -> libc::c_long { CHUNK_SIZE as libc::c_long }
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800134 }
135}
136
Victor Hsieh45636232021-10-15 17:52:51 -0700137#[allow(clippy::enum_variant_names)]
138enum AccessMode {
Victor Hsieh6a47e7f2021-03-03 15:53:49 -0800139 ReadOnly,
140 ReadWrite,
141}
142
Victor Hsieh45636232021-10-15 17:52:51 -0700143fn create_stat(
144 ino: libc::ino_t,
145 file_size: u64,
146 access_mode: AccessMode,
147) -> io::Result<libc::stat64> {
148 // SAFETY: stat64 is a plan C struct without pointer.
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800149 let mut st = unsafe { MaybeUninit::<libc::stat64>::zeroed().assume_init() };
150
151 st.st_ino = ino;
Victor Hsieh45636232021-10-15 17:52:51 -0700152 st.st_mode = match access_mode {
Victor Hsieh6a47e7f2021-03-03 15:53:49 -0800153 // Until needed, let's just grant the owner access.
Victor Hsieh45636232021-10-15 17:52:51 -0700154 // TODO(205169366): Implement mode properly.
155 AccessMode::ReadOnly => libc::S_IFREG | libc::S_IRUSR,
156 AccessMode::ReadWrite => libc::S_IFREG | libc::S_IRUSR | libc::S_IWUSR,
Victor Hsieh6a47e7f2021-03-03 15:53:49 -0800157 };
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800158 st.st_nlink = 1;
159 st.st_uid = 0;
160 st.st_gid = 0;
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800161 st.st_size = libc::off64_t::try_from(file_size)
162 .map_err(|_| io::Error::from_raw_os_error(libc::EFBIG))?;
163 st.st_blksize = blk_size();
164 // Per man stat(2), st_blocks is "Number of 512B blocks allocated".
165 st.st_blocks = libc::c_longlong::try_from(divide_roundup(file_size, 512))
166 .map_err(|_| io::Error::from_raw_os_error(libc::EFBIG))?;
167 Ok(st)
168}
169
Victor Hsieh45636232021-10-15 17:52:51 -0700170fn create_dir_stat(ino: libc::ino_t, file_number: u16) -> io::Result<libc::stat64> {
171 // SAFETY: stat64 is a plan C struct without pointer.
172 let mut st = unsafe { MaybeUninit::<libc::stat64>::zeroed().assume_init() };
173
174 st.st_ino = ino;
175 // TODO(205169366): Implement mode properly.
176 st.st_mode = libc::S_IFDIR
177 | libc::S_IXUSR
178 | libc::S_IWUSR
179 | libc::S_IRUSR
180 | libc::S_IXGRP
181 | libc::S_IXOTH;
182
183 // 2 extra for . and ..
184 st.st_nlink = file_number
185 .checked_add(2)
186 .ok_or_else(|| io::Error::from_raw_os_error(libc::EOVERFLOW))?
187 .into();
188
189 st.st_uid = 0;
190 st.st_gid = 0;
191 Ok(st)
192}
193
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800194fn offset_to_chunk_index(offset: u64) -> u64 {
Victor Hsiehda3fbc42021-02-23 16:12:49 -0800195 offset / CHUNK_SIZE
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800196}
197
Victor Hsiehd0bb5d32021-03-19 12:48:03 -0700198fn read_chunks<W: io::Write, T: ReadByChunk>(
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800199 mut w: W,
200 file: &T,
201 file_size: u64,
202 offset: u64,
203 size: u32,
204) -> io::Result<usize> {
205 let remaining = file_size.saturating_sub(offset);
206 let size_to_read = std::cmp::min(size as usize, remaining as usize);
Victor Hsiehac4f3f42021-02-26 12:35:58 -0800207 let total = ChunkedSizeIter::new(size_to_read, offset, CHUNK_SIZE as usize).try_fold(
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800208 0,
209 |total, (current_offset, planned_data_size)| {
210 // TODO(victorhsieh): There might be a non-trivial way to avoid this copy. For example,
211 // instead of accepting a buffer, the writer could expose the final destination buffer
212 // for the reader to write to. It might not be generally applicable though, e.g. with
213 // virtio transport, the buffer may not be continuous.
Victor Hsiehda3fbc42021-02-23 16:12:49 -0800214 let mut buf = [0u8; CHUNK_SIZE as usize];
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800215 let read_size = file.read_chunk(offset_to_chunk_index(current_offset), &mut buf)?;
216 if read_size < planned_data_size {
217 return Err(io::Error::from_raw_os_error(libc::ENODATA));
218 }
219
Victor Hsiehda3fbc42021-02-23 16:12:49 -0800220 let begin = (current_offset % CHUNK_SIZE) as usize;
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800221 let end = begin + planned_data_size;
222 let s = w.write(&buf[begin..end])?;
223 if s != planned_data_size {
224 return Err(io::Error::from_raw_os_error(libc::EIO));
225 }
226 Ok(total + s)
227 },
228 )?;
229
230 Ok(total)
231}
232
233// No need to support enumerating directory entries.
234struct EmptyDirectoryIterator {}
235
236impl DirectoryIterator for EmptyDirectoryIterator {
237 fn next(&mut self) -> Option<DirEntry> {
238 None
239 }
240}
241
242impl FileSystem for AuthFs {
243 type Inode = Inode;
244 type Handle = Handle;
245 type DirIter = EmptyDirectoryIterator;
246
247 fn max_buffer_size(&self) -> u32 {
248 self.max_write
249 }
250
Victor Hsieh6a47e7f2021-03-03 15:53:49 -0800251 fn init(&self, _capable: FsOptions) -> io::Result<FsOptions> {
252 // Enable writeback cache for better performance especially since our bandwidth to the
253 // backend service is limited.
254 Ok(FsOptions::WRITEBACK_CACHE)
255 }
256
Victor Hsieh45636232021-10-15 17:52:51 -0700257 fn lookup(&self, _ctx: Context, parent: Inode, name: &CStr) -> io::Result<Entry> {
258 // TODO(victorhsieh): convert root directory (inode == 1) to a readonly directory. Right
259 // now, it's the (global) inode pool, so all inodes can be accessed from root.
260 if parent == 1 {
261 // Only accept file name that looks like an integrer. Files in the pool are simply
262 // exposed by their inode number. Also, there is currently no directory structure.
263 let num = name.to_str().map_err(|_| io::Error::from_raw_os_error(libc::EINVAL))?;
264 // Normally, `lookup` is required to increase a reference count for the inode (while
265 // `forget` will decrease it). It is not necessary here since the files are configured
266 // to be static.
267 let inode =
268 num.parse::<Inode>().map_err(|_| io::Error::from_raw_os_error(libc::ENOENT))?;
269 let st = self.handle_file(&inode, |config| match config {
270 FileConfig::UnverifiedReadonly { file_size, .. }
271 | FileConfig::VerifiedReadonly { file_size, .. } => {
272 create_stat(inode, *file_size, AccessMode::ReadOnly)
273 }
274 FileConfig::VerifiedNew { editor } => {
275 create_stat(inode, editor.size(), AccessMode::ReadWrite)
276 }
277 FileConfig::VerifiedNewDirectory { dir } => {
278 create_dir_stat(inode, dir.number_of_entries())
279 }
280 })?;
281 Ok(Entry {
282 inode,
283 generation: 0,
284 attr: st,
285 entry_timeout: DEFAULT_METADATA_TIMEOUT,
286 attr_timeout: DEFAULT_METADATA_TIMEOUT,
287 })
288 } else {
289 let inode = self.handle_file(&parent, |config| match config {
290 FileConfig::VerifiedNewDirectory { dir } => {
291 let path: &Path = cstr_to_path(name);
292 dir.find_inode(path).ok_or_else(|| io::Error::from_raw_os_error(libc::ENOENT))
293 }
294 _ => Err(io::Error::from_raw_os_error(libc::ENOTDIR)),
295 })?;
296 let st = self.handle_file(&inode, |config| match config {
297 FileConfig::VerifiedNew { editor } => {
298 create_stat(inode, editor.size(), AccessMode::ReadWrite)
299 }
300 FileConfig::VerifiedNewDirectory { dir } => {
301 create_dir_stat(inode, dir.number_of_entries())
302 }
303 _ => Err(io::Error::from_raw_os_error(libc::EBADF)),
304 })?;
305 Ok(Entry {
306 inode,
307 generation: 0,
308 attr: st,
309 entry_timeout: DEFAULT_METADATA_TIMEOUT,
310 attr_timeout: DEFAULT_METADATA_TIMEOUT,
311 })
312 }
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800313 }
314
315 fn getattr(
316 &self,
317 _ctx: Context,
318 inode: Inode,
319 _handle: Option<Handle>,
320 ) -> io::Result<(libc::stat64, Duration)> {
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700321 self.handle_file(&inode, |config| {
322 Ok((
323 match config {
324 FileConfig::UnverifiedReadonly { file_size, .. }
325 | FileConfig::VerifiedReadonly { file_size, .. } => {
Victor Hsieh45636232021-10-15 17:52:51 -0700326 create_stat(inode, *file_size, AccessMode::ReadOnly)?
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700327 }
328 FileConfig::VerifiedNew { editor } => {
Victor Hsieh45636232021-10-15 17:52:51 -0700329 create_stat(inode, editor.size(), AccessMode::ReadWrite)?
330 }
331 FileConfig::VerifiedNewDirectory { dir } => {
332 create_dir_stat(inode, dir.number_of_entries())?
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700333 }
334 },
335 DEFAULT_METADATA_TIMEOUT,
336 ))
337 })
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800338 }
339
340 fn open(
341 &self,
342 _ctx: Context,
343 inode: Self::Inode,
344 flags: u32,
345 ) -> io::Result<(Option<Self::Handle>, fuse::sys::OpenOptions)> {
346 // Since file handle is not really used in later operations (which use Inode directly),
Victor Hsieh09e26262021-03-03 16:00:55 -0800347 // return None as the handle.
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700348 self.handle_file(&inode, |config| {
349 match config {
350 FileConfig::VerifiedReadonly { .. } | FileConfig::UnverifiedReadonly { .. } => {
351 check_access_mode(flags, libc::O_RDONLY)?;
352 }
353 FileConfig::VerifiedNew { .. } => {
354 // No need to check access modes since all the modes are allowed to the
355 // read-writable file.
356 }
Victor Hsieh45636232021-10-15 17:52:51 -0700357 FileConfig::VerifiedNewDirectory { .. } => {
358 // TODO(victorhsieh): implement when needed.
359 return Err(io::Error::from_raw_os_error(libc::ENOSYS));
360 }
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800361 }
Victor Hsieh45636232021-10-15 17:52:51 -0700362 // Always cache the file content. There is currently no need to support direct I/O or
363 // avoid the cache buffer. Memory mapping is only possible with cache enabled.
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700364 Ok((None, fuse::sys::OpenOptions::KEEP_CACHE))
365 })
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800366 }
367
Victor Hsieh45636232021-10-15 17:52:51 -0700368 fn create(
369 &self,
370 _ctx: Context,
371 parent: Self::Inode,
372 name: &CStr,
373 _mode: u32,
374 _flags: u32,
375 _umask: u32,
376 ) -> io::Result<(Entry, Option<Self::Handle>, fuse::sys::OpenOptions)> {
377 // TODO(205169366): Implement mode properly.
378 // TODO(205172873): handle O_TRUNC and O_EXCL properly.
379 let new_inode = self.insert_new_inode(&parent, |config| match config {
380 FileConfig::VerifiedNewDirectory { dir } => {
381 let basename: &Path = cstr_to_path(name);
382 if dir.find_inode(basename).is_some() {
383 return Err(io::Error::from_raw_os_error(libc::EEXIST));
384 }
385 let (new_inode, new_file) = dir.create_file(basename)?;
386 Ok((new_inode, FileConfig::VerifiedNew { editor: new_file }))
387 }
388 _ => Err(io::Error::from_raw_os_error(libc::EBADF)),
389 })?;
390
391 Ok((
392 Entry {
393 inode: new_inode,
394 generation: 0,
395 attr: create_stat(new_inode, /* file_size */ 0, AccessMode::ReadWrite)?,
396 entry_timeout: DEFAULT_METADATA_TIMEOUT,
397 attr_timeout: DEFAULT_METADATA_TIMEOUT,
398 },
399 // See also `open`.
400 /* handle */ None,
401 fuse::sys::OpenOptions::KEEP_CACHE,
402 ))
403 }
404
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800405 fn read<W: io::Write + ZeroCopyWriter>(
406 &self,
407 _ctx: Context,
408 inode: Inode,
409 _handle: Handle,
410 w: W,
411 size: u32,
412 offset: u64,
413 _lock_owner: Option<u64>,
414 _flags: u32,
415 ) -> io::Result<usize> {
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700416 self.handle_file(&inode, |config| {
417 match config {
418 FileConfig::VerifiedReadonly { reader, file_size } => {
419 read_chunks(w, reader, *file_size, offset, size)
420 }
421 FileConfig::UnverifiedReadonly { reader, file_size } => {
422 read_chunks(w, reader, *file_size, offset, size)
423 }
424 FileConfig::VerifiedNew { editor } => {
425 // Note that with FsOptions::WRITEBACK_CACHE, it's possible for the kernel to
426 // request a read even if the file is open with O_WRONLY.
427 read_chunks(w, editor, editor.size(), offset, size)
428 }
Victor Hsieh45636232021-10-15 17:52:51 -0700429 _ => Err(io::Error::from_raw_os_error(libc::EBADF)),
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800430 }
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700431 })
Victor Hsieh6a47e7f2021-03-03 15:53:49 -0800432 }
433
434 fn write<R: io::Read + ZeroCopyReader>(
435 &self,
436 _ctx: Context,
437 inode: Self::Inode,
438 _handle: Self::Handle,
439 mut r: R,
440 size: u32,
441 offset: u64,
442 _lock_owner: Option<u64>,
443 _delayed_write: bool,
444 _flags: u32,
445 ) -> io::Result<usize> {
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700446 self.handle_file(&inode, |config| match config {
Victor Hsieh88e50172021-10-15 13:27:13 -0700447 FileConfig::VerifiedNew { editor } => {
Victor Hsieh6a47e7f2021-03-03 15:53:49 -0800448 let mut buf = vec![0; size as usize];
449 r.read_exact(&mut buf)?;
Victor Hsieh1bcf4112021-03-19 14:26:57 -0700450 editor.write_at(&buf, offset)
Victor Hsieh6a47e7f2021-03-03 15:53:49 -0800451 }
452 _ => Err(io::Error::from_raw_os_error(libc::EBADF)),
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700453 })
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800454 }
Victor Hsieh9d0ab622021-04-26 17:07:02 -0700455
456 fn setattr(
457 &self,
458 _ctx: Context,
459 inode: Inode,
460 attr: libc::stat64,
461 _handle: Option<Handle>,
462 valid: SetattrValid,
463 ) -> io::Result<(libc::stat64, Duration)> {
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700464 self.handle_file(&inode, |config| {
465 match config {
466 FileConfig::VerifiedNew { editor } => {
467 // Initialize the default stat.
Victor Hsieh45636232021-10-15 17:52:51 -0700468 let mut new_attr = create_stat(inode, editor.size(), AccessMode::ReadWrite)?;
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700469 // `valid` indicates what fields in `attr` are valid. Update to return correctly.
470 if valid.contains(SetattrValid::SIZE) {
471 // st_size is i64, but the cast should be safe since kernel should not give a
472 // negative size.
473 debug_assert!(attr.st_size >= 0);
474 new_attr.st_size = attr.st_size;
475 editor.resize(attr.st_size as u64)?;
476 }
Victor Hsieh9d0ab622021-04-26 17:07:02 -0700477
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700478 if valid.contains(SetattrValid::MODE) {
479 warn!("Changing st_mode is not currently supported");
480 return Err(io::Error::from_raw_os_error(libc::ENOSYS));
481 }
482 if valid.contains(SetattrValid::UID) {
483 warn!("Changing st_uid is not currently supported");
484 return Err(io::Error::from_raw_os_error(libc::ENOSYS));
485 }
486 if valid.contains(SetattrValid::GID) {
487 warn!("Changing st_gid is not currently supported");
488 return Err(io::Error::from_raw_os_error(libc::ENOSYS));
489 }
490 if valid.contains(SetattrValid::CTIME) {
491 debug!(
492 "Ignoring ctime change as authfs does not maintain timestamp currently"
493 );
494 }
495 if valid.intersects(SetattrValid::ATIME | SetattrValid::ATIME_NOW) {
496 debug!(
497 "Ignoring atime change as authfs does not maintain timestamp currently"
498 );
499 }
500 if valid.intersects(SetattrValid::MTIME | SetattrValid::MTIME_NOW) {
501 debug!(
502 "Ignoring mtime change as authfs does not maintain timestamp currently"
503 );
504 }
505 Ok((new_attr, DEFAULT_METADATA_TIMEOUT))
Victor Hsieh9d0ab622021-04-26 17:07:02 -0700506 }
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700507 _ => Err(io::Error::from_raw_os_error(libc::EBADF)),
Victor Hsieh9d0ab622021-04-26 17:07:02 -0700508 }
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700509 })
Victor Hsieh9d0ab622021-04-26 17:07:02 -0700510 }
Victor Hsieh71f10032021-08-13 11:24:02 -0700511
512 fn getxattr(
513 &self,
514 _ctx: Context,
515 inode: Self::Inode,
516 name: &CStr,
517 size: u32,
518 ) -> io::Result<GetxattrReply> {
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700519 self.handle_file(&inode, |config| {
520 match config {
521 FileConfig::VerifiedNew { editor } => {
522 // FUSE ioctl is limited, thus we can't implement fs-verity ioctls without a kernel
523 // change (see b/196635431). Until it's possible, use xattr to expose what we need
524 // as an authfs specific API.
525 if name != CStr::from_bytes_with_nul(b"authfs.fsverity.digest\0").unwrap() {
526 return Err(io::Error::from_raw_os_error(libc::ENODATA));
527 }
Victor Hsieh71f10032021-08-13 11:24:02 -0700528
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700529 if size == 0 {
530 // Per protocol, when size is 0, return the value size.
531 Ok(GetxattrReply::Count(editor.get_fsverity_digest_size() as u32))
Victor Hsieh71f10032021-08-13 11:24:02 -0700532 } else {
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700533 let digest = editor.calculate_fsverity_digest()?;
534 if digest.len() > size as usize {
535 Err(io::Error::from_raw_os_error(libc::ERANGE))
536 } else {
537 Ok(GetxattrReply::Value(digest.to_vec()))
538 }
Victor Hsieh71f10032021-08-13 11:24:02 -0700539 }
540 }
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700541 _ => Err(io::Error::from_raw_os_error(libc::ENODATA)),
Victor Hsieh71f10032021-08-13 11:24:02 -0700542 }
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700543 })
Victor Hsieh71f10032021-08-13 11:24:02 -0700544 }
Victor Hsieh45636232021-10-15 17:52:51 -0700545
546 fn mkdir(
547 &self,
548 _ctx: Context,
549 parent: Self::Inode,
550 name: &CStr,
551 _mode: u32,
552 _umask: u32,
553 ) -> io::Result<Entry> {
554 // TODO(205169366): Implement mode properly.
555 let new_inode = self.insert_new_inode(&parent, |config| match config {
556 FileConfig::VerifiedNewDirectory { dir } => {
557 let basename: &Path = cstr_to_path(name);
558 if dir.find_inode(basename).is_some() {
559 return Err(io::Error::from_raw_os_error(libc::EEXIST));
560 }
561 let (new_inode, new_dir) = dir.mkdir(basename)?;
562 Ok((new_inode, FileConfig::VerifiedNewDirectory { dir: new_dir }))
563 }
564 _ => Err(io::Error::from_raw_os_error(libc::EBADF)),
565 })?;
566
567 Ok(Entry {
568 inode: new_inode,
569 generation: 0,
570 attr: create_dir_stat(new_inode, /* file_number */ 0)?,
571 entry_timeout: DEFAULT_METADATA_TIMEOUT,
572 attr_timeout: DEFAULT_METADATA_TIMEOUT,
573 })
574 }
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800575}
576
577/// Mount and start the FUSE instance. This requires CAP_SYS_ADMIN.
578pub fn loop_forever(
579 file_pool: BTreeMap<Inode, FileConfig>,
580 mountpoint: &Path,
Victor Hsieh4cc3b792021-08-04 12:00:04 -0700581 extra_options: &Option<String>,
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800582) -> Result<(), fuse::Error> {
583 let max_read: u32 = 65536;
584 let max_write: u32 = 65536;
585 let dev_fuse = OpenOptions::new()
586 .read(true)
587 .write(true)
588 .open("/dev/fuse")
589 .expect("Failed to open /dev/fuse");
590
Victor Hsieh4cc3b792021-08-04 12:00:04 -0700591 let mut mount_options = vec![
592 MountOption::FD(dev_fuse.as_raw_fd()),
593 MountOption::RootMode(libc::S_IFDIR | libc::S_IXUSR | libc::S_IXGRP | libc::S_IXOTH),
594 MountOption::AllowOther,
595 MountOption::UserId(0),
596 MountOption::GroupId(0),
597 MountOption::MaxRead(max_read),
598 ];
599 if let Some(value) = extra_options {
600 mount_options.push(MountOption::Extra(value));
601 }
602
603 fuse::mount(mountpoint, "authfs", libc::MS_NOSUID | libc::MS_NODEV, &mount_options)
604 .expect("Failed to mount fuse");
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800605
606 fuse::worker::start_message_loop(
607 dev_fuse,
608 max_write,
609 max_read,
Victor Hsieh45636232021-10-15 17:52:51 -0700610 AuthFs::new(Arc::new(Mutex::new(file_pool)), max_write),
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800611 )
612}
Victor Hsieh45636232021-10-15 17:52:51 -0700613
614fn cstr_to_path(cstr: &CStr) -> &Path {
615 OsStr::from_bytes(cstr.to_bytes()).as_ref()
616}