blob: ce9aaf8890837a5c6a30294f716af0728c2021b6 [file] [log] [blame]
Victor Hsiehf7de50d2021-07-23 16:02:04 -07001/*
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::{bail, Context, Result};
18use log::warn;
19use minijail::Minijail;
20use nix::sys::statfs::{statfs, FsType};
21use std::fs::{File, OpenOptions};
22use std::path::Path;
23use std::thread::sleep;
24use std::time::{Duration, Instant};
25
26const AUTHFS_BIN: &str = "/system/bin/authfs";
27const AUTHFS_SETUP_POLL_INTERVAL_MS: Duration = Duration::from_millis(50);
28const AUTHFS_SETUP_TIMEOUT_SEC: Duration = Duration::from_secs(10);
29const FUSE_SUPER_MAGIC: FsType = FsType(0x65735546);
30
31/// The number that hints the future file descriptor. These are not really file descriptor, but
32/// represents the file descriptor number to pass to the task.
33pub type PseudoRawFd = i32;
34
35/// Annotation of input file descriptor.
36#[derive(Debug)]
37pub struct InFdAnnotation {
38 /// A number/file descriptor that is supposed to represent a remote file.
39 pub fd: PseudoRawFd,
40
41 /// The file size of the remote file. Remote input files are supposed to be immutable and
42 /// to be verified with fs-verity by authfs.
43 pub file_size: u64,
44}
45
46/// Annotation of output file descriptor.
47#[derive(Debug)]
48pub struct OutFdAnnotation {
49 /// A number/file descriptor that is supposed to represent a remote file.
50 pub fd: PseudoRawFd,
51}
52
53/// An `AuthFs` instance is supposed to be backed by the `authfs` process. When the lifetime of the
54/// instance is over, the process is terminated and the FUSE is unmounted.
55pub struct AuthFs {
56 mountpoint: String,
57 jail: Minijail,
58}
59
60impl AuthFs {
61 /// Mount an authfs at `mountpoint` with specified FD annotations.
62 pub fn mount_and_wait(
63 mountpoint: &str,
64 in_fds: &[InFdAnnotation],
65 out_fds: &[OutFdAnnotation],
66 debuggable: bool,
67 ) -> Result<AuthFs> {
68 let jail = jail_authfs(mountpoint, in_fds, out_fds, debuggable)?;
69 wait_until_authfs_ready(mountpoint)?;
70 Ok(AuthFs { mountpoint: mountpoint.to_string(), jail })
71 }
72
73 /// Open a file at authfs' root directory.
74 pub fn open_file(&self, basename: PseudoRawFd, writable: bool) -> Result<File> {
75 OpenOptions::new()
76 .read(true)
77 .write(writable)
78 .open(format!("{}/{}", self.mountpoint, basename))
79 .with_context(|| format!("open authfs file {}", basename))
80 }
81}
82
83impl Drop for AuthFs {
84 fn drop(&mut self) {
85 if let Err(e) = self.jail.kill() {
86 if !matches!(e, minijail::Error::Killed(_)) {
87 warn!("Failed to kill authfs: {}", e);
88 }
89 }
90 }
91}
92
93fn jail_authfs(
94 mountpoint: &str,
95 in_fds: &[InFdAnnotation],
96 out_fds: &[OutFdAnnotation],
97 debuggable: bool,
98) -> Result<Minijail> {
99 // TODO(b/185175567): Run in a more restricted sandbox.
100 let jail = Minijail::new()?;
101
102 let mut args = vec![
103 AUTHFS_BIN.to_string(),
104 mountpoint.to_string(),
105 "--cid=2".to_string(), // Always use host unless we need to support other cases
106 ];
107 for conf in in_fds {
108 // TODO(b/185178698): Many input files need to be signed and verified.
109 // or can we use debug cert for now, which is better than nothing?
110 args.push("--remote-ro-file-unverified".to_string());
111 args.push(format!("{}:{}:{}", conf.fd, conf.fd, conf.file_size));
112 }
113 for conf in out_fds {
114 args.push("--remote-new-rw-file".to_string());
115 args.push(format!("{}:{}", conf.fd, conf.fd));
116 }
117
118 let preserve_fds = if debuggable {
119 vec![1, 2] // inherit/redirect stdout/stderr for debugging
120 } else {
121 vec![]
122 };
123
124 let _pid = jail.run(Path::new(AUTHFS_BIN), &preserve_fds, &args)?;
125 Ok(jail)
126}
127
128fn wait_until_authfs_ready(mountpoint: &str) -> Result<()> {
129 let start_time = Instant::now();
130 loop {
131 if is_fuse(mountpoint)? {
132 break;
133 }
134 if start_time.elapsed() > AUTHFS_SETUP_TIMEOUT_SEC {
135 bail!("Time out mounting authfs");
136 }
137 sleep(AUTHFS_SETUP_POLL_INTERVAL_MS);
138 }
139 Ok(())
140}
141
142fn is_fuse(path: &str) -> Result<bool> {
143 Ok(statfs(path)?.filesystem_type() == FUSE_SUPER_MAGIC)
144}