Merge "idsig: move apksigv4.rs into libidsig"
diff --git a/apkverify/Android.bp b/apkverify/Android.bp
index d2dbf41..df1cac6 100644
--- a/apkverify/Android.bp
+++ b/apkverify/Android.bp
@@ -4,7 +4,6 @@
 
 rust_defaults {
     name: "libapkverify.defaults",
-    host_supported: true,
     crate_name: "apkverify",
     srcs: ["src/lib.rs"],
     prefer_rlib: true,
@@ -33,12 +32,14 @@
 
 rust_test {
     name: "libapkverify.integration_test",
-    host_supported: true,
     crate_name: "apkverify_test",
     srcs: ["tests/*_test.rs"],
     prefer_rlib: true,
     edition: "2018",
     test_suites: ["general-tests"],
-    rustlibs: ["libapkverify"],
+    rustlibs: [
+        "libapkverify",
+        "libzip",
+    ],
     data: ["tests/data/*"],
 }
diff --git a/apkverify/src/lib.rs b/apkverify/src/lib.rs
index 9930099..869431e 100644
--- a/apkverify/src/lib.rs
+++ b/apkverify/src/lib.rs
@@ -18,6 +18,7 @@
 
 mod bytes_ext;
 mod sigutil;
+mod testing;
 mod v3;
 mod ziputil;
 
diff --git a/apkverify/src/testing.rs b/apkverify/src/testing.rs
new file mode 100644
index 0000000..777afb8
--- /dev/null
+++ b/apkverify/src/testing.rs
@@ -0,0 +1,34 @@
+/*
+ * 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.
+ */
+
+//! A collection of utilities for testing
+
+/// Asserts if `haystack.contains(needed)`
+#[macro_export]
+macro_rules! assert_contains {
+    ($haystack:expr,$needle:expr $(,)?) => {
+        match (&$haystack, &$needle) {
+            (haystack_value, needle_value) => {
+                assert!(
+                    haystack_value.contains(needle_value),
+                    "{} is not found in {}",
+                    needle_value,
+                    haystack_value
+                );
+            }
+        }
+    };
+}
diff --git a/apkverify/src/ziputil.rs b/apkverify/src/ziputil.rs
index dbf5131..bfb1c01 100644
--- a/apkverify/src/ziputil.rs
+++ b/apkverify/src/ziputil.rs
@@ -22,8 +22,10 @@
 use zip::ZipArchive;
 
 const EOCD_MIN_SIZE: usize = 22;
+const EOCD_CENTRAL_DIRECTORY_SIZE_FIELD_OFFSET: usize = 12;
 const EOCD_CENTRAL_DIRECTORY_OFFSET_FIELD_OFFSET: usize = 16;
 const EOCD_MAGIC: u32 = 0x06054b50;
+const ZIP64_MARK: u32 = 0xffffffff;
 
 #[derive(Debug, PartialEq)]
 pub struct ZipSections {
@@ -44,30 +46,39 @@
     // retrieve reader back
     reader = archive.into_inner();
     // the current position should point EOCD offset
-    let eocd_offset = reader.seek(SeekFrom::Current(0))?;
+    let eocd_offset = reader.seek(SeekFrom::Current(0))? as u32;
     let mut eocd = vec![0u8; eocd_size as usize];
     reader.read_exact(&mut eocd)?;
     if (&eocd[0..]).get_u32_le() != EOCD_MAGIC {
         bail!("Invalid ZIP: ZipArchive::new() should point EOCD after reading.");
     }
-    let central_directory_offset = get_central_directory_offset(&eocd)?;
-    let central_directory_size = eocd_offset as u32 - central_directory_offset;
+    let (central_directory_size, central_directory_offset) = get_central_directory(&eocd)?;
+    if central_directory_offset == ZIP64_MARK || central_directory_size == ZIP64_MARK {
+        bail!("Unsupported ZIP: ZIP64 is not supported.");
+    }
+    if central_directory_offset + central_directory_size != eocd_offset {
+        bail!("Invalid ZIP: EOCD should follow CD with no extra data or overlap.");
+    }
+
     Ok((
         reader,
         ZipSections {
             central_directory_offset,
             central_directory_size,
-            eocd_offset: eocd_offset as u32,
+            eocd_offset,
             eocd_size: eocd_size as u32,
         },
     ))
 }
 
-fn get_central_directory_offset(buf: &[u8]) -> Result<u32> {
+fn get_central_directory(buf: &[u8]) -> Result<(u32, u32)> {
     if buf.len() < EOCD_MIN_SIZE {
         bail!("Invalid EOCD size: {}", buf.len());
     }
-    Ok((&buf[EOCD_CENTRAL_DIRECTORY_OFFSET_FIELD_OFFSET..]).get_u32_le())
+    let mut buf = &buf[EOCD_CENTRAL_DIRECTORY_SIZE_FIELD_OFFSET..];
+    let size = buf.get_u32_le();
+    let offset = buf.get_u32_le();
+    Ok((size, offset))
 }
 
 /// Update EOCD's central_directory_offset field.
@@ -78,3 +89,42 @@
     (&mut buf[EOCD_CENTRAL_DIRECTORY_OFFSET_FIELD_OFFSET..]).put_u32_le(value);
     Ok(())
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::assert_contains;
+    use std::io::{Cursor, Write};
+    use zip::{write::FileOptions, ZipWriter};
+
+    fn create_test_zip() -> Cursor<Vec<u8>> {
+        let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
+        writer.start_file("testfile", FileOptions::default()).unwrap();
+        writer.write_all(b"testcontent").unwrap();
+        writer.finish().unwrap()
+    }
+
+    #[test]
+    fn test_zip_sections() {
+        let (cursor, sections) = zip_sections(create_test_zip()).unwrap();
+        assert_eq!(sections.eocd_offset, (cursor.get_ref().len() - EOCD_MIN_SIZE) as u32);
+    }
+
+    #[test]
+    fn test_reject_if_extra_data_between_cd_and_eocd() {
+        // prepare normal zip
+        let buf = create_test_zip().into_inner();
+
+        // insert garbage between CD and EOCD.
+        // by the way, to mock zip-rs, use CD as garbage. This is implementation detail of zip-rs,
+        // which reads CD at (eocd_offset - cd_size) instead of at cd_offset from EOCD.
+        let (pre_eocd, eocd) = buf.split_at(buf.len() - EOCD_MIN_SIZE);
+        let (_, cd_offset) = get_central_directory(eocd).unwrap();
+        let cd = &pre_eocd[cd_offset as usize..];
+
+        // ZipArchive::new() succeeds, but we should reject
+        let res = zip_sections(Cursor::new([pre_eocd, cd, eocd].concat()));
+        assert!(res.is_err());
+        assert_contains!(res.err().unwrap().to_string(), "Invalid ZIP: offset should be 0");
+    }
+}
diff --git a/apkverify/tests/apkverify_test.rs b/apkverify/tests/apkverify_test.rs
index cad5ef2..3366524 100644
--- a/apkverify/tests/apkverify_test.rs
+++ b/apkverify/tests/apkverify_test.rs
@@ -14,22 +14,8 @@
  * limitations under the License.
  */
 
-use apkverify::verify;
-
-macro_rules! assert_contains {
-    ($haystack:expr,$needle:expr $(,)?) => {
-        match (&$haystack, &$needle) {
-            (haystack_value, needle_value) => {
-                assert!(
-                    haystack_value.contains(needle_value),
-                    "{} is not found in {}",
-                    needle_value,
-                    haystack_value
-                );
-            }
-        }
-    };
-}
+use apkverify::{assert_contains, verify};
+use std::matches;
 
 #[test]
 fn test_verify_v3() {
@@ -49,3 +35,14 @@
     assert!(res.is_err());
     assert_contains!(res.err().unwrap().to_string(), "Public key mismatch");
 }
+
+#[test]
+fn test_verify_truncated_cd() {
+    use zip::result::ZipError;
+    let res = verify("tests/data/v2-only-truncated-cd.apk");
+    // TODO(jooyung): consider making a helper for err assertion
+    assert!(matches!(
+        res.err().unwrap().root_cause().downcast_ref::<ZipError>().unwrap(),
+        ZipError::InvalidArchive(_),
+    ));
+}
diff --git a/apkverify/tests/data/README.md b/apkverify/tests/data/README.md
index 953ecdb..7556921 100644
--- a/apkverify/tests/data/README.md
+++ b/apkverify/tests/data/README.md
@@ -11,6 +11,4 @@
 Number of signers: 1
 ```
 
-Some test APKs are copied from tools/apksig/src/test/resources/com/android/apksig/.
-- v3-only-cert-and-public-key-mismatch.apk
-- v3-only-with-rsa-pkcs1-sha512-8192-digest-mismatch.apk
+APK files are copied from tools/apksig/src/test/resources/com/android/apksig/.
diff --git a/apkverify/tests/data/v2-only-truncated-cd.apk b/apkverify/tests/data/v2-only-truncated-cd.apk
new file mode 100644
index 0000000..d2e3e8d
--- /dev/null
+++ b/apkverify/tests/data/v2-only-truncated-cd.apk
Binary files differ
diff --git a/compos/Android.bp b/compos/Android.bp
index 7f4f55c..e29387d 100644
--- a/compos/Android.bp
+++ b/compos/Android.bp
@@ -30,38 +30,14 @@
     name: "compsvc",
     srcs: ["src/compsvc_main.rs"],
     rustlibs: [
-        "authfs_aidl_interface-rust",
-        "compos_aidl_interface-rust",
-        "libandroid_logger",
-        "libanyhow",
-        "libbinder_rpc_unstable_bindgen",
-        "libbinder_rs",
-        "libclap",
-        "liblog_rust",
-        "libminijail_rust",
-    ],
-    prefer_rlib: true,
-    shared_libs: [
-        "libbinder_rpc_unstable",
-    ],
-    apex_available: [
-        "com.android.compos",
-    ],
-}
-
-rust_binary {
-    name: "compos_key_main",
-    srcs: ["src/compos_key_main.rs"],
-    edition: "2018",
-    rustlibs: [
-        "authfs_aidl_interface-rust",
-        "compos_aidl_interface-rust",
-        "android.system.keystore2-V1-rust",
         "android.hardware.security.keymint-V1-rust",
+        "android.system.keystore2-V1-rust",
+        "authfs_aidl_interface-rust",
+        "compos_aidl_interface-rust",
         "libandroid_logger",
         "libanyhow",
-        "libbinder_rs",
         "libbinder_rpc_unstable_bindgen",
+        "libbinder_rs",
         "libclap",
         "liblog_rust",
         "libminijail_rust",
@@ -72,5 +48,7 @@
     shared_libs: [
         "libbinder_rpc_unstable",
     ],
-    apex_available: ["com.android.compos"],
+    apex_available: [
+        "com.android.compos",
+    ],
 }
diff --git a/compos/aidl/com/android/compos/ICompOsKeyService.aidl b/compos/aidl/com/android/compos/ICompOsService.aidl
similarity index 72%
rename from compos/aidl/com/android/compos/ICompOsKeyService.aidl
rename to compos/aidl/com/android/compos/ICompOsService.aidl
index eb2caa7..ec4f0f6 100644
--- a/compos/aidl/com/android/compos/ICompOsKeyService.aidl
+++ b/compos/aidl/com/android/compos/ICompOsService.aidl
@@ -17,10 +17,23 @@
 package com.android.compos;
 
 import com.android.compos.CompOsKeyData;
-import com.android.compos.ICompService;
+import com.android.compos.Metadata;
 
 /** {@hide} */
-interface ICompOsKeyService {
+interface ICompOsService {
+    /**
+     * 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);
+
     /**
      * Generate a new public/private key pair suitable for signing CompOs output files.
      *
@@ -49,13 +62,4 @@
      */
     // STOPSHIP(b/193241041): We must not expose this from the PVM.
     byte[] sign(in byte[] keyBlob, in byte[] data);
-
-    /**
-     * Return an instance of ICompService that will sign output files with a given encrypted
-     * private key.
-     *
-     * @param keyBlob The encrypted blob containing the private key, as returned by
-     *                generateSigningKey().
-     */
-    ICompService getCompService(in byte[] keyBlob);
 }
diff --git a/compos/aidl/com/android/compos/ICompService.aidl b/compos/aidl/com/android/compos/ICompService.aidl
deleted file mode 100644
index 0e18442..0000000
--- a/compos/aidl/com/android/compos/ICompService.aidl
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * 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/apex/Android.bp b/compos/apex/Android.bp
index 12d2f06..5b21802 100644
--- a/compos/apex/Android.bp
+++ b/compos/apex/Android.bp
@@ -39,7 +39,6 @@
 
     binaries: [
         "compos_key_cmd",
-        "compos_key_main",
         "compsvc",
         "pvm_exec",
     ],
diff --git a/compos/apk/assets/key_service_vm_config.json b/compos/apk/assets/key_service_vm_config.json
deleted file mode 100644
index 3b6b88c..0000000
--- a/compos/apk/assets/key_service_vm_config.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
-    "version": 1,
-    "os": {
-        "name": "microdroid"
-    },
-    "task": {
-        "type": "executable",
-        "command": "/apex/com.android.compos/bin/compos_key_main",
-        "args": [
-            "--rpc-binder"
-        ]
-    },
-    "apexes": [
-        {
-            "name": "com.android.compos"
-        }
-    ]
-}
\ No newline at end of file
diff --git a/compos/apk/assets/vm_config.json b/compos/apk/assets/vm_config.json
index f9f1f90..3be8a8a 100644
--- a/compos/apk/assets/vm_config.json
+++ b/compos/apk/assets/vm_config.json
@@ -7,8 +7,7 @@
     "type": "executable",
     "command": "/apex/com.android.compos/bin/compsvc",
     "args": [
-      "--rpc-binder",
-      "/apex/com.android.art/bin/dex2oat64"
+      "--rpc-binder"
     ]
   },
   "apexes": [
diff --git a/compos/compos_key_cmd/compos_key_cmd.cpp b/compos/compos_key_cmd/compos_key_cmd.cpp
index 84a0a7c..04ba1d0 100644
--- a/compos/compos_key_cmd/compos_key_cmd.cpp
+++ b/compos/compos_key_cmd/compos_key_cmd.cpp
@@ -16,7 +16,7 @@
 
 #include <aidl/android/system/virtualizationservice/BnVirtualMachineCallback.h>
 #include <aidl/android/system/virtualizationservice/IVirtualizationService.h>
-#include <aidl/com/android/compos/ICompOsKeyService.h>
+#include <aidl/com/android/compos/ICompOsService.h>
 #include <android-base/file.h>
 #include <android-base/logging.h>
 #include <android-base/result.h>
@@ -56,7 +56,7 @@
 using aidl::android::system::virtualizationservice::IVirtualMachineCallback;
 using aidl::android::system::virtualizationservice::VirtualMachineConfig;
 using aidl::com::android::compos::CompOsKeyData;
-using aidl::com::android::compos::ICompOsKeyService;
+using aidl::com::android::compos::ICompOsService;
 using android::base::ErrnoError;
 using android::base::Error;
 using android::base::Result;
@@ -66,7 +66,7 @@
 using ndk::ScopedFileDescriptor;
 using ndk::SharedRefBase;
 
-constexpr unsigned int kRpcPort = 3142;
+constexpr unsigned int kRpcPort = 6432;
 
 constexpr const char* kConfigApkPath =
         "/apex/com.android.compos/app/CompOSPayloadApp/CompOSPayloadApp.apk";
@@ -89,11 +89,11 @@
     return std::vector<uint8_t>(str.begin(), str.end());
 }
 
-static std::shared_ptr<ICompOsKeyService> getService(int cid) {
+static std::shared_ptr<ICompOsService> getService(int cid) {
     LOG(INFO) << "Connecting to cid " << cid;
     ndk::SpAIBinder binder(cid == 0 ? AServiceManager_getService("android.system.composkeyservice")
                                     : RpcClient(cid, kRpcPort));
-    return ICompOsKeyService::fromBinder(binder);
+    return ICompOsService::fromBinder(binder);
 }
 
 namespace {
@@ -337,7 +337,7 @@
     return result;
 }
 
-static Result<void> signFile(ICompOsKeyService* service, const std::vector<uint8_t>& key_blob,
+static Result<void> signFile(ICompOsService* service, const std::vector<uint8_t>& key_blob,
                              const std::string& file) {
     unique_fd fd(TEMP_FAILURE_RETRY(open(file.c_str(), O_RDONLY | O_CLOEXEC)));
     if (!fd.ok()) {
diff --git a/compos/src/compilation.rs b/compos/src/compilation.rs
new file mode 100644
index 0000000..53302e8
--- /dev/null
+++ b/compos/src/compilation.rs
@@ -0,0 +1,121 @@
+/*
+ * 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::error;
+use minijail::{self, Minijail};
+use std::os::unix::io::AsRawFd;
+use std::path::Path;
+
+use authfs_aidl_interface::aidl::com::android::virt::fs::{
+    AuthFsConfig::AuthFsConfig, IAuthFs::IAuthFs, IAuthFsService::IAuthFsService,
+    InputFdAnnotation::InputFdAnnotation, OutputFdAnnotation::OutputFdAnnotation,
+};
+use authfs_aidl_interface::binder::{ParcelFileDescriptor, Strong};
+use compos_aidl_interface::aidl::com::android::compos::Metadata::Metadata;
+
+/// The number that represents the file descriptor number expecting by the task. The number may be
+/// meaningless in the current process.
+pub type PseudoRawFd = i32;
+
+/// Runs the compiler with given flags with file descriptors described in `metadata` retrieved via
+/// `authfs_service`. Returns exit code of the compiler process.
+pub fn compile(
+    compiler_path: &Path,
+    compiler_args: &[String],
+    authfs_service: Strong<dyn IAuthFsService>,
+    metadata: &Metadata,
+) -> Result<i8> {
+    // Mount authfs (via authfs_service).
+    let authfs_config = build_authfs_config(metadata);
+    let authfs = authfs_service.mount(&authfs_config)?;
+
+    // The task expects to receive FD numbers that match its flags (e.g. --zip-fd=42) prepared
+    // on the host side. Since the local FD opened from authfs (e.g. /authfs/42) may not match
+    // the task's expectation, prepare a FD mapping and let minijail prepare the correct FD
+    // setup.
+    let fd_mapping =
+        open_authfs_files_for_fd_mapping(&authfs, &authfs_config).context("Open on authfs")?;
+
+    let jail =
+        spawn_jailed_task(compiler_path, compiler_args, fd_mapping).context("Spawn dex2oat")?;
+    let jail_result = jail.wait();
+
+    // Be explicit about the lifetime, which should last at least until the task is finished.
+    drop(authfs);
+
+    match jail_result {
+        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);
+            Ok(exit_code as i8)
+        }
+        Err(e) => {
+            bail!("Unexpected minijail error: {}", e)
+        }
+    }
+}
+
+fn build_authfs_config(metadata: &Metadata) -> AuthFsConfig {
+    AuthFsConfig {
+        port: 3264, // TODO: support dynamic port
+        inputFdAnnotations: metadata
+            .input_fd_annotations
+            .iter()
+            .map(|x| InputFdAnnotation { fd: x.fd, fileSize: x.file_size })
+            .collect(),
+        outputFdAnnotations: metadata
+            .output_fd_annotations
+            .iter()
+            .map(|x| OutputFdAnnotation { fd: x.fd })
+            .collect(),
+    }
+}
+
+fn open_authfs_files_for_fd_mapping(
+    authfs: &Strong<dyn IAuthFs>,
+    config: &AuthFsConfig,
+) -> Result<Vec<(ParcelFileDescriptor, PseudoRawFd)>> {
+    let mut fd_mapping = Vec::new();
+
+    let results: Result<Vec<_>> = config
+        .inputFdAnnotations
+        .iter()
+        .map(|annotation| Ok((authfs.openFile(annotation.fd, false)?, annotation.fd)))
+        .collect();
+    fd_mapping.append(&mut results?);
+
+    let results: Result<Vec<_>> = config
+        .outputFdAnnotations
+        .iter()
+        .map(|annotation| Ok((authfs.openFile(annotation.fd, true)?, annotation.fd)))
+        .collect();
+    fd_mapping.append(&mut results?);
+
+    Ok(fd_mapping)
+}
+
+fn spawn_jailed_task(
+    executable: &Path,
+    args: &[String],
+    fd_mapping: Vec<(ParcelFileDescriptor, PseudoRawFd)>,
+) -> Result<Minijail> {
+    // TODO(b/185175567): Run in a more restricted sandbox.
+    let jail = Minijail::new()?;
+    let preserve_fds: Vec<_> = fd_mapping.iter().map(|(f, id)| (f.as_raw_fd(), *id)).collect();
+    let _pid = jail.run_remap(executable, preserve_fds.as_slice(), args)?;
+    Ok(jail)
+}
diff --git a/compos/src/compos_key_main.rs b/compos/src/compos_key_main.rs
deleted file mode 100644
index 9d57e4d..0000000
--- a/compos/src/compos_key_main.rs
+++ /dev/null
@@ -1,71 +0,0 @@
-// Copyright 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.
-
-//! Run the CompOS key management service, either in the host using normal Binder or in the
-//! VM using RPC Binder.
-
-mod compos_key_service;
-mod compsvc;
-mod signer;
-
-use crate::compos_key_service::KeystoreNamespace;
-use anyhow::{bail, Context, Result};
-use binder::unstable_api::AsNative;
-use compos_aidl_interface::binder::{add_service, ProcessState};
-use log::{info, Level};
-
-const LOG_TAG: &str = "CompOsKeyService";
-const OUR_SERVICE_NAME: &str = "android.system.composkeyservice";
-const OUR_VSOCK_PORT: u32 = 3142;
-
-fn main() -> Result<()> {
-    android_logger::init_once(
-        android_logger::Config::default().with_tag(LOG_TAG).with_min_level(Level::Info),
-    );
-
-    let matches = clap::App::new("compos_key_main")
-        .arg(clap::Arg::with_name("rpc_binder").long("rpc-binder"))
-        .get_matches();
-
-    let rpc_binder = matches.is_present("rpc_binder");
-
-    let key_namespace =
-        if rpc_binder { KeystoreNamespace::VmPayload } else { KeystoreNamespace::Odsign };
-    let mut service = compos_key_service::new(key_namespace)?.as_binder();
-
-    if rpc_binder {
-        info!("Starting RPC service");
-        // SAFETY: Service ownership is transferring to the server and won't be valid afterward.
-        // Plus the binder objects are threadsafe.
-        let retval = unsafe {
-            binder_rpc_unstable_bindgen::RunRpcServer(
-                service.as_native_mut() as *mut binder_rpc_unstable_bindgen::AIBinder,
-                OUR_VSOCK_PORT,
-            )
-        };
-        if retval {
-            info!("RPC server has shut down gracefully");
-        } else {
-            bail!("Premature termination of RPC server");
-        }
-    } else {
-        info!("Starting binder service");
-        add_service(OUR_SERVICE_NAME, service).context("Adding service failed")?;
-        info!("It's alive!");
-
-        ProcessState::join_thread_pool();
-    }
-
-    Ok(())
-}
diff --git a/compos/src/compos_key_service.rs b/compos/src/compos_key_service.rs
index 779b798..92b04f2 100644
--- a/compos/src/compos_key_service.rs
+++ b/compos/src/compos_key_service.rs
@@ -16,8 +16,6 @@
 //! access to Keystore in the VM, but not persistent storage; instead the host stores the key
 //! on our behalf via this service.
 
-use crate::compsvc;
-use crate::signer::Signer;
 use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
     Algorithm::Algorithm, Digest::Digest, KeyParameter::KeyParameter,
     KeyParameterValue::KeyParameterValue, KeyPurpose::KeyPurpose, PaddingMode::PaddingMode,
@@ -27,20 +25,12 @@
     Domain::Domain, IKeystoreSecurityLevel::IKeystoreSecurityLevel,
     IKeystoreService::IKeystoreService, KeyDescriptor::KeyDescriptor,
 };
+use android_system_keystore2::binder::{wait_for_interface, Strong};
 use anyhow::{anyhow, Context, Result};
-use compos_aidl_interface::aidl::com::android::compos::{
-    CompOsKeyData::CompOsKeyData,
-    ICompOsKeyService::{BnCompOsKeyService, ICompOsKeyService},
-    ICompService::ICompService,
-};
-use compos_aidl_interface::binder::{
-    self, wait_for_interface, BinderFeatures, ExceptionCode, Interface, Status, Strong,
-};
-use log::warn;
+use compos_aidl_interface::aidl::com::android::compos::CompOsKeyData::CompOsKeyData;
 use ring::rand::{SecureRandom, SystemRandom};
 use ring::signature;
 use scopeguard::ScopeGuard;
-use std::ffi::CString;
 
 /// Keystore2 namespace IDs, used for access control to keys.
 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
@@ -52,23 +42,6 @@
     VmPayload = 140,
 }
 
-/// Constructs a binder object that implements ICompOsKeyService. namespace is the Keystore2 namespace to
-/// use for the keys.
-pub fn new(namespace: KeystoreNamespace) -> Result<Strong<dyn ICompOsKeyService>> {
-    let keystore_service = wait_for_interface::<dyn IKeystoreService>(KEYSTORE_SERVICE_NAME)
-        .context("No Keystore service")?;
-
-    let service = CompOsKeyService {
-        namespace,
-        random: SystemRandom::new(),
-        security_level: keystore_service
-            .getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT)
-            .context("Getting SecurityLevel failed")?,
-    };
-
-    Ok(BnCompOsKeyService::new_binder(service, BinderFeatures::default()))
-}
-
 const KEYSTORE_SERVICE_NAME: &str = "android.system.keystore2.IKeystoreService/default";
 const PURPOSE_SIGN: KeyParameter =
     KeyParameter { tag: Tag::PURPOSE, value: KeyParameterValue::KeyPurpose(KeyPurpose::SIGN) };
@@ -90,65 +63,31 @@
 const BLOB_KEY_DESCRIPTOR: KeyDescriptor =
     KeyDescriptor { domain: Domain::BLOB, nspace: 0, alias: None, blob: None };
 
+/// An internal service for CompOS key management.
 #[derive(Clone)]
-struct CompOsKeyService {
+pub struct CompOsKeyService {
     namespace: KeystoreNamespace,
     random: SystemRandom,
     security_level: Strong<dyn IKeystoreSecurityLevel>,
 }
 
-impl Interface for CompOsKeyService {}
+impl CompOsKeyService {
+    pub fn new(rpc_binder: bool) -> Result<Self> {
+        let keystore_service = wait_for_interface::<dyn IKeystoreService>(KEYSTORE_SERVICE_NAME)
+            .context("No Keystore service")?;
 
-impl ICompOsKeyService for CompOsKeyService {
-    fn generateSigningKey(&self) -> binder::Result<CompOsKeyData> {
-        self.do_generate()
-            .map_err(|e| new_binder_exception(ExceptionCode::ILLEGAL_STATE, e.to_string()))
-    }
-
-    fn verifySigningKey(&self, key_blob: &[u8], public_key: &[u8]) -> binder::Result<bool> {
-        Ok(if let Err(e) = self.do_verify(key_blob, public_key) {
-            warn!("Signing key verification failed: {}", e.to_string());
-            false
-        } else {
-            true
+        let namespace =
+            if rpc_binder { KeystoreNamespace::VmPayload } else { KeystoreNamespace::Odsign };
+        Ok(CompOsKeyService {
+            namespace,
+            random: SystemRandom::new(),
+            security_level: keystore_service
+                .getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT)
+                .context("Getting SecurityLevel failed")?,
         })
     }
 
-    fn sign(&self, key_blob: &[u8], data: &[u8]) -> binder::Result<Vec<u8>> {
-        self.do_sign(key_blob, data)
-            .map_err(|e| new_binder_exception(ExceptionCode::ILLEGAL_STATE, e.to_string()))
-    }
-
-    fn getCompService(&self, key_blob: &[u8]) -> binder::Result<Strong<dyn ICompService>> {
-        let signer =
-            Box::new(CompOsSigner { key_blob: key_blob.to_owned(), key_service: self.clone() });
-        let debuggable = true;
-        Ok(compsvc::new_binder(
-            "/apex/com.android.art/bin/dex2oat64".to_owned(),
-            debuggable,
-            Some(signer),
-        ))
-    }
-}
-
-/// Constructs a new Binder error `Status` with the given `ExceptionCode` and message.
-fn new_binder_exception<T: AsRef<str>>(exception: ExceptionCode, message: T) -> Status {
-    Status::new_exception(exception, CString::new(message.as_ref()).ok().as_deref())
-}
-
-struct CompOsSigner {
-    key_blob: Vec<u8>,
-    key_service: CompOsKeyService,
-}
-
-impl Signer for CompOsSigner {
-    fn sign(&self, data: &[u8]) -> Result<Vec<u8>> {
-        self.key_service.do_sign(&self.key_blob, data)
-    }
-}
-
-impl CompOsKeyService {
-    fn do_generate(&self) -> Result<CompOsKeyData> {
+    pub fn do_generate(&self) -> Result<CompOsKeyData> {
         let key_descriptor = KeyDescriptor { nspace: self.namespace as i64, ..BLOB_KEY_DESCRIPTOR };
         let key_parameters =
             [PURPOSE_SIGN, ALGORITHM, PADDING, DIGEST, KEY_SIZE, EXPONENT, NO_AUTH_REQUIRED];
@@ -168,7 +107,7 @@
         }
     }
 
-    fn do_verify(&self, key_blob: &[u8], public_key: &[u8]) -> Result<()> {
+    pub fn do_verify(&self, key_blob: &[u8], public_key: &[u8]) -> Result<()> {
         let mut data = [0u8; 32];
         self.random.fill(&mut data).context("No random data")?;
 
@@ -181,7 +120,7 @@
         Ok(())
     }
 
-    fn do_sign(&self, key_blob: &[u8], data: &[u8]) -> Result<Vec<u8>> {
+    pub fn do_sign(&self, key_blob: &[u8], data: &[u8]) -> Result<Vec<u8>> {
         let key_descriptor = KeyDescriptor {
             nspace: self.namespace as i64,
             blob: Some(key_blob.to_vec()),
diff --git a/compos/src/compsvc.rs b/compos/src/compsvc.rs
index 14b520e..b5edd98 100644
--- a/compos/src/compsvc.rs
+++ b/compos/src/compsvc.rs
@@ -14,99 +14,76 @@
  * limitations under the License.
  */
 
-//! compsvc is a service to run computational tasks in a PVM upon request. It is able to set up
+//! compsvc is a service to run compilation tasks in a PVM upon request. It is able to set up
 //! file descriptors backed by authfs (via authfs_service) and pass the file descriptors to the
-//! actual tasks.
+//! actual compiler.
 
 use anyhow::Result;
-use log::error;
-use minijail::{self, Minijail};
+use log::warn;
 use std::ffi::CString;
-use std::os::unix::io::AsRawFd;
-use std::path::{Path, PathBuf};
+use std::path::PathBuf;
 
-use crate::signer::Signer;
-use authfs_aidl_interface::aidl::com::android::virt::fs::{
-    AuthFsConfig::AuthFsConfig, IAuthFs::IAuthFs, IAuthFsService::IAuthFsService,
-    InputFdAnnotation::InputFdAnnotation, OutputFdAnnotation::OutputFdAnnotation,
+use crate::compilation::compile;
+use crate::compos_key_service::CompOsKeyService;
+use authfs_aidl_interface::aidl::com::android::virt::fs::IAuthFsService::IAuthFsService;
+use compos_aidl_interface::aidl::com::android::compos::{
+    CompOsKeyData::CompOsKeyData,
+    ICompOsService::{BnCompOsService, ICompOsService},
+    Metadata::Metadata,
 };
-use authfs_aidl_interface::binder::ParcelFileDescriptor;
-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::{
-    BinderFeatures, ExceptionCode, Interface, Result as BinderResult, Status, StatusCode, Strong,
+    BinderFeatures, ExceptionCode, Interface, Result as BinderResult, Status, Strong,
 };
 
 const AUTHFS_SERVICE_NAME: &str = "authfs_service";
+const DEX2OAT_PATH: &str = "/apex/com.android.art/bin/dex2oat64";
 
-/// The number that represents the file descriptor number expecting by the task. The number may be
-/// meaningless in the current process.
-pub type PseudoRawFd = i32;
-
-/// Constructs a binder object that implements ICompService. task_bin is the path to the binary that will
-/// be run when execute() is called. If debuggable is true then stdout/stderr from the binary will be
-/// available for debugging.
-pub fn new_binder(
-    task_bin: String,
-    debuggable: bool,
-    signer: Option<Box<dyn Signer>>,
-) -> Strong<dyn ICompService> {
-    let service = CompService { task_bin: PathBuf::from(task_bin), debuggable, signer };
-    BnCompService::new_binder(service, BinderFeatures::default())
+/// Constructs a binder object that implements ICompOsService.
+pub fn new_binder(rpc_binder: bool) -> Result<Strong<dyn ICompOsService>> {
+    let service = CompOsService {
+        dex2oat_path: PathBuf::from(DEX2OAT_PATH),
+        key_service: CompOsKeyService::new(rpc_binder)?,
+    };
+    Ok(BnCompOsService::new_binder(service, BinderFeatures::default()))
 }
 
-struct CompService {
-    task_bin: PathBuf,
-    debuggable: bool,
-    #[allow(dead_code)] // TODO: Make use of this
-    signer: Option<Box<dyn Signer>>,
+struct CompOsService {
+    dex2oat_path: PathBuf,
+    key_service: CompOsKeyService,
 }
 
-impl Interface for CompService {}
+impl Interface for CompOsService {}
 
-impl ICompService for CompService {
+impl ICompOsService for CompOsService {
     fn execute(&self, args: &[String], metadata: &Metadata) -> BinderResult<i8> {
-        // Mount authfs (via authfs_service).
-        let authfs_config = build_authfs_config(metadata);
-        let authfs = get_authfs_service()?.mount(&authfs_config)?;
+        let authfs_service = get_authfs_service()?;
+        compile(&self.dex2oat_path, args, authfs_service, metadata).map_err(|e| {
+            new_binder_exception(
+                ExceptionCode::SERVICE_SPECIFIC,
+                format!("Compilation failed: {}", e),
+            )
+        })
+    }
 
-        // The task expects to receive FD numbers that match its flags (e.g. --zip-fd=42) prepared
-        // on the host side. Since the local FD opened from authfs (e.g. /authfs/42) may not match
-        // the task's expectation, prepare a FD mapping and let minijail prepare the correct FD
-        // setup.
-        let fd_mapping =
-            open_authfs_files_for_fd_mapping(&authfs, &authfs_config).map_err(|e| {
-                new_binder_exception(
-                    ExceptionCode::SERVICE_SPECIFIC,
-                    format!("Failed to create FDs on authfs: {:?}", e),
-                )
-            })?;
+    fn generateSigningKey(&self) -> BinderResult<CompOsKeyData> {
+        self.key_service
+            .do_generate()
+            .map_err(|e| new_binder_exception(ExceptionCode::ILLEGAL_STATE, e.to_string()))
+    }
 
-        let jail =
-            spawn_jailed_task(&self.task_bin, args, fd_mapping, self.debuggable).map_err(|e| {
-                new_binder_exception(
-                    ExceptionCode::SERVICE_SPECIFIC,
-                    format!("Failed to spawn the task: {:?}", e),
-                )
-            })?;
-        let jail_result = jail.wait();
+    fn verifySigningKey(&self, key_blob: &[u8], public_key: &[u8]) -> BinderResult<bool> {
+        Ok(if let Err(e) = self.key_service.do_verify(key_blob, public_key) {
+            warn!("Signing key verification failed: {}", e.to_string());
+            false
+        } else {
+            true
+        })
+    }
 
-        // Be explicit about the lifetime, which should last at least until the task is finished.
-        drop(authfs);
-
-        match jail_result {
-            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 minijail error: {}", e);
-                Err(Status::from(StatusCode::UNKNOWN_ERROR))
-            }
-        }
+    fn sign(&self, key_blob: &[u8], data: &[u8]) -> BinderResult<Vec<u8>> {
+        self.key_service
+            .do_sign(key_blob, data)
+            .map_err(|e| new_binder_exception(ExceptionCode::ILLEGAL_STATE, e.to_string()))
     }
 }
 
@@ -114,67 +91,6 @@
     Ok(authfs_aidl_interface::binder::get_interface(AUTHFS_SERVICE_NAME)?)
 }
 
-fn build_authfs_config(metadata: &Metadata) -> AuthFsConfig {
-    AuthFsConfig {
-        port: 3264, // TODO: support dynamic port
-        inputFdAnnotations: metadata
-            .input_fd_annotations
-            .iter()
-            .map(|x| InputFdAnnotation { fd: x.fd, fileSize: x.file_size })
-            .collect(),
-        outputFdAnnotations: metadata
-            .output_fd_annotations
-            .iter()
-            .map(|x| OutputFdAnnotation { fd: x.fd })
-            .collect(),
-    }
-}
-
-fn open_authfs_files_for_fd_mapping(
-    authfs: &Strong<dyn IAuthFs>,
-    config: &AuthFsConfig,
-) -> Result<Vec<(ParcelFileDescriptor, PseudoRawFd)>> {
-    let mut fd_mapping = Vec::new();
-
-    let results: Result<Vec<_>> = config
-        .inputFdAnnotations
-        .iter()
-        .map(|annotation| Ok((authfs.openFile(annotation.fd, false)?, annotation.fd)))
-        .collect();
-    fd_mapping.append(&mut results?);
-
-    let results: Result<Vec<_>> = config
-        .outputFdAnnotations
-        .iter()
-        .map(|annotation| Ok((authfs.openFile(annotation.fd, true)?, annotation.fd)))
-        .collect();
-    fd_mapping.append(&mut results?);
-
-    Ok(fd_mapping)
-}
-
-fn spawn_jailed_task(
-    executable: &Path,
-    args: &[String],
-    fd_mapping: Vec<(ParcelFileDescriptor, PseudoRawFd)>,
-    debuggable: bool,
-) -> Result<Minijail> {
-    // TODO(b/185175567): Run in a more restricted sandbox.
-    let jail = Minijail::new()?;
-
-    let mut preserve_fds = if debuggable {
-        // Inherit/redirect stdout/stderr for debugging, assuming no conflict
-        vec![(1, 1), (2, 2)]
-    } else {
-        vec![]
-    };
-
-    preserve_fds.extend(fd_mapping.iter().map(|(f, id)| (f.as_raw_fd(), *id)));
-
-    let _pid = jail.run_remap(executable, preserve_fds.as_slice(), args)?;
-    Ok(jail)
-}
-
 fn new_binder_exception<T: AsRef<str>>(exception: ExceptionCode, message: T) -> Status {
     Status::new_exception(exception, CString::new(message.as_ref()).as_deref().ok())
 }
diff --git a/compos/src/compsvc_main.rs b/compos/src/compsvc_main.rs
index 9f12132..48e37b6 100644
--- a/compos/src/compsvc_main.rs
+++ b/compos/src/compsvc_main.rs
@@ -16,11 +16,10 @@
 
 //! A tool to start a standalone compsvc server, either in the host using Binder or in a VM using
 //! RPC binder over vsock.
-//!
-//! Example:
-//! $ compsvc /system/bin/sleep
 
 mod common;
+mod compilation;
+mod compos_key_service;
 mod compsvc;
 mod signer;
 
@@ -31,27 +30,17 @@
 use log::debug;
 
 struct Config {
-    task_bin: String,
     rpc_binder: bool,
-    debuggable: bool,
 }
 
 fn parse_args() -> Result<Config> {
     #[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))
         .arg(clap::Arg::with_name("rpc_binder")
              .long("rpc-binder"))
         .get_matches();
 
-    Ok(Config {
-        task_bin: matches.value_of("task_bin").unwrap().to_string(),
-        rpc_binder: matches.is_present("rpc_binder"),
-        debuggable: matches.is_present("debug"),
-    })
+    Ok(Config { rpc_binder: matches.is_present("rpc_binder") })
 }
 
 fn main() -> Result<()> {
@@ -60,7 +49,7 @@
     );
 
     let config = parse_args()?;
-    let mut service = compsvc::new_binder(config.task_bin, config.debuggable, None).as_binder();
+    let mut service = compsvc::new_binder(config.rpc_binder)?.as_binder();
     if config.rpc_binder {
         debug!("compsvc is starting as a rpc service.");
         // SAFETY: Service ownership is transferring to the server and won't be valid afterward.
diff --git a/compos/src/pvm_exec.rs b/compos/src/pvm_exec.rs
index 03fbf72..2218d10 100644
--- a/compos/src/pvm_exec.rs
+++ b/compos/src/pvm_exec.rs
@@ -36,7 +36,7 @@
 use std::process::exit;
 
 use compos_aidl_interface::aidl::com::android::compos::{
-    ICompService::ICompService, InputFdAnnotation::InputFdAnnotation, Metadata::Metadata,
+    ICompOsService::ICompOsService, InputFdAnnotation::InputFdAnnotation, Metadata::Metadata,
     OutputFdAnnotation::OutputFdAnnotation,
 };
 use compos_aidl_interface::binder::Strong;
@@ -46,18 +46,18 @@
 
 const FD_SERVER_BIN: &str = "/apex/com.android.virt/bin/fd_server";
 
-fn get_local_service() -> Result<Strong<dyn ICompService>> {
+fn get_local_service() -> Result<Strong<dyn ICompOsService>> {
     compos_aidl_interface::binder::get_interface(SERVICE_NAME).context("get local binder")
 }
 
-fn get_rpc_binder(cid: u32) -> Result<Strong<dyn ICompService>> {
+fn get_rpc_binder(cid: u32) -> Result<Strong<dyn ICompOsService>> {
     // SAFETY: AIBinder returned by RpcClient has correct reference count, and the ownership can be
     // safely taken by new_spibinder.
     let ibinder = unsafe {
         new_spibinder(binder_rpc_unstable_bindgen::RpcClient(cid, VSOCK_PORT) as *mut AIBinder)
     };
     if let Some(ibinder) = ibinder {
-        <dyn ICompService>::try_from(ibinder).context("Cannot connect to RPC service")
+        <dyn ICompOsService>::try_from(ibinder).context("Cannot connect to RPC service")
     } else {
         bail!("Invalid raw AIBinder")
     }
diff --git a/compos/tests/java/android/compos/test/ComposKeyTestCase.java b/compos/tests/java/android/compos/test/ComposKeyTestCase.java
index 654dc0b..6ef82f7 100644
--- a/compos/tests/java/android/compos/test/ComposKeyTestCase.java
+++ b/compos/tests/java/android/compos/test/ComposKeyTestCase.java
@@ -131,7 +131,7 @@
                         getBuild(),
                         apkName,
                         packageName,
-                        "assets/key_service_vm_config.json",
+                        "assets/vm_config.json",
                         /* debug */ true);
         adbConnectToMicrodroid(getDevice(), mCid);
     }
@@ -145,6 +145,6 @@
     }
 
     private boolean isServiceRunning() {
-        return tryRunOnMicrodroid("pidof compos_key_main") != null;
+        return tryRunOnMicrodroid("pidof compsvc") != null;
     }
 }
diff --git a/compos/tests/java/android/compos/test/ComposTestCase.java b/compos/tests/java/android/compos/test/ComposTestCase.java
index 4471e63..f69b7b7 100644
--- a/compos/tests/java/android/compos/test/ComposTestCase.java
+++ b/compos/tests/java/android/compos/test/ComposTestCase.java
@@ -138,7 +138,7 @@
                         apkName,
                         packageName,
                         "assets/vm_config.json",
-                        /* debug */ true);
+                        /* debug */ false);
         adbConnectToMicrodroid(getDevice(), mCid);
     }