• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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 //! This crate implements AuthFS, a FUSE-based, non-generic filesystem where file access is
18 //! authenticated. This filesystem assumes the underlying layer is not trusted, e.g. file may be
19 //! provided by an untrusted host/VM, so that the content can't be simply trusted. However, with a
20 //! known file hash from trusted party, this filesystem can still verify a (read-only) file even if
21 //! the host/VM as the blob provider is malicious. With the Merkle tree, each read of file block can
22 //! be verified individually only when needed.
23 //!
24 //! AuthFS only serve files that are specifically configured. Each remote file can be configured to
25 //! appear as a local file at the mount point. A file configuration may include its remote file
26 //! identifier and its verification method (e.g. by known digest).
27 //!
28 //! AuthFS also support remote directories. A remote directory may be defined by a manifest file,
29 //! which contains file paths and their corresponding digests.
30 //!
31 //! AuthFS can also be configured for write, in which case the remote file server is treated as a
32 //! (untrusted) storage. The file/directory integrity is maintained in memory in the VM. Currently,
33 //! the state is not persistent, thus only new file/directory are supported.
34 
35 use anyhow::{anyhow, bail, Result};
36 use log::error;
37 use protobuf::Message;
38 use std::convert::TryInto;
39 use std::fs::File;
40 use std::num::NonZeroU8;
41 use std::path::{Path, PathBuf};
42 use structopt::StructOpt;
43 
44 mod common;
45 mod crypto;
46 mod file;
47 mod fsstat;
48 mod fsverity;
49 mod fusefs;
50 
51 use file::{Attr, InMemoryDir, RemoteDirEditor, RemoteFileEditor, RemoteFileReader};
52 use fsstat::RemoteFsStatsReader;
53 use fsverity::VerifiedFileEditor;
54 use fsverity_digests_proto::fsverity_digests::FSVerityDigests;
55 use fusefs::{AuthFs, AuthFsEntry, LazyVerifiedReadonlyFile};
56 
57 #[derive(StructOpt)]
58 struct Args {
59     /// Mount point of AuthFS.
60     #[structopt(parse(from_os_str))]
61     mount_point: PathBuf,
62 
63     /// CID of the VM where the service runs.
64     #[structopt(long)]
65     cid: u32,
66 
67     /// Extra options to FUSE
68     #[structopt(short = "o")]
69     extra_options: Option<String>,
70 
71     /// Number of threads to serve FUSE requests.
72     #[structopt(short = "j")]
73     thread_number: Option<NonZeroU8>,
74 
75     /// A read-only remote file with integrity check. Can be multiple.
76     ///
77     /// For example, `--remote-ro-file 5:sha256-1234abcd` tells the filesystem to associate the
78     /// file $MOUNTPOINT/5 with a remote FD 5, and has a fs-verity digest with sha256 of the hex
79     /// value 1234abcd.
80     #[structopt(long, parse(try_from_str = parse_remote_ro_file_option))]
81     remote_ro_file: Vec<OptionRemoteRoFile>,
82 
83     /// A read-only remote file without integrity check. Can be multiple.
84     ///
85     /// For example, `--remote-ro-file-unverified 5` tells the filesystem to associate the file
86     /// $MOUNTPOINT/5 with a remote FD 5.
87     #[structopt(long)]
88     remote_ro_file_unverified: Vec<i32>,
89 
90     /// A new read-writable remote file with integrity check. Can be multiple.
91     ///
92     /// For example, `--remote-new-rw-file 5` tells the filesystem to associate the file
93     /// $MOUNTPOINT/5 with a remote FD 5.
94     #[structopt(long)]
95     remote_new_rw_file: Vec<i32>,
96 
97     /// A read-only directory that represents a remote directory. The directory view is constructed
98     /// and finalized during the filesystem initialization based on the provided mapping file
99     /// (which is a serialized protobuf of android.security.fsverity.FSVerityDigests, which
100     /// essentially provides <file path, fs-verity digest> mappings of exported files). The mapping
101     /// file is supposed to come from a trusted location in order to provide a trusted view as well
102     /// as verified access of included files with their fs-verity digest. Not all files on the
103     /// remote host may be included in the mapping file, so the directory view may be partial. The
104     /// directory structure won't change throughout the filesystem lifetime.
105     ///
106     /// For example, `--remote-ro-dir 5:/path/to/mapping:prefix/` tells the filesystem to
107     /// construct a directory structure defined in the mapping file at $MOUNTPOINT/5, which may
108     /// include a file like /5/system/framework/framework.jar. "prefix/" tells the filesystem to
109     /// strip the path (e.g. "system/") from the mount point to match the expected location of the
110     /// remote FD (e.g. a directory FD of "/system" in the remote).
111     #[structopt(long, parse(try_from_str = parse_remote_new_ro_dir_option))]
112     remote_ro_dir: Vec<OptionRemoteRoDir>,
113 
114     /// A new directory that is assumed empty in the backing filesystem. New files created in this
115     /// directory are integrity-protected in the same way as --remote-new-verified-file. Can be
116     /// multiple.
117     ///
118     /// For example, `--remote-new-rw-dir 5` tells the filesystem to associate $MOUNTPOINT/5
119     /// with a remote dir FD 5.
120     #[structopt(long)]
121     remote_new_rw_dir: Vec<i32>,
122 
123     /// Enable debugging features.
124     #[structopt(long)]
125     debug: bool,
126 }
127 
128 struct OptionRemoteRoFile {
129     /// ID to refer to the remote file.
130     remote_fd: i32,
131 
132     /// Expected fs-verity digest (with sha256) for the remote file.
133     digest: String,
134 }
135 
136 struct OptionRemoteRoDir {
137     /// ID to refer to the remote dir.
138     remote_dir_fd: i32,
139 
140     /// A mapping file that describes the expecting file/directory structure and integrity metadata
141     /// in the remote directory. The file contains serialized protobuf of
142     /// android.security.fsverity.FSVerityDigests.
143     mapping_file_path: PathBuf,
144 
145     prefix: String,
146 }
147 
parse_remote_ro_file_option(option: &str) -> Result<OptionRemoteRoFile>148 fn parse_remote_ro_file_option(option: &str) -> Result<OptionRemoteRoFile> {
149     let strs: Vec<&str> = option.split(':').collect();
150     if strs.len() != 2 {
151         bail!("Invalid option: {}", option);
152     }
153     if let Some(digest) = strs[1].strip_prefix("sha256-") {
154         Ok(OptionRemoteRoFile { remote_fd: strs[0].parse::<i32>()?, digest: String::from(digest) })
155     } else {
156         bail!("Unsupported hash algorithm or invalid format: {}", strs[1]);
157     }
158 }
159 
parse_remote_new_ro_dir_option(option: &str) -> Result<OptionRemoteRoDir>160 fn parse_remote_new_ro_dir_option(option: &str) -> Result<OptionRemoteRoDir> {
161     let strs: Vec<&str> = option.split(':').collect();
162     if strs.len() != 3 {
163         bail!("Invalid option: {}", option);
164     }
165     Ok(OptionRemoteRoDir {
166         remote_dir_fd: strs[0].parse::<i32>().unwrap(),
167         mapping_file_path: PathBuf::from(strs[1]),
168         prefix: String::from(strs[2]),
169     })
170 }
171 
from_hex_string(s: &str) -> Result<Vec<u8>>172 fn from_hex_string(s: &str) -> Result<Vec<u8>> {
173     if s.len() % 2 == 1 {
174         bail!("Incomplete hex string: {}", s);
175     } else {
176         let results = (0..s.len())
177             .step_by(2)
178             .map(|i| {
179                 u8::from_str_radix(&s[i..i + 2], 16)
180                     .map_err(|e| anyhow!("Cannot parse hex {}: {}", &s[i..i + 2], e))
181             })
182             .collect::<Result<Vec<_>>>();
183         Ok(results?)
184     }
185 }
186 
new_remote_verified_file_entry( service: file::VirtFdService, remote_fd: i32, expected_digest: &str, ) -> Result<AuthFsEntry>187 fn new_remote_verified_file_entry(
188     service: file::VirtFdService,
189     remote_fd: i32,
190     expected_digest: &str,
191 ) -> Result<AuthFsEntry> {
192     Ok(AuthFsEntry::VerifiedReadonly {
193         reader: LazyVerifiedReadonlyFile::prepare_by_fd(
194             service,
195             remote_fd,
196             from_hex_string(expected_digest)?,
197         ),
198     })
199 }
200 
new_remote_unverified_file_entry( service: file::VirtFdService, remote_fd: i32, file_size: u64, ) -> Result<AuthFsEntry>201 fn new_remote_unverified_file_entry(
202     service: file::VirtFdService,
203     remote_fd: i32,
204     file_size: u64,
205 ) -> Result<AuthFsEntry> {
206     let reader = RemoteFileReader::new(service, remote_fd);
207     Ok(AuthFsEntry::UnverifiedReadonly { reader, file_size })
208 }
209 
new_remote_new_verified_file_entry( service: file::VirtFdService, remote_fd: i32, ) -> Result<AuthFsEntry>210 fn new_remote_new_verified_file_entry(
211     service: file::VirtFdService,
212     remote_fd: i32,
213 ) -> Result<AuthFsEntry> {
214     let remote_file = RemoteFileEditor::new(service.clone(), remote_fd);
215     Ok(AuthFsEntry::VerifiedNew {
216         editor: VerifiedFileEditor::new(remote_file),
217         attr: Attr::new_file(service, remote_fd),
218     })
219 }
220 
new_remote_new_verified_dir_entry( service: file::VirtFdService, remote_fd: i32, ) -> Result<AuthFsEntry>221 fn new_remote_new_verified_dir_entry(
222     service: file::VirtFdService,
223     remote_fd: i32,
224 ) -> Result<AuthFsEntry> {
225     let dir = RemoteDirEditor::new(service.clone(), remote_fd);
226     let attr = Attr::new_dir(service, remote_fd);
227     Ok(AuthFsEntry::VerifiedNewDirectory { dir, attr })
228 }
229 
prepare_root_dir_entries( service: file::VirtFdService, authfs: &mut AuthFs, args: &Args, ) -> Result<()>230 fn prepare_root_dir_entries(
231     service: file::VirtFdService,
232     authfs: &mut AuthFs,
233     args: &Args,
234 ) -> Result<()> {
235     for config in &args.remote_ro_file {
236         authfs.add_entry_at_root_dir(
237             remote_fd_to_path_buf(config.remote_fd),
238             new_remote_verified_file_entry(service.clone(), config.remote_fd, &config.digest)?,
239         )?;
240     }
241 
242     for remote_fd in &args.remote_ro_file_unverified {
243         let remote_fd = *remote_fd;
244         authfs.add_entry_at_root_dir(
245             remote_fd_to_path_buf(remote_fd),
246             new_remote_unverified_file_entry(
247                 service.clone(),
248                 remote_fd,
249                 service.getFileSize(remote_fd)?.try_into()?,
250             )?,
251         )?;
252     }
253 
254     for remote_fd in &args.remote_new_rw_file {
255         let remote_fd = *remote_fd;
256         authfs.add_entry_at_root_dir(
257             remote_fd_to_path_buf(remote_fd),
258             new_remote_new_verified_file_entry(service.clone(), remote_fd)?,
259         )?;
260     }
261 
262     for remote_fd in &args.remote_new_rw_dir {
263         let remote_fd = *remote_fd;
264         authfs.add_entry_at_root_dir(
265             remote_fd_to_path_buf(remote_fd),
266             new_remote_new_verified_dir_entry(service.clone(), remote_fd)?,
267         )?;
268     }
269 
270     for config in &args.remote_ro_dir {
271         let dir_root_inode = authfs.add_entry_at_root_dir(
272             remote_fd_to_path_buf(config.remote_dir_fd),
273             AuthFsEntry::ReadonlyDirectory { dir: InMemoryDir::new() },
274         )?;
275 
276         // Build the directory tree based on the mapping file.
277         let mut reader = File::open(&config.mapping_file_path)?;
278         let proto = FSVerityDigests::parse_from_reader(&mut reader)?;
279         for (path_str, digest) in &proto.digests {
280             if digest.hash_alg != "sha256" {
281                 bail!("Unsupported hash algorithm: {}", digest.hash_alg);
282             }
283 
284             let file_entry = {
285                 let remote_path_str = path_str.strip_prefix(&config.prefix).ok_or_else(|| {
286                     anyhow!("Expect path {} to match prefix {}", path_str, config.prefix)
287                 })?;
288                 AuthFsEntry::VerifiedReadonly {
289                     reader: LazyVerifiedReadonlyFile::prepare_by_path(
290                         service.clone(),
291                         config.remote_dir_fd,
292                         PathBuf::from(remote_path_str),
293                         digest.digest.clone(),
294                     ),
295                 }
296             };
297             authfs.add_entry_at_ro_dir_by_path(dir_root_inode, Path::new(path_str), file_entry)?;
298         }
299     }
300 
301     Ok(())
302 }
303 
remote_fd_to_path_buf(fd: i32) -> PathBuf304 fn remote_fd_to_path_buf(fd: i32) -> PathBuf {
305     PathBuf::from(fd.to_string())
306 }
307 
try_main() -> Result<()>308 fn try_main() -> Result<()> {
309     let args = Args::from_args_safe()?;
310 
311     let log_level = if args.debug { log::Level::Debug } else { log::Level::Info };
312     android_logger::init_once(
313         android_logger::Config::default().with_tag("authfs").with_min_level(log_level),
314     );
315 
316     let service = file::get_rpc_binder_service(args.cid)?;
317     let mut authfs = AuthFs::new(RemoteFsStatsReader::new(service.clone()));
318     prepare_root_dir_entries(service, &mut authfs, &args)?;
319 
320     fusefs::mount_and_enter_message_loop(
321         authfs,
322         &args.mount_point,
323         &args.extra_options,
324         args.thread_number,
325     )?;
326     bail!("Unexpected exit after the handler loop")
327 }
328 
main()329 fn main() {
330     if let Err(e) = try_main() {
331         error!("failed with {:?}", e);
332         std::process::exit(1);
333     }
334 }
335 
336 #[cfg(test)]
337 mod tests {
338     use super::*;
339 
340     #[test]
parse_hex_string()341     fn parse_hex_string() {
342         assert_eq!(from_hex_string("deadbeef").unwrap(), vec![0xde, 0xad, 0xbe, 0xef]);
343         assert_eq!(from_hex_string("DEADBEEF").unwrap(), vec![0xde, 0xad, 0xbe, 0xef]);
344         assert_eq!(from_hex_string("").unwrap(), Vec::<u8>::new());
345 
346         assert!(from_hex_string("deadbee").is_err());
347         assert!(from_hex_string("X").is_err());
348     }
349 }
350