blob: d985581b107d6aa8d4ab1a0bcc23470e3d49745b [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 Hsieh9d0ab622021-04-26 17:07:02 -070018use log::{debug, warn};
Victor Hsieh88ac6ca2020-11-13 15:20:24 -080019use std::collections::BTreeMap;
20use std::convert::TryFrom;
21use std::ffi::CStr;
22use std::fs::OpenOptions;
23use std::io;
24use std::mem::MaybeUninit;
25use std::option::Option;
26use std::os::unix::io::AsRawFd;
27use std::path::Path;
28use std::time::Duration;
29
Victor Hsieh6a47e7f2021-03-03 15:53:49 -080030use fuse::filesystem::{
Victor Hsieh71f10032021-08-13 11:24:02 -070031 Context, DirEntry, DirectoryIterator, Entry, FileSystem, FsOptions, GetxattrReply,
32 SetattrValid, ZeroCopyReader, ZeroCopyWriter,
Victor Hsieh6a47e7f2021-03-03 15:53:49 -080033};
Victor Hsieh88ac6ca2020-11-13 15:20:24 -080034use fuse::mount::MountOption;
35
Victor Hsiehac4f3f42021-02-26 12:35:58 -080036use crate::common::{divide_roundup, ChunkedSizeIter, CHUNK_SIZE};
Victor Hsieh6a47e7f2021-03-03 15:53:49 -080037use crate::file::{
Victor Hsieh88e50172021-10-15 13:27:13 -070038 RandomWrite, ReadByChunk, RemoteFileEditor, RemoteFileReader, RemoteMerkleTreeReader,
Victor Hsieh6a47e7f2021-03-03 15:53:49 -080039};
40use crate::fsverity::{VerifiedFileEditor, VerifiedFileReader};
Victor Hsieh88ac6ca2020-11-13 15:20:24 -080041
Victor Hsieh88ac6ca2020-11-13 15:20:24 -080042const DEFAULT_METADATA_TIMEOUT: std::time::Duration = Duration::from_secs(5);
43
44pub type Inode = u64;
45type Handle = u64;
46
Victor Hsieh1bcf4112021-03-19 14:26:57 -070047/// `FileConfig` defines the file type supported by AuthFS.
Victor Hsieh88ac6ca2020-11-13 15:20:24 -080048pub enum FileConfig {
Victor Hsieh1bcf4112021-03-19 14:26:57 -070049 /// A file type that is verified against fs-verity signature (thus read-only). The file is
Victor Hsieh1bcf4112021-03-19 14:26:57 -070050 /// served from a remote server.
Victor Hsieh88e50172021-10-15 13:27:13 -070051 VerifiedReadonly {
Victor Hsieh1bcf4112021-03-19 14:26:57 -070052 reader: VerifiedFileReader<RemoteFileReader, RemoteMerkleTreeReader>,
53 file_size: u64,
54 },
55 /// A file type that is a read-only passthrough from a file on a remote serrver.
Victor Hsieh88e50172021-10-15 13:27:13 -070056 UnverifiedReadonly { reader: RemoteFileReader, file_size: u64 },
Victor Hsieh1bcf4112021-03-19 14:26:57 -070057 /// A file type that is initially empty, and the content is stored on a remote server. File
58 /// integrity is guaranteed with private Merkle tree.
Victor Hsieh88e50172021-10-15 13:27:13 -070059 VerifiedNew { editor: VerifiedFileEditor<RemoteFileEditor> },
Victor Hsieh88ac6ca2020-11-13 15:20:24 -080060}
61
62struct AuthFs {
63 /// Store `FileConfig`s using the `Inode` number as the search index.
64 ///
65 /// For further optimization to minimize the search cost, since Inode is integer, we may
66 /// consider storing them in a Vec if we can guarantee that the numbers are small and
67 /// consecutive.
68 file_pool: BTreeMap<Inode, FileConfig>,
69
70 /// Maximum bytes in the write transaction to the FUSE device. This limits the maximum size to
71 /// a read request (including FUSE protocol overhead).
72 max_write: u32,
73}
74
75impl AuthFs {
76 pub fn new(file_pool: BTreeMap<Inode, FileConfig>, max_write: u32) -> AuthFs {
77 AuthFs { file_pool, max_write }
78 }
79
Victor Hsiehc85e4ef2021-10-18 15:28:53 -070080 /// Handles the file associated with `inode` if found. This function returns whatever the
81 /// handler returns.
82 fn handle_file<F, R>(&self, inode: &Inode, handler: F) -> io::Result<R>
83 where
84 F: FnOnce(&FileConfig) -> io::Result<R>,
85 {
86 let config =
87 self.file_pool.get(inode).ok_or_else(|| io::Error::from_raw_os_error(libc::ENOENT))?;
88 handler(config)
Victor Hsieh88ac6ca2020-11-13 15:20:24 -080089 }
90}
91
92fn check_access_mode(flags: u32, mode: libc::c_int) -> io::Result<()> {
93 if (flags & libc::O_ACCMODE as u32) == mode as u32 {
94 Ok(())
95 } else {
96 Err(io::Error::from_raw_os_error(libc::EACCES))
97 }
98}
99
100cfg_if::cfg_if! {
101 if #[cfg(all(target_arch = "aarch64", target_pointer_width = "64"))] {
Victor Hsiehda3fbc42021-02-23 16:12:49 -0800102 fn blk_size() -> libc::c_int { CHUNK_SIZE as libc::c_int }
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800103 } else {
Victor Hsiehda3fbc42021-02-23 16:12:49 -0800104 fn blk_size() -> libc::c_long { CHUNK_SIZE as libc::c_long }
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800105 }
106}
107
Victor Hsieh6a47e7f2021-03-03 15:53:49 -0800108enum FileMode {
109 ReadOnly,
110 ReadWrite,
111}
112
113fn create_stat(ino: libc::ino_t, file_size: u64, file_mode: FileMode) -> io::Result<libc::stat64> {
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800114 let mut st = unsafe { MaybeUninit::<libc::stat64>::zeroed().assume_init() };
115
116 st.st_ino = ino;
Victor Hsieh6a47e7f2021-03-03 15:53:49 -0800117 st.st_mode = match file_mode {
118 // Until needed, let's just grant the owner access.
119 FileMode::ReadOnly => libc::S_IFREG | libc::S_IRUSR,
120 FileMode::ReadWrite => libc::S_IFREG | libc::S_IRUSR | libc::S_IWUSR,
121 };
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800122 st.st_dev = 0;
123 st.st_nlink = 1;
124 st.st_uid = 0;
125 st.st_gid = 0;
126 st.st_rdev = 0;
127 st.st_size = libc::off64_t::try_from(file_size)
128 .map_err(|_| io::Error::from_raw_os_error(libc::EFBIG))?;
129 st.st_blksize = blk_size();
130 // Per man stat(2), st_blocks is "Number of 512B blocks allocated".
131 st.st_blocks = libc::c_longlong::try_from(divide_roundup(file_size, 512))
132 .map_err(|_| io::Error::from_raw_os_error(libc::EFBIG))?;
133 Ok(st)
134}
135
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800136fn offset_to_chunk_index(offset: u64) -> u64 {
Victor Hsiehda3fbc42021-02-23 16:12:49 -0800137 offset / CHUNK_SIZE
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800138}
139
Victor Hsiehd0bb5d32021-03-19 12:48:03 -0700140fn read_chunks<W: io::Write, T: ReadByChunk>(
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800141 mut w: W,
142 file: &T,
143 file_size: u64,
144 offset: u64,
145 size: u32,
146) -> io::Result<usize> {
147 let remaining = file_size.saturating_sub(offset);
148 let size_to_read = std::cmp::min(size as usize, remaining as usize);
Victor Hsiehac4f3f42021-02-26 12:35:58 -0800149 let total = ChunkedSizeIter::new(size_to_read, offset, CHUNK_SIZE as usize).try_fold(
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800150 0,
151 |total, (current_offset, planned_data_size)| {
152 // TODO(victorhsieh): There might be a non-trivial way to avoid this copy. For example,
153 // instead of accepting a buffer, the writer could expose the final destination buffer
154 // for the reader to write to. It might not be generally applicable though, e.g. with
155 // virtio transport, the buffer may not be continuous.
Victor Hsiehda3fbc42021-02-23 16:12:49 -0800156 let mut buf = [0u8; CHUNK_SIZE as usize];
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800157 let read_size = file.read_chunk(offset_to_chunk_index(current_offset), &mut buf)?;
158 if read_size < planned_data_size {
159 return Err(io::Error::from_raw_os_error(libc::ENODATA));
160 }
161
Victor Hsiehda3fbc42021-02-23 16:12:49 -0800162 let begin = (current_offset % CHUNK_SIZE) as usize;
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800163 let end = begin + planned_data_size;
164 let s = w.write(&buf[begin..end])?;
165 if s != planned_data_size {
166 return Err(io::Error::from_raw_os_error(libc::EIO));
167 }
168 Ok(total + s)
169 },
170 )?;
171
172 Ok(total)
173}
174
175// No need to support enumerating directory entries.
176struct EmptyDirectoryIterator {}
177
178impl DirectoryIterator for EmptyDirectoryIterator {
179 fn next(&mut self) -> Option<DirEntry> {
180 None
181 }
182}
183
184impl FileSystem for AuthFs {
185 type Inode = Inode;
186 type Handle = Handle;
187 type DirIter = EmptyDirectoryIterator;
188
189 fn max_buffer_size(&self) -> u32 {
190 self.max_write
191 }
192
Victor Hsieh6a47e7f2021-03-03 15:53:49 -0800193 fn init(&self, _capable: FsOptions) -> io::Result<FsOptions> {
194 // Enable writeback cache for better performance especially since our bandwidth to the
195 // backend service is limited.
196 Ok(FsOptions::WRITEBACK_CACHE)
197 }
198
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800199 fn lookup(&self, _ctx: Context, _parent: Inode, name: &CStr) -> io::Result<Entry> {
200 // Only accept file name that looks like an integrer. Files in the pool are simply exposed
201 // by their inode number. Also, there is currently no directory structure.
202 let num = name.to_str().map_err(|_| io::Error::from_raw_os_error(libc::EINVAL))?;
203 // Normally, `lookup` is required to increase a reference count for the inode (while
204 // `forget` will decrease it). It is not necessary here since the files are configured to
205 // be static.
206 let inode = num.parse::<Inode>().map_err(|_| io::Error::from_raw_os_error(libc::ENOENT))?;
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700207 let st = self.handle_file(&inode, |config| match config {
Victor Hsieh88e50172021-10-15 13:27:13 -0700208 FileConfig::UnverifiedReadonly { file_size, .. }
209 | FileConfig::VerifiedReadonly { file_size, .. } => {
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700210 create_stat(inode, *file_size, FileMode::ReadOnly)
Victor Hsieh6a47e7f2021-03-03 15:53:49 -0800211 }
Victor Hsieh88e50172021-10-15 13:27:13 -0700212 FileConfig::VerifiedNew { editor } => {
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700213 create_stat(inode, editor.size(), FileMode::ReadWrite)
Victor Hsieh09e26262021-03-03 16:00:55 -0800214 }
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700215 })?;
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800216 Ok(Entry {
217 inode,
218 generation: 0,
219 attr: st,
220 entry_timeout: DEFAULT_METADATA_TIMEOUT,
221 attr_timeout: DEFAULT_METADATA_TIMEOUT,
222 })
223 }
224
225 fn getattr(
226 &self,
227 _ctx: Context,
228 inode: Inode,
229 _handle: Option<Handle>,
230 ) -> io::Result<(libc::stat64, Duration)> {
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700231 self.handle_file(&inode, |config| {
232 Ok((
233 match config {
234 FileConfig::UnverifiedReadonly { file_size, .. }
235 | FileConfig::VerifiedReadonly { file_size, .. } => {
236 create_stat(inode, *file_size, FileMode::ReadOnly)?
237 }
238 FileConfig::VerifiedNew { editor } => {
239 create_stat(inode, editor.size(), FileMode::ReadWrite)?
240 }
241 },
242 DEFAULT_METADATA_TIMEOUT,
243 ))
244 })
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800245 }
246
247 fn open(
248 &self,
249 _ctx: Context,
250 inode: Self::Inode,
251 flags: u32,
252 ) -> io::Result<(Option<Self::Handle>, fuse::sys::OpenOptions)> {
253 // Since file handle is not really used in later operations (which use Inode directly),
Victor Hsieh09e26262021-03-03 16:00:55 -0800254 // return None as the handle.
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700255 self.handle_file(&inode, |config| {
256 match config {
257 FileConfig::VerifiedReadonly { .. } | FileConfig::UnverifiedReadonly { .. } => {
258 check_access_mode(flags, libc::O_RDONLY)?;
259 }
260 FileConfig::VerifiedNew { .. } => {
261 // No need to check access modes since all the modes are allowed to the
262 // read-writable file.
263 }
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800264 }
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700265 // Always cache the file content. There is currently no need to support direct I/O or avoid
266 // the cache buffer. Memory mapping is only possible with cache enabled.
267 Ok((None, fuse::sys::OpenOptions::KEEP_CACHE))
268 })
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800269 }
270
271 fn read<W: io::Write + ZeroCopyWriter>(
272 &self,
273 _ctx: Context,
274 inode: Inode,
275 _handle: Handle,
276 w: W,
277 size: u32,
278 offset: u64,
279 _lock_owner: Option<u64>,
280 _flags: u32,
281 ) -> io::Result<usize> {
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700282 self.handle_file(&inode, |config| {
283 match config {
284 FileConfig::VerifiedReadonly { reader, file_size } => {
285 read_chunks(w, reader, *file_size, offset, size)
286 }
287 FileConfig::UnverifiedReadonly { reader, file_size } => {
288 read_chunks(w, reader, *file_size, offset, size)
289 }
290 FileConfig::VerifiedNew { editor } => {
291 // Note that with FsOptions::WRITEBACK_CACHE, it's possible for the kernel to
292 // request a read even if the file is open with O_WRONLY.
293 read_chunks(w, editor, editor.size(), offset, size)
294 }
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800295 }
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700296 })
Victor Hsieh6a47e7f2021-03-03 15:53:49 -0800297 }
298
299 fn write<R: io::Read + ZeroCopyReader>(
300 &self,
301 _ctx: Context,
302 inode: Self::Inode,
303 _handle: Self::Handle,
304 mut r: R,
305 size: u32,
306 offset: u64,
307 _lock_owner: Option<u64>,
308 _delayed_write: bool,
309 _flags: u32,
310 ) -> io::Result<usize> {
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700311 self.handle_file(&inode, |config| match config {
Victor Hsieh88e50172021-10-15 13:27:13 -0700312 FileConfig::VerifiedNew { editor } => {
Victor Hsieh6a47e7f2021-03-03 15:53:49 -0800313 let mut buf = vec![0; size as usize];
314 r.read_exact(&mut buf)?;
Victor Hsieh1bcf4112021-03-19 14:26:57 -0700315 editor.write_at(&buf, offset)
Victor Hsieh6a47e7f2021-03-03 15:53:49 -0800316 }
317 _ => Err(io::Error::from_raw_os_error(libc::EBADF)),
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700318 })
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800319 }
Victor Hsieh9d0ab622021-04-26 17:07:02 -0700320
321 fn setattr(
322 &self,
323 _ctx: Context,
324 inode: Inode,
325 attr: libc::stat64,
326 _handle: Option<Handle>,
327 valid: SetattrValid,
328 ) -> io::Result<(libc::stat64, Duration)> {
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700329 self.handle_file(&inode, |config| {
330 match config {
331 FileConfig::VerifiedNew { editor } => {
332 // Initialize the default stat.
333 let mut new_attr = create_stat(inode, editor.size(), FileMode::ReadWrite)?;
334 // `valid` indicates what fields in `attr` are valid. Update to return correctly.
335 if valid.contains(SetattrValid::SIZE) {
336 // st_size is i64, but the cast should be safe since kernel should not give a
337 // negative size.
338 debug_assert!(attr.st_size >= 0);
339 new_attr.st_size = attr.st_size;
340 editor.resize(attr.st_size as u64)?;
341 }
Victor Hsieh9d0ab622021-04-26 17:07:02 -0700342
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700343 if valid.contains(SetattrValid::MODE) {
344 warn!("Changing st_mode is not currently supported");
345 return Err(io::Error::from_raw_os_error(libc::ENOSYS));
346 }
347 if valid.contains(SetattrValid::UID) {
348 warn!("Changing st_uid is not currently supported");
349 return Err(io::Error::from_raw_os_error(libc::ENOSYS));
350 }
351 if valid.contains(SetattrValid::GID) {
352 warn!("Changing st_gid is not currently supported");
353 return Err(io::Error::from_raw_os_error(libc::ENOSYS));
354 }
355 if valid.contains(SetattrValid::CTIME) {
356 debug!(
357 "Ignoring ctime change as authfs does not maintain timestamp currently"
358 );
359 }
360 if valid.intersects(SetattrValid::ATIME | SetattrValid::ATIME_NOW) {
361 debug!(
362 "Ignoring atime change as authfs does not maintain timestamp currently"
363 );
364 }
365 if valid.intersects(SetattrValid::MTIME | SetattrValid::MTIME_NOW) {
366 debug!(
367 "Ignoring mtime change as authfs does not maintain timestamp currently"
368 );
369 }
370 Ok((new_attr, DEFAULT_METADATA_TIMEOUT))
Victor Hsieh9d0ab622021-04-26 17:07:02 -0700371 }
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700372 _ => Err(io::Error::from_raw_os_error(libc::EBADF)),
Victor Hsieh9d0ab622021-04-26 17:07:02 -0700373 }
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700374 })
Victor Hsieh9d0ab622021-04-26 17:07:02 -0700375 }
Victor Hsieh71f10032021-08-13 11:24:02 -0700376
377 fn getxattr(
378 &self,
379 _ctx: Context,
380 inode: Self::Inode,
381 name: &CStr,
382 size: u32,
383 ) -> io::Result<GetxattrReply> {
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700384 self.handle_file(&inode, |config| {
385 match config {
386 FileConfig::VerifiedNew { editor } => {
387 // FUSE ioctl is limited, thus we can't implement fs-verity ioctls without a kernel
388 // change (see b/196635431). Until it's possible, use xattr to expose what we need
389 // as an authfs specific API.
390 if name != CStr::from_bytes_with_nul(b"authfs.fsverity.digest\0").unwrap() {
391 return Err(io::Error::from_raw_os_error(libc::ENODATA));
392 }
Victor Hsieh71f10032021-08-13 11:24:02 -0700393
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700394 if size == 0 {
395 // Per protocol, when size is 0, return the value size.
396 Ok(GetxattrReply::Count(editor.get_fsverity_digest_size() as u32))
Victor Hsieh71f10032021-08-13 11:24:02 -0700397 } else {
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700398 let digest = editor.calculate_fsverity_digest()?;
399 if digest.len() > size as usize {
400 Err(io::Error::from_raw_os_error(libc::ERANGE))
401 } else {
402 Ok(GetxattrReply::Value(digest.to_vec()))
403 }
Victor Hsieh71f10032021-08-13 11:24:02 -0700404 }
405 }
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700406 _ => Err(io::Error::from_raw_os_error(libc::ENODATA)),
Victor Hsieh71f10032021-08-13 11:24:02 -0700407 }
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700408 })
Victor Hsieh71f10032021-08-13 11:24:02 -0700409 }
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800410}
411
412/// Mount and start the FUSE instance. This requires CAP_SYS_ADMIN.
413pub fn loop_forever(
414 file_pool: BTreeMap<Inode, FileConfig>,
415 mountpoint: &Path,
Victor Hsieh4cc3b792021-08-04 12:00:04 -0700416 extra_options: &Option<String>,
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800417) -> Result<(), fuse::Error> {
418 let max_read: u32 = 65536;
419 let max_write: u32 = 65536;
420 let dev_fuse = OpenOptions::new()
421 .read(true)
422 .write(true)
423 .open("/dev/fuse")
424 .expect("Failed to open /dev/fuse");
425
Victor Hsieh4cc3b792021-08-04 12:00:04 -0700426 let mut mount_options = vec![
427 MountOption::FD(dev_fuse.as_raw_fd()),
428 MountOption::RootMode(libc::S_IFDIR | libc::S_IXUSR | libc::S_IXGRP | libc::S_IXOTH),
429 MountOption::AllowOther,
430 MountOption::UserId(0),
431 MountOption::GroupId(0),
432 MountOption::MaxRead(max_read),
433 ];
434 if let Some(value) = extra_options {
435 mount_options.push(MountOption::Extra(value));
436 }
437
438 fuse::mount(mountpoint, "authfs", libc::MS_NOSUID | libc::MS_NODEV, &mount_options)
439 .expect("Failed to mount fuse");
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800440
441 fuse::worker::start_message_loop(
442 dev_fuse,
443 max_write,
444 max_read,
445 AuthFs::new(file_pool, max_write),
446 )
447}