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 clap::Parser;
37 use log::error;
38 use protobuf::Message;
39 use std::convert::TryInto;
40 use std::fs::File;
41 use std::num::NonZeroU8;
42 use std::path::{Path, PathBuf};
43
44 mod common;
45 mod file;
46 mod fsstat;
47 mod fsverity;
48 mod fusefs;
49
50 use file::{Attr, InMemoryDir, RemoteDirEditor, RemoteFileEditor, RemoteFileReader};
51 use fsstat::RemoteFsStatsReader;
52 use fsverity::VerifiedFileEditor;
53 use fsverity_digests_proto::fsverity_digests::FSVerityDigests;
54 use fusefs::{AuthFs, AuthFsEntry, LazyVerifiedReadonlyFile};
55
56 #[derive(Parser)]
57 struct Args {
58 /// Mount point of AuthFS.
59 mount_point: PathBuf,
60
61 /// CID of the VM where the service runs.
62 #[clap(long)]
63 cid: u32,
64
65 /// Extra options to FUSE
66 #[clap(short = 'o')]
67 extra_options: Option<String>,
68
69 /// Number of threads to serve FUSE requests.
70 #[clap(short = 'j')]
71 thread_number: Option<NonZeroU8>,
72
73 /// A read-only remote file with integrity check. Can be multiple.
74 ///
75 /// For example, `--remote-ro-file 5:sha256-1234abcd` tells the filesystem to associate the
76 /// file $MOUNTPOINT/5 with a remote FD 5, and has a fs-verity digest with sha256 of the hex
77 /// value 1234abcd.
78 #[clap(long, value_parser = parse_remote_ro_file_option)]
79 remote_ro_file: Vec<OptionRemoteRoFile>,
80
81 /// A read-only remote file without integrity check. Can be multiple.
82 ///
83 /// For example, `--remote-ro-file-unverified 5` tells the filesystem to associate the file
84 /// $MOUNTPOINT/5 with a remote FD 5.
85 #[clap(long)]
86 remote_ro_file_unverified: Vec<i32>,
87
88 /// A new read-writable remote file with integrity check. Can be multiple.
89 ///
90 /// For example, `--remote-new-rw-file 5` tells the filesystem to associate the file
91 /// $MOUNTPOINT/5 with a remote FD 5.
92 #[clap(long)]
93 remote_new_rw_file: Vec<i32>,
94
95 /// A read-only directory that represents a remote directory. The directory view is constructed
96 /// and finalized during the filesystem initialization based on the provided mapping file
97 /// (which is a serialized protobuf of android.security.fsverity.FSVerityDigests, which
98 /// essentially provides <file path, fs-verity digest> mappings of exported files). The mapping
99 /// file is supposed to come from a trusted location in order to provide a trusted view as well
100 /// as verified access of included files with their fs-verity digest. Not all files on the
101 /// remote host may be included in the mapping file, so the directory view may be partial. The
102 /// directory structure won't change throughout the filesystem lifetime.
103 ///
104 /// For example, `--remote-ro-dir 5:/path/to/mapping:prefix/` tells the filesystem to
105 /// construct a directory structure defined in the mapping file at $MOUNTPOINT/5, which may
106 /// include a file like /5/system/framework/framework.jar. "prefix/" tells the filesystem to
107 /// strip the path (e.g. "system/") from the mount point to match the expected location of the
108 /// remote FD (e.g. a directory FD of "/system" in the remote).
109 #[clap(long, value_parser = parse_remote_new_ro_dir_option)]
110 remote_ro_dir: Vec<OptionRemoteRoDir>,
111
112 /// A new directory that is assumed empty in the backing filesystem. New files created in this
113 /// directory are integrity-protected in the same way as --remote-new-verified-file. Can be
114 /// multiple.
115 ///
116 /// For example, `--remote-new-rw-dir 5` tells the filesystem to associate $MOUNTPOINT/5
117 /// with a remote dir FD 5.
118 #[clap(long)]
119 remote_new_rw_dir: Vec<i32>,
120
121 /// Enable debugging features.
122 #[clap(long)]
123 debug: bool,
124 }
125
126 #[derive(Clone)]
127 struct OptionRemoteRoFile {
128 /// ID to refer to the remote file.
129 remote_fd: i32,
130
131 /// Expected fs-verity digest (with sha256) for the remote file.
132 digest: String,
133 }
134
135 #[derive(Clone)]
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
new_remote_verified_file_entry( service: file::VirtFdService, remote_fd: i32, expected_digest: &str, ) -> Result<AuthFsEntry>172 fn new_remote_verified_file_entry(
173 service: file::VirtFdService,
174 remote_fd: i32,
175 expected_digest: &str,
176 ) -> Result<AuthFsEntry> {
177 Ok(AuthFsEntry::VerifiedReadonly {
178 reader: LazyVerifiedReadonlyFile::prepare_by_fd(
179 service,
180 remote_fd,
181 hex::decode(expected_digest)?,
182 ),
183 })
184 }
185
new_remote_unverified_file_entry( service: file::VirtFdService, remote_fd: i32, file_size: u64, ) -> Result<AuthFsEntry>186 fn new_remote_unverified_file_entry(
187 service: file::VirtFdService,
188 remote_fd: i32,
189 file_size: u64,
190 ) -> Result<AuthFsEntry> {
191 let reader = RemoteFileReader::new(service, remote_fd);
192 Ok(AuthFsEntry::UnverifiedReadonly { reader, file_size })
193 }
194
new_remote_new_verified_file_entry( service: file::VirtFdService, remote_fd: i32, ) -> Result<AuthFsEntry>195 fn new_remote_new_verified_file_entry(
196 service: file::VirtFdService,
197 remote_fd: i32,
198 ) -> Result<AuthFsEntry> {
199 let remote_file = RemoteFileEditor::new(service.clone(), remote_fd);
200 Ok(AuthFsEntry::VerifiedNew {
201 editor: VerifiedFileEditor::new(remote_file),
202 attr: Attr::new_file(service, remote_fd),
203 })
204 }
205
new_remote_new_verified_dir_entry( service: file::VirtFdService, remote_fd: i32, ) -> Result<AuthFsEntry>206 fn new_remote_new_verified_dir_entry(
207 service: file::VirtFdService,
208 remote_fd: i32,
209 ) -> Result<AuthFsEntry> {
210 let dir = RemoteDirEditor::new(service.clone(), remote_fd);
211 let attr = Attr::new_dir(service, remote_fd);
212 Ok(AuthFsEntry::VerifiedNewDirectory { dir, attr })
213 }
214
prepare_root_dir_entries( service: file::VirtFdService, authfs: &mut AuthFs, args: &Args, ) -> Result<()>215 fn prepare_root_dir_entries(
216 service: file::VirtFdService,
217 authfs: &mut AuthFs,
218 args: &Args,
219 ) -> Result<()> {
220 for config in &args.remote_ro_file {
221 authfs.add_entry_at_root_dir(
222 remote_fd_to_path_buf(config.remote_fd),
223 new_remote_verified_file_entry(service.clone(), config.remote_fd, &config.digest)?,
224 )?;
225 }
226
227 for remote_fd in &args.remote_ro_file_unverified {
228 let remote_fd = *remote_fd;
229 authfs.add_entry_at_root_dir(
230 remote_fd_to_path_buf(remote_fd),
231 new_remote_unverified_file_entry(
232 service.clone(),
233 remote_fd,
234 service.getFileSize(remote_fd)?.try_into()?,
235 )?,
236 )?;
237 }
238
239 for remote_fd in &args.remote_new_rw_file {
240 let remote_fd = *remote_fd;
241 authfs.add_entry_at_root_dir(
242 remote_fd_to_path_buf(remote_fd),
243 new_remote_new_verified_file_entry(service.clone(), remote_fd)?,
244 )?;
245 }
246
247 for remote_fd in &args.remote_new_rw_dir {
248 let remote_fd = *remote_fd;
249 authfs.add_entry_at_root_dir(
250 remote_fd_to_path_buf(remote_fd),
251 new_remote_new_verified_dir_entry(service.clone(), remote_fd)?,
252 )?;
253 }
254
255 for config in &args.remote_ro_dir {
256 let dir_root_inode = authfs.add_entry_at_root_dir(
257 remote_fd_to_path_buf(config.remote_dir_fd),
258 AuthFsEntry::ReadonlyDirectory { dir: InMemoryDir::new() },
259 )?;
260
261 // Build the directory tree based on the mapping file.
262 let mut reader = File::open(&config.mapping_file_path)?;
263 let proto = FSVerityDigests::parse_from_reader(&mut reader)?;
264 for (path_str, digest) in &proto.digests {
265 if digest.hash_alg != "sha256" {
266 bail!("Unsupported hash algorithm: {}", digest.hash_alg);
267 }
268
269 let file_entry = {
270 let remote_path_str = path_str.strip_prefix(&config.prefix).ok_or_else(|| {
271 anyhow!("Expect path {} to match prefix {}", path_str, config.prefix)
272 })?;
273 AuthFsEntry::VerifiedReadonly {
274 reader: LazyVerifiedReadonlyFile::prepare_by_path(
275 service.clone(),
276 config.remote_dir_fd,
277 PathBuf::from(remote_path_str),
278 digest.digest.clone(),
279 ),
280 }
281 };
282 authfs.add_entry_at_ro_dir_by_path(dir_root_inode, Path::new(path_str), file_entry)?;
283 }
284 }
285
286 Ok(())
287 }
288
remote_fd_to_path_buf(fd: i32) -> PathBuf289 fn remote_fd_to_path_buf(fd: i32) -> PathBuf {
290 PathBuf::from(fd.to_string())
291 }
292
try_main() -> Result<()>293 fn try_main() -> Result<()> {
294 let args = Args::parse();
295
296 let log_level = if args.debug { log::LevelFilter::Debug } else { log::LevelFilter::Info };
297 android_logger::init_once(
298 android_logger::Config::default().with_tag("authfs").with_max_level(log_level),
299 );
300
301 let service = file::get_rpc_binder_service(args.cid)?;
302 let mut authfs = AuthFs::new(RemoteFsStatsReader::new(service.clone()));
303 prepare_root_dir_entries(service, &mut authfs, &args)?;
304
305 fusefs::mount_and_enter_message_loop(
306 authfs,
307 &args.mount_point,
308 &args.extra_options,
309 args.thread_number,
310 )?;
311 bail!("Unexpected exit after the handler loop")
312 }
313
main()314 fn main() {
315 if let Err(e) = try_main() {
316 error!("failed with {:?}", e);
317 std::process::exit(1);
318 }
319 }
320