blob: d2948c78797e1e880327276b751390c13cde73f9 [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 Hsieh9d0ab622021-04-26 17:07:02 -070031 Context, DirEntry, DirectoryIterator, Entry, FileSystem, FsOptions, SetattrValid,
32 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 Hsiehd0bb5d32021-03-19 12:48:03 -070038 LocalFileReader, RandomWrite, ReadByChunk, RemoteFileEditor, RemoteFileReader,
Victor Hsieh6a47e7f2021-03-03 15:53:49 -080039 RemoteMerkleTreeReader,
40};
41use crate::fsverity::{VerifiedFileEditor, VerifiedFileReader};
Victor Hsieh88ac6ca2020-11-13 15:20:24 -080042
Victor Hsieh88ac6ca2020-11-13 15:20:24 -080043const DEFAULT_METADATA_TIMEOUT: std::time::Duration = Duration::from_secs(5);
44
45pub type Inode = u64;
46type Handle = u64;
47
Victor Hsieh1bcf4112021-03-19 14:26:57 -070048/// `FileConfig` defines the file type supported by AuthFS.
Victor Hsieh88ac6ca2020-11-13 15:20:24 -080049pub enum FileConfig {
Victor Hsieh1bcf4112021-03-19 14:26:57 -070050 /// A file type that is verified against fs-verity signature (thus read-only). The file is
51 /// backed by a local file. Debug only.
52 LocalVerifiedReadonlyFile {
53 reader: VerifiedFileReader<LocalFileReader, LocalFileReader>,
54 file_size: u64,
55 },
56 /// A file type that is a read-only passthrough from a local file. Debug only.
57 LocalUnverifiedReadonlyFile { reader: LocalFileReader, file_size: u64 },
58 /// A file type that is verified against fs-verity signature (thus read-only). The file is
59 /// served from a remote server.
60 RemoteVerifiedReadonlyFile {
61 reader: VerifiedFileReader<RemoteFileReader, RemoteMerkleTreeReader>,
62 file_size: u64,
63 },
64 /// A file type that is a read-only passthrough from a file on a remote serrver.
65 RemoteUnverifiedReadonlyFile { reader: RemoteFileReader, file_size: u64 },
66 /// A file type that is initially empty, and the content is stored on a remote server. File
67 /// integrity is guaranteed with private Merkle tree.
68 RemoteVerifiedNewFile { editor: VerifiedFileEditor<RemoteFileEditor> },
Victor Hsieh88ac6ca2020-11-13 15:20:24 -080069}
70
71struct AuthFs {
72 /// Store `FileConfig`s using the `Inode` number as the search index.
73 ///
74 /// For further optimization to minimize the search cost, since Inode is integer, we may
75 /// consider storing them in a Vec if we can guarantee that the numbers are small and
76 /// consecutive.
77 file_pool: BTreeMap<Inode, FileConfig>,
78
79 /// Maximum bytes in the write transaction to the FUSE device. This limits the maximum size to
80 /// a read request (including FUSE protocol overhead).
81 max_write: u32,
82}
83
84impl AuthFs {
85 pub fn new(file_pool: BTreeMap<Inode, FileConfig>, max_write: u32) -> AuthFs {
86 AuthFs { file_pool, max_write }
87 }
88
89 fn get_file_config(&self, inode: &Inode) -> io::Result<&FileConfig> {
90 self.file_pool.get(&inode).ok_or_else(|| io::Error::from_raw_os_error(libc::ENOENT))
91 }
92}
93
94fn check_access_mode(flags: u32, mode: libc::c_int) -> io::Result<()> {
95 if (flags & libc::O_ACCMODE as u32) == mode as u32 {
96 Ok(())
97 } else {
98 Err(io::Error::from_raw_os_error(libc::EACCES))
99 }
100}
101
102cfg_if::cfg_if! {
103 if #[cfg(all(target_arch = "aarch64", target_pointer_width = "64"))] {
Victor Hsiehda3fbc42021-02-23 16:12:49 -0800104 fn blk_size() -> libc::c_int { CHUNK_SIZE as libc::c_int }
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800105 } else {
Victor Hsiehda3fbc42021-02-23 16:12:49 -0800106 fn blk_size() -> libc::c_long { CHUNK_SIZE as libc::c_long }
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800107 }
108}
109
Victor Hsieh6a47e7f2021-03-03 15:53:49 -0800110enum FileMode {
111 ReadOnly,
112 ReadWrite,
113}
114
115fn create_stat(ino: libc::ino_t, file_size: u64, file_mode: FileMode) -> io::Result<libc::stat64> {
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800116 let mut st = unsafe { MaybeUninit::<libc::stat64>::zeroed().assume_init() };
117
118 st.st_ino = ino;
Victor Hsieh6a47e7f2021-03-03 15:53:49 -0800119 st.st_mode = match file_mode {
120 // Until needed, let's just grant the owner access.
121 FileMode::ReadOnly => libc::S_IFREG | libc::S_IRUSR,
122 FileMode::ReadWrite => libc::S_IFREG | libc::S_IRUSR | libc::S_IWUSR,
123 };
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800124 st.st_dev = 0;
125 st.st_nlink = 1;
126 st.st_uid = 0;
127 st.st_gid = 0;
128 st.st_rdev = 0;
129 st.st_size = libc::off64_t::try_from(file_size)
130 .map_err(|_| io::Error::from_raw_os_error(libc::EFBIG))?;
131 st.st_blksize = blk_size();
132 // Per man stat(2), st_blocks is "Number of 512B blocks allocated".
133 st.st_blocks = libc::c_longlong::try_from(divide_roundup(file_size, 512))
134 .map_err(|_| io::Error::from_raw_os_error(libc::EFBIG))?;
135 Ok(st)
136}
137
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800138fn offset_to_chunk_index(offset: u64) -> u64 {
Victor Hsiehda3fbc42021-02-23 16:12:49 -0800139 offset / CHUNK_SIZE
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800140}
141
Victor Hsiehd0bb5d32021-03-19 12:48:03 -0700142fn read_chunks<W: io::Write, T: ReadByChunk>(
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800143 mut w: W,
144 file: &T,
145 file_size: u64,
146 offset: u64,
147 size: u32,
148) -> io::Result<usize> {
149 let remaining = file_size.saturating_sub(offset);
150 let size_to_read = std::cmp::min(size as usize, remaining as usize);
Victor Hsiehac4f3f42021-02-26 12:35:58 -0800151 let total = ChunkedSizeIter::new(size_to_read, offset, CHUNK_SIZE as usize).try_fold(
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800152 0,
153 |total, (current_offset, planned_data_size)| {
154 // TODO(victorhsieh): There might be a non-trivial way to avoid this copy. For example,
155 // instead of accepting a buffer, the writer could expose the final destination buffer
156 // for the reader to write to. It might not be generally applicable though, e.g. with
157 // virtio transport, the buffer may not be continuous.
Victor Hsiehda3fbc42021-02-23 16:12:49 -0800158 let mut buf = [0u8; CHUNK_SIZE as usize];
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800159 let read_size = file.read_chunk(offset_to_chunk_index(current_offset), &mut buf)?;
160 if read_size < planned_data_size {
161 return Err(io::Error::from_raw_os_error(libc::ENODATA));
162 }
163
Victor Hsiehda3fbc42021-02-23 16:12:49 -0800164 let begin = (current_offset % CHUNK_SIZE) as usize;
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800165 let end = begin + planned_data_size;
166 let s = w.write(&buf[begin..end])?;
167 if s != planned_data_size {
168 return Err(io::Error::from_raw_os_error(libc::EIO));
169 }
170 Ok(total + s)
171 },
172 )?;
173
174 Ok(total)
175}
176
177// No need to support enumerating directory entries.
178struct EmptyDirectoryIterator {}
179
180impl DirectoryIterator for EmptyDirectoryIterator {
181 fn next(&mut self) -> Option<DirEntry> {
182 None
183 }
184}
185
186impl FileSystem for AuthFs {
187 type Inode = Inode;
188 type Handle = Handle;
189 type DirIter = EmptyDirectoryIterator;
190
191 fn max_buffer_size(&self) -> u32 {
192 self.max_write
193 }
194
Victor Hsieh6a47e7f2021-03-03 15:53:49 -0800195 fn init(&self, _capable: FsOptions) -> io::Result<FsOptions> {
196 // Enable writeback cache for better performance especially since our bandwidth to the
197 // backend service is limited.
198 Ok(FsOptions::WRITEBACK_CACHE)
199 }
200
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800201 fn lookup(&self, _ctx: Context, _parent: Inode, name: &CStr) -> io::Result<Entry> {
202 // Only accept file name that looks like an integrer. Files in the pool are simply exposed
203 // by their inode number. Also, there is currently no directory structure.
204 let num = name.to_str().map_err(|_| io::Error::from_raw_os_error(libc::EINVAL))?;
205 // Normally, `lookup` is required to increase a reference count for the inode (while
206 // `forget` will decrease it). It is not necessary here since the files are configured to
207 // be static.
208 let inode = num.parse::<Inode>().map_err(|_| io::Error::from_raw_os_error(libc::ENOENT))?;
209 let st = match self.get_file_config(&inode)? {
Victor Hsieh1bcf4112021-03-19 14:26:57 -0700210 FileConfig::LocalVerifiedReadonlyFile { file_size, .. }
211 | FileConfig::LocalUnverifiedReadonlyFile { file_size, .. }
212 | FileConfig::RemoteUnverifiedReadonlyFile { file_size, .. }
213 | FileConfig::RemoteVerifiedReadonlyFile { file_size, .. } => {
Victor Hsieh6a47e7f2021-03-03 15:53:49 -0800214 create_stat(inode, *file_size, FileMode::ReadOnly)?
215 }
Victor Hsieh1bcf4112021-03-19 14:26:57 -0700216 FileConfig::RemoteVerifiedNewFile { editor } => {
217 create_stat(inode, editor.size(), FileMode::ReadWrite)?
Victor Hsieh09e26262021-03-03 16:00:55 -0800218 }
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800219 };
220 Ok(Entry {
221 inode,
222 generation: 0,
223 attr: st,
224 entry_timeout: DEFAULT_METADATA_TIMEOUT,
225 attr_timeout: DEFAULT_METADATA_TIMEOUT,
226 })
227 }
228
229 fn getattr(
230 &self,
231 _ctx: Context,
232 inode: Inode,
233 _handle: Option<Handle>,
234 ) -> io::Result<(libc::stat64, Duration)> {
235 Ok((
236 match self.get_file_config(&inode)? {
Victor Hsieh1bcf4112021-03-19 14:26:57 -0700237 FileConfig::LocalVerifiedReadonlyFile { file_size, .. }
238 | FileConfig::LocalUnverifiedReadonlyFile { file_size, .. }
239 | FileConfig::RemoteUnverifiedReadonlyFile { file_size, .. }
240 | FileConfig::RemoteVerifiedReadonlyFile { file_size, .. } => {
Victor Hsieh6a47e7f2021-03-03 15:53:49 -0800241 create_stat(inode, *file_size, FileMode::ReadOnly)?
242 }
Victor Hsieh1bcf4112021-03-19 14:26:57 -0700243 FileConfig::RemoteVerifiedNewFile { editor } => {
244 create_stat(inode, editor.size(), FileMode::ReadWrite)?
Victor Hsieh09e26262021-03-03 16:00:55 -0800245 }
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800246 },
247 DEFAULT_METADATA_TIMEOUT,
248 ))
249 }
250
251 fn open(
252 &self,
253 _ctx: Context,
254 inode: Self::Inode,
255 flags: u32,
256 ) -> io::Result<(Option<Self::Handle>, fuse::sys::OpenOptions)> {
257 // Since file handle is not really used in later operations (which use Inode directly),
Victor Hsieh09e26262021-03-03 16:00:55 -0800258 // return None as the handle.
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800259 match self.get_file_config(&inode)? {
Victor Hsieh1bcf4112021-03-19 14:26:57 -0700260 FileConfig::LocalVerifiedReadonlyFile { .. }
Victor Hsieh8e161b52021-04-26 17:47:19 -0700261 | FileConfig::LocalUnverifiedReadonlyFile { .. }
262 | FileConfig::RemoteVerifiedReadonlyFile { .. }
Victor Hsieh1bcf4112021-03-19 14:26:57 -0700263 | FileConfig::RemoteUnverifiedReadonlyFile { .. } => {
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800264 check_access_mode(flags, libc::O_RDONLY)?;
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800265 }
Victor Hsieh1bcf4112021-03-19 14:26:57 -0700266 FileConfig::RemoteVerifiedNewFile { .. } => {
Victor Hsieh6a47e7f2021-03-03 15:53:49 -0800267 // No need to check access modes since all the modes are allowed to the
268 // read-writable file.
Victor Hsieh6a47e7f2021-03-03 15:53:49 -0800269 }
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800270 }
Victor Hsieh8e161b52021-04-26 17:47:19 -0700271 // Always cache the file content. There is currently no need to support direct I/O or avoid
272 // the cache buffer. Memory mapping is only possible with cache enabled.
273 Ok((None, fuse::sys::OpenOptions::KEEP_CACHE))
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800274 }
275
276 fn read<W: io::Write + ZeroCopyWriter>(
277 &self,
278 _ctx: Context,
279 inode: Inode,
280 _handle: Handle,
281 w: W,
282 size: u32,
283 offset: u64,
284 _lock_owner: Option<u64>,
285 _flags: u32,
286 ) -> io::Result<usize> {
287 match self.get_file_config(&inode)? {
Victor Hsieh1bcf4112021-03-19 14:26:57 -0700288 FileConfig::LocalVerifiedReadonlyFile { reader, file_size } => {
289 read_chunks(w, reader, *file_size, offset, size)
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800290 }
Victor Hsieh1bcf4112021-03-19 14:26:57 -0700291 FileConfig::LocalUnverifiedReadonlyFile { reader, file_size } => {
292 read_chunks(w, reader, *file_size, offset, size)
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800293 }
Victor Hsieh1bcf4112021-03-19 14:26:57 -0700294 FileConfig::RemoteVerifiedReadonlyFile { reader, file_size } => {
295 read_chunks(w, reader, *file_size, offset, size)
Victor Hsiehf01f3232020-12-11 13:31:31 -0800296 }
Victor Hsieh1bcf4112021-03-19 14:26:57 -0700297 FileConfig::RemoteUnverifiedReadonlyFile { reader, file_size } => {
298 read_chunks(w, reader, *file_size, offset, size)
Victor Hsiehf01f3232020-12-11 13:31:31 -0800299 }
Victor Hsieh1bcf4112021-03-19 14:26:57 -0700300 FileConfig::RemoteVerifiedNewFile { editor } => {
Victor Hsieh6a47e7f2021-03-03 15:53:49 -0800301 // Note that with FsOptions::WRITEBACK_CACHE, it's possible for the kernel to
302 // request a read even if the file is open with O_WRONLY.
Victor Hsieh1bcf4112021-03-19 14:26:57 -0700303 read_chunks(w, editor, editor.size(), offset, size)
Victor Hsieh6a47e7f2021-03-03 15:53:49 -0800304 }
305 }
306 }
307
308 fn write<R: io::Read + ZeroCopyReader>(
309 &self,
310 _ctx: Context,
311 inode: Self::Inode,
312 _handle: Self::Handle,
313 mut r: R,
314 size: u32,
315 offset: u64,
316 _lock_owner: Option<u64>,
317 _delayed_write: bool,
318 _flags: u32,
319 ) -> io::Result<usize> {
320 match self.get_file_config(&inode)? {
Victor Hsieh1bcf4112021-03-19 14:26:57 -0700321 FileConfig::RemoteVerifiedNewFile { editor } => {
Victor Hsieh6a47e7f2021-03-03 15:53:49 -0800322 let mut buf = vec![0; size as usize];
323 r.read_exact(&mut buf)?;
Victor Hsieh1bcf4112021-03-19 14:26:57 -0700324 editor.write_at(&buf, offset)
Victor Hsieh6a47e7f2021-03-03 15:53:49 -0800325 }
326 _ => Err(io::Error::from_raw_os_error(libc::EBADF)),
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800327 }
328 }
Victor Hsieh9d0ab622021-04-26 17:07:02 -0700329
330 fn setattr(
331 &self,
332 _ctx: Context,
333 inode: Inode,
334 attr: libc::stat64,
335 _handle: Option<Handle>,
336 valid: SetattrValid,
337 ) -> io::Result<(libc::stat64, Duration)> {
338 match self.get_file_config(&inode)? {
339 FileConfig::RemoteVerifiedNewFile { editor } => {
340 // Initialize the default stat.
341 let mut new_attr = create_stat(inode, editor.size(), FileMode::ReadWrite)?;
342 // `valid` indicates what fields in `attr` are valid. Update to return correctly.
343 if valid.contains(SetattrValid::SIZE) {
344 // st_size is i64, but the cast should be safe since kernel should not give a
345 // negative size.
346 debug_assert!(attr.st_size >= 0);
347 new_attr.st_size = attr.st_size;
348 editor.resize(attr.st_size as u64)?;
349 }
350
351 if valid.contains(SetattrValid::MODE) {
352 warn!("Changing st_mode is not currently supported");
353 return Err(io::Error::from_raw_os_error(libc::ENOSYS));
354 }
355 if valid.contains(SetattrValid::UID) {
356 warn!("Changing st_uid is not currently supported");
357 return Err(io::Error::from_raw_os_error(libc::ENOSYS));
358 }
359 if valid.contains(SetattrValid::GID) {
360 warn!("Changing st_gid is not currently supported");
361 return Err(io::Error::from_raw_os_error(libc::ENOSYS));
362 }
363 if valid.contains(SetattrValid::CTIME) {
364 debug!("Ignoring ctime change as authfs does not maintain timestamp currently");
365 }
366 if valid.intersects(SetattrValid::ATIME | SetattrValid::ATIME_NOW) {
367 debug!("Ignoring atime change as authfs does not maintain timestamp currently");
368 }
369 if valid.intersects(SetattrValid::MTIME | SetattrValid::MTIME_NOW) {
370 debug!("Ignoring mtime change as authfs does not maintain timestamp currently");
371 }
372 Ok((new_attr, DEFAULT_METADATA_TIMEOUT))
373 }
374 _ => Err(io::Error::from_raw_os_error(libc::EBADF)),
375 }
376 }
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800377}
378
379/// Mount and start the FUSE instance. This requires CAP_SYS_ADMIN.
380pub fn loop_forever(
381 file_pool: BTreeMap<Inode, FileConfig>,
382 mountpoint: &Path,
383) -> Result<(), fuse::Error> {
384 let max_read: u32 = 65536;
385 let max_write: u32 = 65536;
386 let dev_fuse = OpenOptions::new()
387 .read(true)
388 .write(true)
389 .open("/dev/fuse")
390 .expect("Failed to open /dev/fuse");
391
392 fuse::mount(
393 mountpoint,
394 "authfs",
395 libc::MS_NOSUID | libc::MS_NODEV,
396 &[
397 MountOption::FD(dev_fuse.as_raw_fd()),
398 MountOption::RootMode(libc::S_IFDIR | libc::S_IXUSR | libc::S_IXGRP | libc::S_IXOTH),
399 MountOption::AllowOther,
400 MountOption::UserId(0),
401 MountOption::GroupId(0),
402 MountOption::MaxRead(max_read),
403 ],
404 )
405 .expect("Failed to mount fuse");
406
407 fuse::worker::start_message_loop(
408 dev_fuse,
409 max_write,
410 max_read,
411 AuthFs::new(file_pool, max_write),
412 )
413}