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);
}