Merge "Fix type error"
diff --git a/compos/Android.bp b/compos/Android.bp
new file mode 100644
index 0000000..ac69a52
--- /dev/null
+++ b/compos/Android.bp
@@ -0,0 +1,41 @@
+rust_binary {
+    name: "pvm_exec",
+    srcs: ["src/pvm_exec.rs"],
+    rustlibs: [
+        "compos_aidl_interface-rust",
+        "libanyhow",
+        "libclap",
+        "liblibc",
+        "liblog_rust",
+        "libminijail_rust",
+        "libnix",
+        "libscopeguard",
+    ],
+}
+
+rust_binary {
+    name: "compsvc",
+    srcs: ["src/compsvc.rs"],
+    rustlibs: [
+        "compos_aidl_interface-rust",
+        "libandroid_logger",
+        "libanyhow",
+        "libclap",
+        "liblog_rust",
+        "libminijail_rust",
+    ],
+}
+
+rust_binary {
+    name: "compsvc_worker",
+    srcs: ["src/compsvc_worker.rs"],
+    rustlibs: [
+        "libandroid_logger",
+        "libanyhow",
+        "libclap",
+        "liblog_rust",
+        "libminijail_rust",
+        "libnix",
+        "libscopeguard",
+    ],
+}
diff --git a/compos/aidl/Android.bp b/compos/aidl/Android.bp
new file mode 100644
index 0000000..8737d63
--- /dev/null
+++ b/compos/aidl/Android.bp
@@ -0,0 +1,12 @@
+aidl_interface {
+    name: "compos_aidl_interface",
+    unstable: true,
+    srcs: [
+        "com/android/compos/*.aidl",
+    ],
+    backend: {
+        rust: {
+            enabled: true,
+        },
+    },
+}
diff --git a/compos/aidl/com/android/compos/ICompService.aidl b/compos/aidl/com/android/compos/ICompService.aidl
new file mode 100644
index 0000000..0e18442
--- /dev/null
+++ b/compos/aidl/com/android/compos/ICompService.aidl
@@ -0,0 +1,35 @@
+/*
+ * 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.compos;
+
+import com.android.compos.Metadata;
+
+/** {@hide} */
+interface ICompService {
+    /**
+     * Execute a command composed of the args, in a context that may be specified in the Metadata,
+     * e.g. with file descriptors pre-opened. The service is responsible to decide what executables
+     * it may run.
+     *
+     * @param args The command line arguments to run. The 0-th args is normally the program name,
+     *             which may not be used by the service. The service may be configured to always use
+     *             a fixed executable, or possibly use the 0-th args are the executable lookup hint.
+     * @param metadata Additional information of the execution
+     * @return exit code of the program
+     */
+    byte execute(in String[] args, in Metadata metadata);
+}
diff --git a/compos/aidl/com/android/compos/InputFdAnnotation.aidl b/compos/aidl/com/android/compos/InputFdAnnotation.aidl
new file mode 100644
index 0000000..44a5591
--- /dev/null
+++ b/compos/aidl/com/android/compos/InputFdAnnotation.aidl
@@ -0,0 +1,29 @@
+/*
+ * 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.compos;
+
+/** {@hide} */
+parcelable InputFdAnnotation {
+    /**
+     * File descriptor number to be passed to the program.  This is also the same file descriptor
+     * number used in the backend server.
+     */
+    int fd;
+
+    /** The actual file size in bytes of the backing file to be read. */
+    long file_size;
+}
diff --git a/compos/aidl/com/android/compos/Metadata.aidl b/compos/aidl/com/android/compos/Metadata.aidl
new file mode 100644
index 0000000..a15214d
--- /dev/null
+++ b/compos/aidl/com/android/compos/Metadata.aidl
@@ -0,0 +1,26 @@
+/*
+ * 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.compos;
+
+import com.android.compos.InputFdAnnotation;
+import com.android.compos.OutputFdAnnotation;
+
+/** {@hide} */
+parcelable Metadata {
+    InputFdAnnotation[] input_fd_annotations;
+    OutputFdAnnotation[] output_fd_annotations;
+}
diff --git a/compos/aidl/com/android/compos/OutputFdAnnotation.aidl b/compos/aidl/com/android/compos/OutputFdAnnotation.aidl
new file mode 100644
index 0000000..95ce425
--- /dev/null
+++ b/compos/aidl/com/android/compos/OutputFdAnnotation.aidl
@@ -0,0 +1,26 @@
+/*
+ * 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.compos;
+
+/** {@hide} */
+parcelable OutputFdAnnotation {
+    /**
+     * File descriptor number to be passed to the program.  This is currently assumed to be same as
+     * the file descriptor number used in the backend server.
+     */
+    int fd;
+}
diff --git a/compos/apex/Android.bp b/compos/apex/Android.bp
new file mode 100644
index 0000000..ed4dd58
--- /dev/null
+++ b/compos/apex/Android.bp
@@ -0,0 +1,33 @@
+// 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.
+apex_key {
+    name: "com.android.compos.key",
+    public_key: "com.android.compos.avbpubkey",
+    private_key: "com.android.compos.pem",
+}
+
+android_app_certificate {
+    name: "com.android.compos.certificate",
+    certificate: "com.android.compos",
+}
+
+apex {
+    name: "com.android.compos",
+    manifest: "manifest.json",
+    file_contexts: ":com.android.compos-file_contexts",
+    key: "com.android.compos.key",
+
+    // TODO(victorhsieh): make it updatable
+    updatable: false,
+}
diff --git a/compos/apex/com.android.compos.avbpubkey b/compos/apex/com.android.compos.avbpubkey
new file mode 100644
index 0000000..3f09680
--- /dev/null
+++ b/compos/apex/com.android.compos.avbpubkey
Binary files differ
diff --git a/compos/apex/com.android.compos.pem b/compos/apex/com.android.compos.pem
new file mode 100644
index 0000000..7193b76
--- /dev/null
+++ b/compos/apex/com.android.compos.pem
@@ -0,0 +1,51 @@
+-----BEGIN RSA PRIVATE KEY-----
+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
+-----END RSA PRIVATE KEY-----
diff --git a/compos/apex/com.android.compos.pk8 b/compos/apex/com.android.compos.pk8
new file mode 100644
index 0000000..c93fdef
--- /dev/null
+++ b/compos/apex/com.android.compos.pk8
Binary files differ
diff --git a/compos/apex/com.android.compos.x509.pem b/compos/apex/com.android.compos.x509.pem
new file mode 100644
index 0000000..15c8cd9
--- /dev/null
+++ b/compos/apex/com.android.compos.x509.pem
@@ -0,0 +1,33 @@
+-----BEGIN CERTIFICATE-----
+MIIFyTCCA7ECFCk8uxwqGdXE8FFta+4wn/VMWmYeMA0GCSqGSIb3DQEBCwUAMIGf
+MQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91
+bnRhaW4gVmlldzEQMA4GA1UECgwHQW5kcm9pZDEQMA4GA1UECwwHQW5kcm9pZDEi
+MCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTEbMBkGA1UEAwwSY29t
+LmFuZHJvaWQuY29tcG9zMCAXDTIxMDUwNjIzMzMyN1oYDzQ3NTkwNDAyMjMzMzI3
+WjCBnzELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcM
+DU1vdW50YWluIFZpZXcxEDAOBgNVBAoMB0FuZHJvaWQxEDAOBgNVBAsMB0FuZHJv
+aWQxIjAgBgkqhkiG9w0BCQEWE2FuZHJvaWRAYW5kcm9pZC5jb20xGzAZBgNVBAMM
+EmNvbS5hbmRyb2lkLmNvbXBvczCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC
+ggIBAKNNRc4stDl9YmGq68Alp9EhysiUmvvvW4w1B5UNhY6mQ0+go9g+bGwTGcyO
+t/2sgQgd8PpbeJUDJapmpdQPmPiwyDU0ljNZ/5IEND9wUoyrLtKpcw3pn7ZCZeGQ
+C2TCHsiVORlsyDGUYakYtplpVr9DM2W1MgVf6mrr8tM82zmz7ll1PvfSvXWREIwy
+sJxkTkz8r40UFwq/B6TS9K9PYLRpFZKrhg3+62cOQcqv7RIQqIeh+5kd+MwNv8Vy
+om8/EObQ8hgOjHSkDB6x0/jZvNkjHy5E7iPO74k2TtVKgiE2/GrIDy+tOOl8FTNw
+LRJR3xbembPcyiZh70NiybWXb9nxdvrXaTdzvo5+rNLpRhAbTDSDKVI08EKscHVR
+85NZcD44Ivibn+I8WsIrM5g/rXMidZe/Wmey/mIq/1NNyKWGbvvz8LRqGA2N4bng
+uv0CJHPP6Op77QXS/XgCZpto5PrtEhVw0msGpxEHQWLDhEH1ICgVkU87KNohyQMZ
++ls/2mYNJXKivzQ0pYSRM+7Yc9bTV2hApzOuHIQWZ3L2zXt3EqpBZrpV8l7Nb+Vz
+d8DiEvfvS3jgYWzKv6CBMZ0M2CKZfthk7kz3NB31jwdx5S5gS9xLubL0TPEsNMGt
+GPJJ7odrqaZwfeHrm4OB5Xcxky9oSMjfTLII2b/EBL5WQBKtAgMBAAEwDQYJKoZI
+hvcNAQELBQADggIBAItvcl4ycoQbJOIdc1Y9R/uts5cEyc4VZy4tXHm+BwWH8GFp
+BHYn1J9/7FOCmMMTyn0psS0b2nU5BcN0yrzjFO7pgL3BIg+XMQWy7hkgy3fs4PaB
+xTXY9fJeynnQbkxkJnCCAw+MxkFn23LslHEfpkazSfI+9IYJOsWkux+iBbdHKHVx
+yDkhBQvzzhjAr9xCfb/xCY06gsksdtkI6C0HycAMYWrLwcAyb9bBrZ8bj6BkMOMb
+xntanqTzFwMbUrsJuThaf/4oBsMltlmqiTZeaXCLDFpPXt9tWzD7Odqh5l/J7NJR
+VlZCYnhRsclTxtzVfCNIhH/0ylnLY86rgjs0B0g2vB1qRRCmoZRKrqQKiISt7+UL
+PWL7qi1JJXWhg3MUHpy8BG4L/3Ui5KhYSfjJoda4nvbBcYQnBKhDdu6Q1Jv9egpI
+15j7QUa5JyF/T61g00IJ6XI9GzBWNbdHf8M8AulFRVTxx6gK0VmhS7InKotDs0Jf
+d2hCkdzFz3VLoSNfF2a9X5gxiMYG8MFRJpK1drUFfkqey808bzc95SCIpXHcQjD0
+TJbmr6g0w4HDIsv5ELvCsUeZGW+4Ji74DJ213dCxXxTST8ct9+rwS0YZ7ozRwgrL
+aBDUcBrLxz9BoShJ4E5XVROmrxYotcNlFDsBiP6IGhErDvlf8ksJWWh+T8Tx
+-----END CERTIFICATE-----
diff --git a/compos/apex/manifest.json b/compos/apex/manifest.json
new file mode 100644
index 0000000..cdb87a3
--- /dev/null
+++ b/compos/apex/manifest.json
@@ -0,0 +1,4 @@
+{
+  "name": "com.android.compos",
+  "version": 1
+}
diff --git a/compos/src/compsvc.rs b/compos/src/compsvc.rs
new file mode 100644
index 0000000..e912463
--- /dev/null
+++ b/compos/src/compsvc.rs
@@ -0,0 +1,154 @@
+/*
+ * 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.
+ */
+
+//! compsvc is a service to run computational tasks in a PVM upon request. It is able to set up
+//! file descriptors backed by fd_server and pass the file descriptors to the actual tasks for
+//! read/write. The service also attempts to sandbox the execution so that one task cannot leak or
+//! impact future tasks.
+//!
+//! Example:
+//! $ compsvc /system/bin/sleep
+//!
+//! The current architecture / process hierarchy looks like:
+//! - compsvc (handle requests)
+//!   - compsvc_worker (for environment setup)
+//!     - authfs (fd translation)
+//!     - actual task
+
+use anyhow::{bail, Context, Result};
+use log::error;
+use minijail::{self, Minijail};
+use std::path::PathBuf;
+
+use compos_aidl_interface::aidl::com::android::compos::ICompService::{
+    BnCompService, ICompService,
+};
+use compos_aidl_interface::aidl::com::android::compos::Metadata::Metadata;
+use compos_aidl_interface::binder::{
+    add_service, BinderFeatures, Interface, ProcessState, Result as BinderResult, Status,
+    StatusCode, Strong,
+};
+
+const SERVICE_NAME: &str = "compsvc";
+// TODO(b/161470604): Move the executable into an apex.
+const WORKER_BIN: &str = "/system/bin/compsvc_worker";
+// TODO: Replace with a valid directory setup in the VM.
+const AUTHFS_MOUNTPOINT: &str = "/data/local/tmp/authfs_mnt";
+
+struct CompService {
+    worker_bin: PathBuf,
+    task_bin: String,
+    debuggable: bool,
+}
+
+impl CompService {
+    pub fn new_binder(service: CompService) -> Strong<dyn ICompService> {
+        BnCompService::new_binder(service, BinderFeatures::default())
+    }
+
+    fn run_worker_in_jail_and_wait(&self, args: &[String]) -> Result<(), minijail::Error> {
+        let mut jail = Minijail::new()?;
+
+        // TODO(b/185175567): New user and uid namespace when supported. Run as nobody.
+        // New mount namespace to isolate the FUSE mount.
+        jail.namespace_vfs();
+
+        let inheritable_fds = if self.debuggable {
+            vec![1, 2] // inherit/redirect stdout/stderr for debugging
+        } else {
+            vec![]
+        };
+        let _pid = jail.run(&self.worker_bin, &inheritable_fds, &args)?;
+        jail.wait()
+    }
+
+    fn build_worker_args(&self, args: &[String], metadata: &Metadata) -> Vec<String> {
+        let mut worker_args = vec![
+            WORKER_BIN.to_string(),
+            "--authfs-root".to_string(),
+            AUTHFS_MOUNTPOINT.to_string(),
+        ];
+        for annotation in &metadata.input_fd_annotations {
+            worker_args.push("--in-fd".to_string());
+            worker_args.push(format!("{}:{}", annotation.fd, annotation.file_size));
+        }
+        for annotation in &metadata.output_fd_annotations {
+            worker_args.push("--out-fd".to_string());
+            worker_args.push(annotation.fd.to_string());
+        }
+        if self.debuggable {
+            worker_args.push("--debug".to_string());
+        }
+        worker_args.push("--".to_string());
+
+        // Do not accept arbitrary code execution. We want to execute some specific task of this
+        // service. Use the associated executable.
+        worker_args.push(self.task_bin.clone());
+        worker_args.extend_from_slice(&args[1..]);
+        worker_args
+    }
+}
+
+impl Interface for CompService {}
+
+impl ICompService for CompService {
+    fn execute(&self, args: &[String], metadata: &Metadata) -> BinderResult<i8> {
+        let worker_args = self.build_worker_args(args, metadata);
+
+        match self.run_worker_in_jail_and_wait(&worker_args) {
+            Ok(_) => Ok(0), // TODO(b/161471326): Sign the output on succeed.
+            Err(minijail::Error::ReturnCode(exit_code)) => {
+                error!("Task failed with exit code {}", exit_code);
+                Err(Status::from(StatusCode::FAILED_TRANSACTION))
+            }
+            Err(e) => {
+                error!("Unexpected error: {}", e);
+                Err(Status::from(StatusCode::UNKNOWN_ERROR))
+            }
+        }
+    }
+}
+
+fn parse_args() -> Result<CompService> {
+    #[rustfmt::skip]
+    let matches = clap::App::new("compsvc")
+        .arg(clap::Arg::with_name("debug")
+             .long("debug"))
+        .arg(clap::Arg::with_name("task_bin")
+             .required(true))
+        .get_matches();
+
+    Ok(CompService {
+        task_bin: matches.value_of("task_bin").unwrap().to_string(),
+        worker_bin: PathBuf::from(WORKER_BIN),
+        debuggable: matches.is_present("debug"),
+    })
+}
+
+fn main() -> Result<()> {
+    android_logger::init_once(
+        android_logger::Config::default().with_tag("compsvc").with_min_level(log::Level::Debug),
+    );
+
+    let service = parse_args()?;
+
+    ProcessState::start_thread_pool();
+    // TODO: switch to remote binder
+    add_service(SERVICE_NAME, CompService::new_binder(service).as_binder())
+        .with_context(|| format!("Failed to register service {}", SERVICE_NAME))?;
+    ProcessState::join_thread_pool();
+    bail!("Unexpected exit after join_thread_pool")
+}
diff --git a/compos/src/compsvc_worker.rs b/compos/src/compsvc_worker.rs
new file mode 100644
index 0000000..cf40f81
--- /dev/null
+++ b/compos/src/compsvc_worker.rs
@@ -0,0 +1,235 @@
+/*
+ * 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 executable works as a child/worker for the main compsvc service. This worker is mainly
+//! responsible for setting up the execution environment, e.g. to create file descriptors for
+//! remote file access via an authfs mount.
+
+use anyhow::{bail, Result};
+use log::warn;
+use minijail::Minijail;
+use nix::sys::statfs::{statfs, FsType};
+use std::fs::{File, OpenOptions};
+use std::io;
+use std::os::unix::io::AsRawFd;
+use std::path::Path;
+use std::process::exit;
+use std::thread::sleep;
+use std::time::{Duration, Instant};
+
+const AUTHFS_BIN: &str = "/apex/com.android.virt/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);
+
+/// The number that hints the future file descriptor. These are not really file descriptor, but
+/// represents the file descriptor number to pass to the task.
+type PseudoRawFd = i32;
+
+fn is_fuse(path: &str) -> Result<bool> {
+    Ok(statfs(path)?.filesystem_type() == FUSE_SUPER_MAGIC)
+}
+
+fn spawn_authfs(config: &Config) -> Result<Minijail> {
+    // TODO(b/185175567): Run in a more restricted sandbox.
+    let jail = Minijail::new()?;
+
+    let mut args = vec![AUTHFS_BIN.to_string(), config.authfs_root.clone()];
+    for conf in &config.in_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("--remote-ro-file-unverified".to_string());
+        args.push(format!("{}:{}:{}", conf.fd, conf.fd, conf.file_size));
+    }
+    for conf in &config.out_fds {
+        args.push("--remote-new-rw-file".to_string());
+        args.push(format!("{}:{}", conf.fd, conf.fd));
+    }
+
+    let preserve_fds = if config.debuggable {
+        vec![1, 2] // inherit/redirect stdout/stderr for debugging
+    } else {
+        vec![]
+    };
+
+    let _pid = jail.run(Path::new(AUTHFS_BIN), &preserve_fds, &args)?;
+    Ok(jail)
+}
+
+fn wait_until_authfs_ready(authfs_root: &str) -> Result<()> {
+    let start_time = Instant::now();
+    loop {
+        if is_fuse(authfs_root)? {
+            break;
+        }
+        if start_time.elapsed() > AUTHFS_SETUP_TIMEOUT_SEC {
+            bail!("Time out mounting authfs");
+        }
+        sleep(AUTHFS_SETUP_POLL_INTERVAL_MS);
+    }
+    Ok(())
+}
+
+fn open_authfs_file(authfs_root: &str, basename: PseudoRawFd, writable: bool) -> io::Result<File> {
+    OpenOptions::new().read(true).write(writable).open(format!("{}/{}", authfs_root, basename))
+}
+
+fn open_authfs_files_for_mapping(config: &Config) -> io::Result<Vec<(File, PseudoRawFd)>> {
+    let mut fd_mapping = Vec::with_capacity(config.in_fds.len() + config.out_fds.len());
+
+    let results: io::Result<Vec<_>> = config
+        .in_fds
+        .iter()
+        .map(|conf| Ok((open_authfs_file(&config.authfs_root, conf.fd, false)?, conf.fd)))
+        .collect();
+    fd_mapping.append(&mut results?);
+
+    let results: io::Result<Vec<_>> = config
+        .out_fds
+        .iter()
+        .map(|conf| Ok((open_authfs_file(&config.authfs_root, conf.fd, true)?, conf.fd)))
+        .collect();
+    fd_mapping.append(&mut results?);
+
+    Ok(fd_mapping)
+}
+
+fn spawn_jailed_task(config: &Config, fd_mapping: Vec<(File, PseudoRawFd)>) -> Result<Minijail> {
+    // TODO(b/185175567): Run in a more restricted sandbox.
+    let jail = Minijail::new()?;
+    let mut preserve_fds: Vec<_> = fd_mapping.iter().map(|(f, id)| (f.as_raw_fd(), *id)).collect();
+    if config.debuggable {
+        // inherit/redirect stdout/stderr for debugging
+        preserve_fds.push((1, 1));
+        preserve_fds.push((2, 2));
+    }
+    let _pid =
+        jail.run_remap(&Path::new(&config.args[0]), preserve_fds.as_slice(), &config.args)?;
+    Ok(jail)
+}
+
+struct InFdAnnotation {
+    fd: PseudoRawFd,
+    file_size: u64,
+}
+
+struct OutFdAnnotation {
+    fd: PseudoRawFd,
+}
+
+struct Config {
+    authfs_root: String,
+    in_fds: Vec<InFdAnnotation>,
+    out_fds: Vec<OutFdAnnotation>,
+    args: Vec<String>,
+    debuggable: bool,
+}
+
+fn parse_args() -> Result<Config> {
+    #[rustfmt::skip]
+    let matches = clap::App::new("compsvc_worker")
+        .arg(clap::Arg::with_name("authfs-root")
+             .long("authfs-root")
+             .value_name("DIR")
+             .required(true)
+             .takes_value(true))
+        .arg(clap::Arg::with_name("in-fd")
+             .long("in-fd")
+             .multiple(true)
+             .takes_value(true)
+             .requires("authfs-root"))
+        .arg(clap::Arg::with_name("out-fd")
+             .long("out-fd")
+             .multiple(true)
+             .takes_value(true)
+             .requires("authfs-root"))
+        .arg(clap::Arg::with_name("debug")
+             .long("debug"))
+        .arg(clap::Arg::with_name("args")
+             .last(true)
+             .required(true)
+             .multiple(true))
+        .get_matches();
+
+    // Safe to unwrap since the arg is required by the clap rule
+    let authfs_root = matches.value_of("authfs-root").unwrap().to_string();
+
+    let results: Result<Vec<_>> = matches
+        .values_of("in-fd")
+        .unwrap_or_default()
+        .into_iter()
+        .map(|arg| {
+            if let Some(index) = arg.find(':') {
+                let (fd, size) = arg.split_at(index);
+                Ok(InFdAnnotation { fd: fd.parse()?, file_size: size[1..].parse()? })
+            } else {
+                bail!("Invalid argument: {}", arg);
+            }
+        })
+        .collect();
+    let in_fds = results?;
+
+    let results: Result<Vec<_>> = matches
+        .values_of("out-fd")
+        .unwrap_or_default()
+        .into_iter()
+        .map(|arg| Ok(OutFdAnnotation { fd: arg.parse()? }))
+        .collect();
+    let out_fds = results?;
+
+    let args: Vec<_> = matches.values_of("args").unwrap().map(|s| s.to_string()).collect();
+    let debuggable = matches.is_present("debug");
+
+    Ok(Config { authfs_root, in_fds, out_fds, args, debuggable })
+}
+
+fn main() -> Result<()> {
+    let log_level =
+        if env!("TARGET_BUILD_VARIANT") == "eng" { log::Level::Trace } else { log::Level::Info };
+    android_logger::init_once(
+        android_logger::Config::default().with_tag("compsvc_worker").with_min_level(log_level),
+    );
+
+    let config = parse_args()?;
+
+    let authfs_jail = spawn_authfs(&config)?;
+    let authfs_lifetime = scopeguard::guard(authfs_jail, |authfs_jail| {
+        if let Err(e) = authfs_jail.kill() {
+            if !matches!(e, minijail::Error::Killed(_)) {
+                warn!("Failed to kill authfs: {}", e);
+            }
+        }
+    });
+
+    wait_until_authfs_ready(&config.authfs_root)?;
+    let fd_mapping = open_authfs_files_for_mapping(&config)?;
+
+    let jail = spawn_jailed_task(&config, fd_mapping)?;
+    let jail_result = jail.wait();
+
+    // Be explicit about the lifetime, which should last at least until the task is finished.
+    drop(authfs_lifetime);
+
+    match jail_result {
+        Ok(_) => Ok(()),
+        Err(minijail::Error::ReturnCode(exit_code)) => {
+            exit(exit_code as i32);
+        }
+        Err(e) => {
+            bail!("Unexpected minijail error: {}", e);
+        }
+    }
+}
diff --git a/compos/src/pvm_exec.rs b/compos/src/pvm_exec.rs
new file mode 100644
index 0000000..fcde266
--- /dev/null
+++ b/compos/src/pvm_exec.rs
@@ -0,0 +1,169 @@
+/*
+ * 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.
+ */
+
+//! pvm_exec is a proxy/wrapper command to run a command remotely. It does not transport the
+//! program and just pass the command line arguments to compsvc to execute. The most important task
+//! for this program is to run a `fd_server` that serves remote file read/write requests.
+//!
+//! Example:
+//! $ adb shell exec 3</dev/zero 4<>/dev/null pvm_exec --in-fd 3 --out-fd 4 -- sleep 10
+//!
+//! Note the immediate argument right after "--" (e.g. "sleep" in the example above) is not really
+//! used. It is only for ergonomics.
+
+use anyhow::{bail, Context, Result};
+use log::{error, warn};
+use minijail::Minijail;
+use nix::fcntl::{fcntl, FcntlArg::F_GETFD};
+use nix::sys::stat::fstat;
+use std::os::unix::io::RawFd;
+use std::path::Path;
+use std::process::exit;
+
+use compos_aidl_interface::aidl::com::android::compos::{
+    ICompService::ICompService, InputFdAnnotation::InputFdAnnotation, Metadata::Metadata,
+    OutputFdAnnotation::OutputFdAnnotation,
+};
+use compos_aidl_interface::binder::Strong;
+
+static SERVICE_NAME: &str = "compsvc";
+static FD_SERVER_BIN: &str = "/apex/com.android.virt/bin/fd_server";
+
+fn get_local_service() -> Strong<dyn ICompService> {
+    compos_aidl_interface::binder::get_interface(SERVICE_NAME).expect("Cannot reach compsvc")
+}
+
+fn spawn_fd_server(metadata: &Metadata, debuggable: bool) -> Result<Minijail> {
+    let mut inheritable_fds = if debuggable {
+        vec![1, 2] // inherit/redirect stdout/stderr for debugging
+    } else {
+        vec![]
+    };
+
+    let mut args = vec![FD_SERVER_BIN.to_string()];
+    for metadata in &metadata.input_fd_annotations {
+        args.push("--ro-fds".to_string());
+        args.push(metadata.fd.to_string());
+        inheritable_fds.push(metadata.fd);
+    }
+    for metadata in &metadata.output_fd_annotations {
+        args.push("--rw-fds".to_string());
+        args.push(metadata.fd.to_string());
+        inheritable_fds.push(metadata.fd);
+    }
+
+    let jail = Minijail::new()?;
+    let _pid = jail.run(Path::new(FD_SERVER_BIN), &inheritable_fds, &args)?;
+    Ok(jail)
+}
+
+fn is_fd_valid(fd: RawFd) -> Result<bool> {
+    let retval = fcntl(fd, F_GETFD)?;
+    Ok(retval >= 0)
+}
+
+fn parse_arg_fd(arg: &str) -> Result<RawFd> {
+    let fd = arg.parse::<RawFd>()?;
+    if !is_fd_valid(fd)? {
+        bail!("Bad FD: {}", fd);
+    }
+    Ok(fd)
+}
+
+struct Config {
+    args: Vec<String>,
+    metadata: Metadata,
+    debuggable: bool,
+}
+
+fn parse_args() -> Result<Config> {
+    #[rustfmt::skip]
+    let matches = clap::App::new("pvm_exec")
+        .arg(clap::Arg::with_name("in-fd")
+             .long("in-fd")
+             .takes_value(true)
+             .multiple(true)
+             .use_delimiter(true))
+        .arg(clap::Arg::with_name("out-fd")
+             .long("out-fd")
+             .takes_value(true)
+             .multiple(true)
+             .use_delimiter(true))
+        .arg(clap::Arg::with_name("debug")
+             .long("debug"))
+        .arg(clap::Arg::with_name("args")
+             .last(true)
+             .required(true)
+             .multiple(true))
+        .get_matches();
+
+    let results: Result<Vec<_>> = matches
+        .values_of("in-fd")
+        .unwrap_or_default()
+        .map(|arg| {
+            let fd = parse_arg_fd(arg)?;
+            let file_size = fstat(fd)?.st_size;
+            Ok(InputFdAnnotation { fd, file_size })
+        })
+        .collect();
+    let input_fd_annotations = results?;
+
+    let results: Result<Vec<_>> = matches
+        .values_of("out-fd")
+        .unwrap_or_default()
+        .map(|arg| {
+            let fd = parse_arg_fd(arg)?;
+            Ok(OutputFdAnnotation { fd })
+        })
+        .collect();
+    let output_fd_annotations = results?;
+
+    let args: Vec<_> = matches.values_of("args").unwrap().map(|s| s.to_string()).collect();
+    let debuggable = matches.is_present("debug");
+
+    Ok(Config {
+        args,
+        metadata: Metadata { input_fd_annotations, output_fd_annotations },
+        debuggable,
+    })
+}
+
+fn main() -> Result<()> {
+    // 1. Parse the command line arguments for collect execution data.
+    let Config { args, metadata, debuggable } = parse_args()?;
+
+    // 2. Spawn and configure a fd_server to serve remote read/write requests.
+    let fd_server_jail = spawn_fd_server(&metadata, debuggable)?;
+    let fd_server_lifetime = scopeguard::guard(fd_server_jail, |fd_server_jail| {
+        if let Err(e) = fd_server_jail.kill() {
+            if !matches!(e, minijail::Error::Killed(_)) {
+                warn!("Failed to kill fd_server: {}", e);
+            }
+        }
+    });
+
+    // 3. Send the command line args to the remote to execute.
+    let exit_code = get_local_service().execute(&args, &metadata).context("Binder call failed")?;
+
+    // Be explicit about the lifetime, which should last at least until the task is finished.
+    drop(fd_server_lifetime);
+
+    if exit_code > 0 {
+        error!("remote execution failed with exit code {}", exit_code);
+        exit(exit_code as i32);
+    }
+    Ok(())
+}
diff --git a/microdroid/Android.bp b/microdroid/Android.bp
index 47f3474..55075ca 100644
--- a/microdroid/Android.bp
+++ b/microdroid/Android.bp
@@ -59,6 +59,9 @@
         "libadbd_auth",
         "libadbd_fs",
 
+        // "com.android.art" requires
+        "heapprofd_client_api",
+
         "apexd",
         "debuggerd",
         "linker",
@@ -168,7 +171,9 @@
             kernel_prebuilt: ":kernel_prebuilts-5.10-x86_64",
             cmdline: microdroid_boot_cmdline +
                 "pci=noacpi " +
-                "androidboot.boot_devices=pci0000:00/0000:00:01.0",
+                "androidboot.boot_devices=pci0000:00/0000:00:01.0," + // os
+                "pci0000:00/0000:00:03.0," + // payload
+                "pci0000:00/0000:00:04.0", // userdata
         },
     },
     dtb_prebuilt: "dummy_dtb.img",
diff --git a/microdroid/README.md b/microdroid/README.md
index 91371b5..4edd65b 100644
--- a/microdroid/README.md
+++ b/microdroid/README.md
@@ -51,9 +51,9 @@
 ```
 
 Copy the artifacts to the temp directory, create the composite image using
-`mk_cdisk`, copy the VM config file, and run it via `vm`. For now, some other
-files have to be manually created. In the future, you won't need these, and this
-shall be done via [`virtmanager`](../virtmanager/).
+`mk_cdisk` and copy the VM config file. For now, some other files have to be
+manually created. In the future, you won't need these, and this shall be done
+via [`virtmanager`](../virtmanager/).
 
 ```sh
 $ adb root
@@ -67,10 +67,20 @@
 $ adb shell 'cd /data/local/tmp/microdroid; /apex/com.android.virt/bin/mk_cdisk /apex/com.android.virt/etc/microdroid_cdisk.json os_composite.img'
 $ adb shell 'cd /data/local/tmp/microdroid; /apex/com.android.virt/bin/mk_cdisk /apex/com.android.virt/etc/microdroid_cdisk_env.json env_composite.img'
 $ adb shell 'cd /data/local/tmp/microdroid; /apex/com.android.virt/bin/mk_cdisk /apex/com.android.virt/etc/microdroid_cdisk_userdata.json userdata_composite.img'
-$ adb shell '/apex/com.android.virt/bin/crosvm create_qcow2 --backing_file=/data/local/tmp/microdroid/userdata_composite.img /data/local/tmp/microdroid/userdata_composite.$ qcow2'
+$ adb shell '/apex/com.android.virt/bin/crosvm create_qcow2 --backing_file=/data/local/tmp/microdroid/userdata_composite.img /data/local/tmp/microdroid/userdata_composite.qcow2'
 $ adb shell 'cd /data/local/tmp/microdroid; /apex/com.android.virt/bin/mk_payload /apex/com.android.virt/etc/microdroid_payload.json payload.img'
 $ adb shell 'chmod go+r /data/local/tmp/microdroid/*-header.img /data/local/tmp/microdroid/*-footer.img'
 $ adb push microdroid.json /data/local/tmp/microdroid/microdroid.json
+```
+
+Ensure SELinux is in permissive mode to allow virtmanager and crosvm to open
+files from `/data/local/tmp`. Opening files from this directory is
+neverallow-ed and file descriptors should be passed instead but, before that is
+supported, `adb shell setenforce 0` will put the device in permissive mode.
+
+Now, run the VM and look for `adbd` starting in the logs.
+
+```sh
 $ adb shell "start virtmanager"
 $ adb shell "RUST_BACKTRACE=1 RUST_LOG=trace /apex/com.android.virt/bin/vm run /data/local/tmp/microdroid/microdroid.json"
 ```