/* * 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 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 Result<(i32, FdConfig)> { let result: Result, _> = arg.split(':').map(|x| x.parse::()).collect(); let fds = result?; if fds.len() > 2 { bail!("Too many options: {}", arg); } Ok(( fds[0], FdConfig::Readonly { file: take_fd_ownership(fds[0])?.into(), // Alternative metadata source, if provided alt_metadata: fds .get(1) .map(|fd| take_fd_ownership(*fd)) .transpose()? .and_then(|f| parse_fsverity_metadata(f.into()).ok()), }, )) } #[derive(Parser)] struct Args { /// Read-only FD of file, with optional FD of corresponding .fsv_meta, joined with a ':'. /// Example: "1:2", "3". #[clap(long)] ro_fds: Vec, /// Read-writable FD of file #[clap(long)] rw_fds: Vec, /// Read-only FD of directory #[clap(long)] ro_dirs: Vec, /// Read-writable FD of directory #[clap(long)] rw_dirs: Vec, /// A pipe FD for signaling the other end once ready #[clap(long)] ready_fd: Option, } /// Convert argument strings and integers to a form that is easier to use and handles ownership. fn convert_args(args: Args) -> Result<(BTreeMap, Option)> { let mut fd_pool = BTreeMap::new(); for arg in args.ro_fds { let (fd, config) = parse_arg_ro_fds(&arg)?; fd_pool.insert(fd, config); } for fd in args.rw_fds { let file: File = take_fd_ownership(fd)?.into(); if file.metadata()?.len() > 0 { bail!("File is expected to be empty"); } fd_pool.insert(fd, FdConfig::ReadWrite(file)); } for fd in args.ro_dirs { fd_pool.insert(fd, FdConfig::InputDir(take_fd_ownership(fd)?)); } for fd in args.rw_dirs { fd_pool.insert(fd, FdConfig::OutputDir(take_fd_ownership(fd)?)); } let ready_fd = args.ready_fd.map(take_fd_ownership).transpose()?; Ok((fd_pool, ready_fd)) } fn main() -> Result<()> { // SAFETY: This is very early in the process. Nobody has taken ownership of the inherited FDs // yet. unsafe { rustutils::inherited_fd::init_once()? }; android_logger::init_once( android_logger::Config::default() .with_tag("fd_server") .with_max_level(log::LevelFilter::Debug), ); let args = Args::parse(); let (fd_pool, mut ready_fd) = convert_args(args)?; // Allow open/create/mkdir from authfs to create with expecting mode. It's possible to still // use a custom mask on creation, then report the actual file mode back to authfs. But there // is no demand now. let old_umask = umask(Mode::empty()); debug!("Setting umask to 0 (old: {:03o})", old_umask.bits()); debug!("fd_server is starting as a rpc service."); let service = FdService::new_binder(fd_pool).as_binder(); // TODO(b/259920193): Only accept connections from the intended guest VM. let (server, _) = RpcServer::new_vsock(service, libc::VMADDR_CID_ANY, RPC_SERVICE_PORT)?; debug!("fd_server is ready"); // Close the ready-fd if we were given one to signal our readiness. drop(ready_fd.take()); server.join(); Ok(()) } #[cfg(test)] mod tests { use super::*; use clap::CommandFactory; #[test] fn verify_args() { // Check that the command parsing has been configured in a valid way. Args::command().debug_assert(); } }