Merge "authfs: fd_server to expose local FD via binder interface"
diff --git a/authfs/aidl/Android.bp b/authfs/aidl/Android.bp
new file mode 100644
index 0000000..8cb9dcf
--- /dev/null
+++ b/authfs/aidl/Android.bp
@@ -0,0 +1,10 @@
+aidl_interface {
+ name: "authfs_aidl_interface",
+ unstable: true,
+ srcs: ["com/android/virt/fs/*.aidl"],
+ backend: {
+ rust: {
+ enabled: true,
+ },
+ },
+}
diff --git a/authfs/aidl/com/android/virt/fs/IVirtFdService.aidl b/authfs/aidl/com/android/virt/fs/IVirtFdService.aidl
new file mode 100644
index 0000000..628ee3c
--- /dev/null
+++ b/authfs/aidl/com/android/virt/fs/IVirtFdService.aidl
@@ -0,0 +1,48 @@
+/*
+ * 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.
+ */
+
+package com.android.virt.fs;
+
+/** {@hide} */
+interface IVirtFdService {
+ /** Error when the requesting FD is unknown. */
+ const int ERROR_UNKNOWN_FD = 1;
+
+ /**
+ * Error when I/O fails. This can happen when actual I/O error happens to the backing file,
+ * when the given offset or size are invalid, or any problems that can fail a read/write
+ * request.
+ */
+ const int ERROR_IO = 2;
+
+ /** Maximum content size that the service allows the client to request. */
+ const int MAX_REQUESTING_DATA = 16384;
+
+ /**
+ * Returns the content of the given file ID, from the offset, for the amount of requested size
+ * or until EOF.
+ */
+ byte[] readFile(int id, long offset, int size);
+
+ /**
+ * Returns the content of fs-verity compatible Merkle tree of the given file ID, from the
+ * offset, for the amount of requested size or until EOF.
+ */
+ byte[] readFsverityMerkleTree(int id, long offset, int size);
+
+ /** Returns the fs-verity signature of the given file ID. */
+ byte[] readFsveritySignature(int id);
+}
diff --git a/authfs/fd_server/Android.bp b/authfs/fd_server/Android.bp
new file mode 100644
index 0000000..1099fd9
--- /dev/null
+++ b/authfs/fd_server/Android.bp
@@ -0,0 +1,12 @@
+rust_binary {
+ name: "fd_server",
+ srcs: ["src/main.rs"],
+ rustlibs: [
+ "authfs_aidl_interface-rust",
+ "libanyhow",
+ "libbinder_rs",
+ "libclap",
+ "liblibc",
+ "liblog_rust",
+ ],
+}
diff --git a/authfs/fd_server/src/main.rs b/authfs/fd_server/src/main.rs
new file mode 100644
index 0000000..cbd7712
--- /dev/null
+++ b/authfs/fd_server/src/main.rs
@@ -0,0 +1,220 @@
+/*
+ * 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.
+ */
+
+//! This program is a constrained file/FD server to serve file requests through a remote[1] binder
+//! service. The file server is not designed to serve arbitrary file paths in the filesystem. On
+//! the contrary, the server should be configured to start with already opened FDs, and serve the
+//! client's request against the FDs
+//!
+//! For example, `exec 9</path/to/file fd_server --ro-fds 9` starts the binder service. A client
+//! client can then request the content of file 9 by offset and size.
+//!
+//! [1] Since the remote binder is not ready, this currently implementation uses local binder
+//! first.
+
+use std::cmp::min;
+use std::collections::BTreeMap;
+use std::convert::TryInto;
+use std::ffi::CString;
+use std::fs::File;
+use std::io;
+use std::os::unix::fs::FileExt;
+use std::os::unix::io::FromRawFd;
+
+use anyhow::{bail, Context, Result};
+use binder::IBinder; // TODO(178852354): remove once set_requesting_sid is exposed in the API.
+use log::{debug, error};
+
+use authfs_aidl_interface::aidl::com::android::virt::fs::IVirtFdService::{
+ BnVirtFdService, IVirtFdService, ERROR_IO, ERROR_UNKNOWN_FD, MAX_REQUESTING_DATA,
+};
+use authfs_aidl_interface::binder::{
+ add_service, ExceptionCode, Interface, ProcessState, Result as BinderResult, Status, Strong,
+};
+
+const SERVICE_NAME: &str = "authfs_fd_server";
+
+fn new_binder_exception<T: AsRef<str>>(exception: ExceptionCode, message: T) -> Status {
+ Status::new_exception(exception, CString::new(message.as_ref()).as_deref().ok())
+}
+
+fn validate_and_cast_offset(offset: i64) -> Result<u64, Status> {
+ offset.try_into().map_err(|_| {
+ new_binder_exception(ExceptionCode::ILLEGAL_ARGUMENT, format!("Invalid offset: {}", offset))
+ })
+}
+
+fn validate_and_cast_size(size: i32) -> Result<usize, Status> {
+ if size > MAX_REQUESTING_DATA {
+ Err(new_binder_exception(
+ ExceptionCode::ILLEGAL_ARGUMENT,
+ format!("Unexpectedly large size: {}", size),
+ ))
+ } else {
+ size.try_into().map_err(|_| {
+ new_binder_exception(ExceptionCode::ILLEGAL_ARGUMENT, format!("Invalid size: {}", size))
+ })
+ }
+}
+
+/// Configuration of a read-only file to serve by this server. The file is supposed to be verifiable
+/// with the associated fs-verity metadata.
+struct ReadonlyFdConfig {
+ /// The file to read from. fs-verity metadata can be retrieved from this file's FD.
+ file: File,
+
+ /// Alternative Merkle tree stored in another file.
+ alt_merkle_file: Option<File>,
+
+ /// Alternative signature stored in another file.
+ alt_signature_file: Option<File>,
+}
+
+struct FdService {
+ /// A pool of read-only files
+ fd_pool: BTreeMap<i32, ReadonlyFdConfig>,
+}
+
+impl FdService {
+ pub fn new_binder(fd_pool: BTreeMap<i32, ReadonlyFdConfig>) -> Strong<dyn IVirtFdService> {
+ let result = BnVirtFdService::new_binder(FdService { fd_pool });
+ result.as_binder().set_requesting_sid(false);
+ result
+ }
+
+ fn get_file_config(&self, id: i32) -> BinderResult<&ReadonlyFdConfig> {
+ self.fd_pool.get(&id).ok_or_else(|| Status::from(ERROR_UNKNOWN_FD))
+ }
+
+ fn get_file(&self, id: i32) -> BinderResult<&File> {
+ Ok(&self.get_file_config(id)?.file)
+ }
+}
+
+impl Interface for FdService {}
+
+impl IVirtFdService for FdService {
+ fn readFile(&self, id: i32, offset: i64, size: i32) -> BinderResult<Vec<u8>> {
+ let size: usize = validate_and_cast_size(size)?;
+ let offset: u64 = validate_and_cast_offset(offset)?;
+
+ read_into_buf(self.get_file(id)?, size, offset).map_err(|e| {
+ error!("readFile: read error: {}", e);
+ Status::from(ERROR_IO)
+ })
+ }
+
+ fn readFsverityMerkleTree(&self, id: i32, offset: i64, size: i32) -> BinderResult<Vec<u8>> {
+ let size: usize = validate_and_cast_size(size)?;
+ let offset: u64 = validate_and_cast_offset(offset)?;
+
+ if let Some(file) = &self.get_file_config(id)?.alt_merkle_file {
+ read_into_buf(&file, size, offset).map_err(|e| {
+ error!("readFsverityMerkleTree: read error: {}", e);
+ Status::from(ERROR_IO)
+ })
+ } else {
+ // TODO(victorhsieh) retrieve from the fd when the new ioctl is ready
+ Err(new_binder_exception(ExceptionCode::UNSUPPORTED_OPERATION, "Not implemented yet"))
+ }
+ }
+
+ fn readFsveritySignature(&self, id: i32) -> BinderResult<Vec<u8>> {
+ if let Some(file) = &self.get_file_config(id)?.alt_signature_file {
+ // Supposedly big enough buffer size to store signature.
+ let size = MAX_REQUESTING_DATA as usize;
+ read_into_buf(&file, size, 0).map_err(|e| {
+ error!("readFsveritySignature: read error: {}", e);
+ Status::from(ERROR_IO)
+ })
+ } else {
+ // TODO(victorhsieh) retrieve from the fd when the new ioctl is ready
+ Err(new_binder_exception(ExceptionCode::UNSUPPORTED_OPERATION, "Not implemented yet"))
+ }
+ }
+}
+
+fn read_into_buf(file: &File, max_size: usize, offset: u64) -> io::Result<Vec<u8>> {
+ let remaining = file.metadata()?.len().saturating_sub(offset);
+ let buf_size = min(remaining, max_size as u64) as usize;
+ let mut buf = vec![0; buf_size];
+ file.read_exact_at(&mut buf, offset)?;
+ Ok(buf)
+}
+
+fn is_fd_valid(fd: i32) -> bool {
+ // SAFETY: a query-only syscall
+ let retval = unsafe { libc::fcntl(fd, libc::F_GETFD) };
+ retval >= 0
+}
+
+fn fd_to_file(fd: i32) -> Result<File> {
+ if !is_fd_valid(fd) {
+ bail!("Bad FD: {}", fd);
+ }
+ // SAFETY: The caller is supposed to provide valid FDs to this process.
+ Ok(unsafe { File::from_raw_fd(fd) })
+}
+
+fn parse_arg_ro_fds(arg: &str) -> Result<(i32, ReadonlyFdConfig)> {
+ let result: Result<Vec<i32>, _> = arg.split(':').map(|x| x.parse::<i32>()).collect();
+ let fds = result?;
+ if fds.len() > 3 {
+ bail!("Too many options: {}", arg);
+ }
+
+ Ok((
+ fds[0],
+ ReadonlyFdConfig {
+ file: fd_to_file(fds[0])?,
+ alt_merkle_file: fds.get(1).map(|fd| fd_to_file(*fd)).transpose()?,
+ alt_signature_file: fds.get(2).map(|fd| fd_to_file(*fd)).transpose()?,
+ },
+ ))
+}
+
+fn parse_args() -> Result<BTreeMap<i32, ReadonlyFdConfig>> {
+ #[rustfmt::skip]
+ let matches = clap::App::new("fd_server")
+ .arg(clap::Arg::with_name("ro-fds")
+ .long("ro-fds")
+ .required(true)
+ .multiple(true)
+ .number_of_values(1))
+ .get_matches();
+
+ let mut fd_pool = BTreeMap::new();
+ if let Some(args) = matches.values_of("ro-fds") {
+ for arg in args {
+ let (fd, config) = parse_arg_ro_fds(arg)?;
+ fd_pool.insert(fd, config);
+ }
+ }
+ Ok(fd_pool)
+}
+
+fn main() -> Result<()> {
+ let fd_pool = parse_args()?;
+
+ ProcessState::start_thread_pool();
+
+ add_service(SERVICE_NAME, FdService::new_binder(fd_pool).as_binder())
+ .with_context(|| format!("Failed to register service {}", SERVICE_NAME))?;
+ debug!("fd_server is running.");
+
+ ProcessState::join_thread_pool();
+ bail!("Unexpected exit after join_thread_pool")
+}