1 /* 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 17 //! compsvc is a service to run computational tasks in a PVM upon request. It is able to set up 18 //! file descriptors backed by fd_server and pass the file descriptors to the actual tasks for 19 //! read/write. The service also attempts to sandbox the execution so that one task cannot leak or 20 //! impact future tasks. 21 //! 22 //! Example: 23 //! $ compsvc /system/bin/sleep 24 //! 25 //! The current architecture / process hierarchy looks like: 26 //! - compsvc (handle requests) 27 //! - compsvc_worker (for environment setup) 28 //! - authfs (fd translation) 29 //! - actual task 30 31 use anyhow::{bail, Context, Result}; 32 use log::error; 33 use minijail::{self, Minijail}; 34 use std::path::PathBuf; 35 36 use compos_aidl_interface::aidl::com::android::compos::ICompService::{ 37 BnCompService, ICompService, 38 }; 39 use compos_aidl_interface::aidl::com::android::compos::Metadata::Metadata; 40 use compos_aidl_interface::binder::{ 41 add_service, BinderFeatures, Interface, ProcessState, Result as BinderResult, Status, 42 StatusCode, Strong, 43 }; 44 45 const SERVICE_NAME: &str = "compsvc"; 46 // TODO(b/161470604): Move the executable into an apex. 47 const WORKER_BIN: &str = "/system/bin/compsvc_worker"; 48 // TODO: Replace with a valid directory setup in the VM. 49 const AUTHFS_MOUNTPOINT: &str = "/data/local/tmp/authfs_mnt"; 50 51 struct CompService { 52 worker_bin: PathBuf, 53 task_bin: String, 54 debuggable: bool, 55 } 56 57 impl CompService { 58 pub fn new_binder(service: CompService) -> Strong<dyn ICompService> { 59 BnCompService::new_binder(service, BinderFeatures::default()) 60 } 61 62 fn run_worker_in_jail_and_wait(&self, args: &[String]) -> Result<(), minijail::Error> { 63 let mut jail = Minijail::new()?; 64 65 // TODO(b/185175567): New user and uid namespace when supported. Run as nobody. 66 // New mount namespace to isolate the FUSE mount. 67 jail.namespace_vfs(); 68 69 let inheritable_fds = if self.debuggable { 70 vec![1, 2] // inherit/redirect stdout/stderr for debugging 71 } else { 72 vec![] 73 }; 74 let _pid = jail.run(&self.worker_bin, &inheritable_fds, &args)?; 75 jail.wait() 76 } 77 78 fn build_worker_args(&self, args: &[String], metadata: &Metadata) -> Vec<String> { 79 let mut worker_args = vec![ 80 WORKER_BIN.to_string(), 81 "--authfs-root".to_string(), 82 AUTHFS_MOUNTPOINT.to_string(), 83 ]; 84 for annotation in &metadata.input_fd_annotations { 85 worker_args.push("--in-fd".to_string()); 86 worker_args.push(format!("{}:{}", annotation.fd, annotation.file_size)); 87 } 88 for annotation in &metadata.output_fd_annotations { 89 worker_args.push("--out-fd".to_string()); 90 worker_args.push(annotation.fd.to_string()); 91 } 92 if self.debuggable { 93 worker_args.push("--debug".to_string()); 94 } 95 worker_args.push("--".to_string()); 96 97 // Do not accept arbitrary code execution. We want to execute some specific task of this 98 // service. Use the associated executable. 99 worker_args.push(self.task_bin.clone()); 100 worker_args.extend_from_slice(&args[1..]); 101 worker_args 102 } 103 } 104 105 impl Interface for CompService {} 106 107 impl ICompService for CompService { 108 fn execute(&self, args: &[String], metadata: &Metadata) -> BinderResult<i8> { 109 let worker_args = self.build_worker_args(args, metadata); 110 111 match self.run_worker_in_jail_and_wait(&worker_args) { 112 Ok(_) => Ok(0), // TODO(b/161471326): Sign the output on succeed. 113 Err(minijail::Error::ReturnCode(exit_code)) => { 114 error!("Task failed with exit code {}", exit_code); 115 Err(Status::from(StatusCode::FAILED_TRANSACTION)) 116 } 117 Err(e) => { 118 error!("Unexpected error: {}", e); 119 Err(Status::from(StatusCode::UNKNOWN_ERROR)) 120 } 121 } 122 } 123 } 124 125 fn parse_args() -> Result<CompService> { 126 #[rustfmt::skip] 127 let matches = clap::App::new("compsvc") 128 .arg(clap::Arg::with_name("debug") 129 .long("debug")) 130 .arg(clap::Arg::with_name("task_bin") 131 .required(true)) 132 .get_matches(); 133 134 Ok(CompService { 135 task_bin: matches.value_of("task_bin").unwrap().to_string(), 136 worker_bin: PathBuf::from(WORKER_BIN), 137 debuggable: matches.is_present("debug"), 138 }) 139 } 140 141 fn main() -> Result<()> { 142 android_logger::init_once( 143 android_logger::Config::default().with_tag("compsvc").with_min_level(log::Level::Debug), 144 ); 145 146 let service = parse_args()?; 147 148 ProcessState::start_thread_pool(); 149 // TODO: switch to remote binder 150 add_service(SERVICE_NAME, CompService::new_binder(service).as_binder()) 151 .with_context(|| format!("Failed to register service {}", SERVICE_NAME))?; 152 ProcessState::join_thread_pool(); 153 bail!("Unexpected exit after join_thread_pool") 154 } 155