blob: f2638c2f746c7b1f854657afa497c0d02b0fcb4e [file] [log] [blame]
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
use anyhow::{bail, Context, Result};
use log::{debug, error, warn};
use nix::mount::{umount2, MntFlags};
use nix::sys::statfs::{statfs, FsType};
use shared_child::SharedChild;
use std::ffi::{OsStr, OsString};
use std::fs::{remove_dir, OpenOptions};
use std::path::PathBuf;
use std::process::Command;
use std::thread::sleep;
use std::time::{Duration, Instant};
use authfs_aidl_interface::aidl::com::android::virt::fs::AuthFsConfig::{
AuthFsConfig, InputDirFdAnnotation::InputDirFdAnnotation, InputFdAnnotation::InputFdAnnotation,
OutputDirFdAnnotation::OutputDirFdAnnotation, OutputFdAnnotation::OutputFdAnnotation,
};
use authfs_aidl_interface::aidl::com::android::virt::fs::IAuthFs::{BnAuthFs, IAuthFs};
use binder::{self, BinderFeatures, Interface, ParcelFileDescriptor, Status, Strong};
const AUTHFS_BIN: &str = "/system/bin/authfs";
const AUTHFS_SETUP_POLL_INTERVAL_MS: Duration = Duration::from_millis(50);
const AUTHFS_SETUP_TIMEOUT_SEC: Duration = Duration::from_secs(10);
const FUSE_SUPER_MAGIC: FsType = FsType(0x65735546);
/// An `AuthFs` instance is supposed to be backed by an `authfs` process. When the lifetime of the
/// instance is over, it should leave no trace on the system: the process should be terminated, the
/// FUSE should be unmounted, and the mount directory should be deleted.
pub struct AuthFs {
mountpoint: OsString,
process: SharedChild,
}
impl Interface for AuthFs {}
impl IAuthFs for AuthFs {
fn openFile(
&self,
remote_fd_name: i32,
writable: bool,
) -> binder::Result<ParcelFileDescriptor> {
let mut path = PathBuf::from(&self.mountpoint);
path.push(remote_fd_name.to_string());
let file = OpenOptions::new().read(true).write(writable).open(&path).map_err(|e| {
Status::new_service_specific_error_str(
-1,
Some(format!("failed to open {:?} on authfs: {}", &path, e)),
)
})?;
Ok(ParcelFileDescriptor::new(file))
}
fn getMountPoint(&self) -> binder::Result<String> {
if let Some(s) = self.mountpoint.to_str() {
Ok(s.to_string())
} else {
Err(Status::new_service_specific_error_str(-1, Some("Bad string encoding")))
}
}
}
impl AuthFs {
/// Mount an authfs at `mountpoint` with specified FD annotations.
pub fn mount_and_wait(
mountpoint: OsString,
config: &AuthFsConfig,
debuggable: bool,
) -> Result<Strong<dyn IAuthFs>> {
let child = run_authfs(
&mountpoint,
&config.inputFdAnnotations,
&config.outputFdAnnotations,
&config.inputDirFdAnnotations,
&config.outputDirFdAnnotations,
debuggable,
)?;
wait_until_authfs_ready(&child, &mountpoint).inspect_err(|_| match child.wait() {
Ok(status) => debug!("Wait for authfs: {}", status),
Err(e) => warn!("Failed to wait for child: {}", e),
})?;
let authfs = AuthFs { mountpoint, process: child };
Ok(BnAuthFs::new_binder(authfs, BinderFeatures::default()))
}
}
impl Drop for AuthFs {
/// On drop, try to erase all the traces for this authfs mount.
fn drop(&mut self) {
debug!("Dropping AuthFs instance at mountpoint {:?}", &self.mountpoint);
if let Err(e) = self.process.kill() {
error!("Failed to kill authfs: {}", e);
}
match self.process.wait() {
Ok(status) => debug!("authfs exit code: {}", status),
Err(e) => warn!("Failed to wait for authfs: {}", e),
}
// The client may still hold the file descriptors that refer to this filesystem. Use
// MNT_DETACH to detach the mountpoint, and automatically unmount when there is no more
// reference.
if let Err(e) = umount2(self.mountpoint.as_os_str(), MntFlags::MNT_DETACH) {
error!("Failed to umount authfs at {:?}: {}", &self.mountpoint, e)
}
if let Err(e) = remove_dir(&self.mountpoint) {
error!("Failed to clean up mount directory {:?}: {}", &self.mountpoint, e)
}
}
}
fn run_authfs(
mountpoint: &OsStr,
in_file_fds: &[InputFdAnnotation],
out_file_fds: &[OutputFdAnnotation],
in_dir_fds: &[InputDirFdAnnotation],
out_dir_fds: &[OutputDirFdAnnotation],
debuggable: bool,
) -> Result<SharedChild> {
let mut args = vec![mountpoint.to_owned(), OsString::from("--cid=2")];
args.push(OsString::from("-o"));
args.push(OsString::from("fscontext=u:object_r:authfs_fuse:s0"));
for conf in in_file_fds {
// TODO(b/185178698): Many input files need to be signed and verified.
// or can we use debug cert for now, which is better than nothing?
args.push(OsString::from("--remote-ro-file-unverified"));
args.push(OsString::from(conf.fd.to_string()));
}
for conf in out_file_fds {
args.push(OsString::from("--remote-new-rw-file"));
args.push(OsString::from(conf.fd.to_string()));
}
for conf in in_dir_fds {
args.push(OsString::from("--remote-ro-dir"));
args.push(OsString::from(format!("{}:{}:{}", conf.fd, conf.manifestPath, conf.prefix)));
}
for conf in out_dir_fds {
args.push(OsString::from("--remote-new-rw-dir"));
args.push(OsString::from(conf.fd.to_string()));
}
if debuggable {
args.push(OsString::from("--debug"));
}
let mut command = Command::new(AUTHFS_BIN);
command.args(&args);
debug!("Spawn authfs: {:?}", command);
SharedChild::spawn(&mut command).context("Spawn authfs")
}
fn wait_until_authfs_ready(child: &SharedChild, mountpoint: &OsStr) -> Result<()> {
let start_time = Instant::now();
loop {
if is_fuse(mountpoint)? {
break;
}
if let Some(exit_status) = child.try_wait()? {
// If the child has exited, we will never become ready.
bail!("Child has exited: {}", exit_status);
}
if start_time.elapsed() > AUTHFS_SETUP_TIMEOUT_SEC {
let _ignored = child.kill();
bail!("Time out mounting authfs");
}
sleep(AUTHFS_SETUP_POLL_INTERVAL_MS);
}
Ok(())
}
fn is_fuse(path: &OsStr) -> Result<bool> {
Ok(statfs(path)?.filesystem_type() == FUSE_SUPER_MAGIC)
}