Merge "Reduce virtualizationservice log level."
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 7be81a8..25908ad 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -17,6 +17,9 @@
     },
     {
       "name": "compsvc_device_tests"
+    },
+    {
+      "name": "compos_key_tests"
     }
   ],
   "postsubmit": [
diff --git a/authfs/src/fusefs.rs b/authfs/src/fusefs.rs
index 03f832d..84129b6 100644
--- a/authfs/src/fusefs.rs
+++ b/authfs/src/fusefs.rs
@@ -49,7 +49,10 @@
 pub type Inode = u64;
 type Handle = u64;
 
-const DEFAULT_METADATA_TIMEOUT: Duration = Duration::from_secs(5);
+/// Maximum time for a file's metadata to be cached by the kernel. Since any file and directory
+/// changes (if not read-only) has to go through AuthFS to be trusted, the timeout can be maximum.
+const DEFAULT_METADATA_TIMEOUT: Duration = Duration::MAX;
+
 const ROOT_INODE: Inode = 1;
 
 /// `AuthFsEntry` defines the filesystem entry type supported by AuthFS.
diff --git a/compos/apex/Android.bp b/compos/apex/Android.bp
index aec3c88..6550b4f 100644
--- a/compos/apex/Android.bp
+++ b/compos/apex/Android.bp
@@ -47,6 +47,7 @@
         "composd_cmd",
 
         // Used in VM
+        "compos_key_helper",
         "compsvc",
     ],
 
diff --git a/compos/compos_key_helper/Android.bp b/compos/compos_key_helper/Android.bp
new file mode 100644
index 0000000..c53d88d
--- /dev/null
+++ b/compos/compos_key_helper/Android.bp
@@ -0,0 +1,47 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_defaults {
+    name: "compos_key_defaults",
+    apex_available: ["com.android.compos"],
+
+    shared_libs: [
+        "libbase",
+        "libbinder_ndk",
+        "libcrypto",
+    ],
+}
+
+cc_library {
+    name: "libcompos_key",
+    defaults: ["compos_key_defaults"],
+    srcs: ["compos_key.cpp"],
+
+    shared_libs: [
+        "android.hardware.security.dice-V1-ndk",
+        "android.security.dice-ndk",
+    ],
+}
+
+cc_binary {
+    name: "compos_key_helper",
+    defaults: ["compos_key_defaults"],
+    srcs: ["compos_key_main.cpp"],
+
+    static_libs: ["libcompos_key"],
+    shared_libs: [
+        "android.security.dice-ndk",
+    ],
+}
+
+cc_test {
+    name: "compos_key_tests",
+    defaults: ["compos_key_defaults"],
+    test_suites: [
+        "general-tests",
+    ],
+
+    srcs: ["compos_key_test.cpp"],
+    static_libs: ["libcompos_key"],
+}
diff --git a/compos/compos_key_helper/compos_key.cpp b/compos/compos_key_helper/compos_key.cpp
new file mode 100644
index 0000000..84a736d
--- /dev/null
+++ b/compos/compos_key_helper/compos_key.cpp
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2022 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.
+ */
+
+#include "compos_key.h"
+
+#include <aidl/android/security/dice/IDiceNode.h>
+#include <android/binder_auto_utils.h>
+#include <android/binder_manager.h>
+#include <openssl/digest.h>
+#include <openssl/hkdf.h>
+#include <openssl/mem.h>
+#include <unistd.h>
+
+using aidl::android::hardware::security::dice::BccHandover;
+using aidl::android::hardware::security::dice::InputValues;
+using aidl::android::security::dice::IDiceNode;
+using android::base::ErrnoError;
+using android::base::Error;
+using android::base::Result;
+
+// Used to ensure the key we derive is distinct from any other.
+constexpr const char* kSigningKeyInfo = "CompOS signing key";
+
+Result<Ed25519KeyPair> deriveKeyFromSecret(const uint8_t* secret, size_t secret_size) {
+    // Ed25519 private keys are derived from a 32 byte seed:
+    // https://datatracker.ietf.org/doc/html/rfc8032#section-5.1.5
+    std::array<uint8_t, 32> seed;
+
+    // We derive the seed from the secret using HKDF - see
+    // https://datatracker.ietf.org/doc/html/rfc5869#section-2.
+    if (!HKDF(seed.data(), seed.size(), EVP_sha256(), secret, secret_size, /*salt=*/nullptr,
+              /*salt_len=*/0, reinterpret_cast<const uint8_t*>(kSigningKeyInfo),
+              strlen(kSigningKeyInfo))) {
+        return Error() << "HKDF failed";
+    }
+
+    Ed25519KeyPair result;
+    ED25519_keypair_from_seed(result.public_key.data(), result.private_key.data(), seed.data());
+    return result;
+}
+
+Result<Signature> sign(const PrivateKey& private_key, const uint8_t* data, size_t data_size) {
+    Signature result;
+    if (!ED25519_sign(result.data(), data, data_size, private_key.data())) {
+        return Error() << "Failed to sign";
+    }
+    return result;
+}
+
+bool verify(const PublicKey& public_key, const Signature& signature, const uint8_t* data,
+            size_t data_size) {
+    return ED25519_verify(data, data_size, signature.data(), public_key.data()) == 1;
+}
+
+Result<Ed25519KeyPair> deriveKeyFromDice() {
+    ndk::SpAIBinder binder{AServiceManager_getService("android.security.dice.IDiceNode")};
+    auto dice_node = IDiceNode::fromBinder(binder);
+    if (!dice_node) {
+        return Error() << "Unable to connect to IDiceNode";
+    }
+
+    const std::vector<InputValues> empty_input_values;
+    BccHandover bcc;
+    auto status = dice_node->derive(empty_input_values, &bcc);
+    if (!status.isOk()) {
+        return Error() << "Derive failed: " << status.getDescription();
+    }
+
+    // We use the sealing CDI because we want stability - the key needs to be the same
+    // for any instance of the "same" VM.
+    return deriveKeyFromSecret(bcc.cdiSeal.data(), bcc.cdiSeal.size());
+}
diff --git a/compos/compos_key_helper/compos_key.h b/compos/compos_key_helper/compos_key.h
new file mode 100644
index 0000000..520f846
--- /dev/null
+++ b/compos/compos_key_helper/compos_key.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2022 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.
+ */
+
+#pragma once
+
+#include <android-base/result.h>
+#include <openssl/curve25519.h>
+
+#include <array>
+
+using PrivateKey = std::array<uint8_t, ED25519_PRIVATE_KEY_LEN>;
+using PublicKey = std::array<uint8_t, ED25519_PUBLIC_KEY_LEN>;
+using Signature = std::array<uint8_t, ED25519_SIGNATURE_LEN>;
+
+struct Ed25519KeyPair {
+    PrivateKey private_key;
+    PublicKey public_key;
+};
+
+android::base::Result<Ed25519KeyPair> deriveKeyFromSecret(const uint8_t* secret,
+                                                          size_t secret_size);
+
+android::base::Result<Ed25519KeyPair> deriveKeyFromDice();
+
+android::base::Result<Signature> sign(const PrivateKey& private_key, const uint8_t* data,
+                                      size_t data_size);
+
+bool verify(const PublicKey& public_key, const Signature& signature, const uint8_t* data,
+            size_t data_size);
diff --git a/compos/compos_key_helper/compos_key_main.cpp b/compos/compos_key_helper/compos_key_main.cpp
new file mode 100644
index 0000000..70f7539
--- /dev/null
+++ b/compos/compos_key_helper/compos_key_main.cpp
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2022 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.
+ */
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <unistd.h>
+
+#include <iostream>
+#include <string_view>
+
+#include "compos_key.h"
+
+using android::base::ErrnoError;
+using android::base::WriteFully;
+using namespace std::literals;
+
+int main(int argc, char** argv) {
+    android::base::InitLogging(argv, android::base::LogdLogger(android::base::SYSTEM));
+
+    if (argc == 2) {
+        if (argv[1] == "public_key"sv) {
+            auto key_pair = deriveKeyFromDice();
+            if (!key_pair.ok()) {
+                LOG(ERROR) << key_pair.error();
+                return 1;
+            }
+            if (!WriteFully(STDOUT_FILENO, key_pair->public_key.data(),
+                            key_pair->public_key.size())) {
+                PLOG(ERROR) << "Write failed";
+                return 1;
+            }
+            return 0;
+        }
+    }
+
+    std::cerr << "Usage:\n"
+                 "compos_key_helper public_key   Write current public key to stdout\n";
+    return 1;
+}
diff --git a/compos/compos_key_helper/compos_key_test.cpp b/compos/compos_key_helper/compos_key_test.cpp
new file mode 100644
index 0000000..e5c4768
--- /dev/null
+++ b/compos/compos_key_helper/compos_key_test.cpp
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2022 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.
+ */
+
+#include "compos_key.h"
+
+#include <vector>
+
+#include "gtest/gtest.h"
+
+const std::vector<uint8_t> secret = {1, 2, 3};
+const std::vector<uint8_t> other_secret = {3, 2, 3};
+const std::vector<uint8_t> data = {42, 180, 65, 0};
+
+struct ComposKeyTest : public testing::Test {
+    Ed25519KeyPair key_pair;
+
+    void SetUp() override {
+        auto key_pair = deriveKeyFromSecret(secret.data(), secret.size());
+        ASSERT_TRUE(key_pair.ok()) << key_pair.error();
+        this->key_pair = *key_pair;
+    }
+};
+
+TEST_F(ComposKeyTest, SameSecretSameKey) {
+    auto other_key_pair = deriveKeyFromSecret(secret.data(), secret.size());
+    ASSERT_TRUE(other_key_pair.ok()) << other_key_pair.error();
+
+    ASSERT_EQ(key_pair.private_key, other_key_pair->private_key);
+    ASSERT_EQ(key_pair.public_key, other_key_pair->public_key);
+}
+
+TEST_F(ComposKeyTest, DifferentSecretDifferentKey) {
+    auto other_key_pair = deriveKeyFromSecret(other_secret.data(), other_secret.size());
+    ASSERT_TRUE(other_key_pair.ok()) << other_key_pair.error();
+
+    ASSERT_NE(key_pair.private_key, other_key_pair->private_key);
+    ASSERT_NE(key_pair.public_key, other_key_pair->public_key);
+}
+
+TEST_F(ComposKeyTest, CanVerifyValidSignature) {
+    auto signature = sign(key_pair.private_key, data.data(), data.size());
+    ASSERT_TRUE(signature.ok()) << signature.error();
+
+    bool verified = verify(key_pair.public_key, *signature, data.data(), data.size());
+    ASSERT_TRUE(verified);
+}
+
+TEST_F(ComposKeyTest, WrongSignatureDoesNotVerify) {
+    auto signature = sign(key_pair.private_key, data.data(), data.size());
+    ASSERT_TRUE(signature.ok()) << signature.error();
+
+    (*signature)[0] ^= 1;
+
+    bool verified = verify(key_pair.public_key, *signature, data.data(), data.size());
+    ASSERT_FALSE(verified);
+}
+
+TEST_F(ComposKeyTest, WrongDataDoesNotVerify) {
+    auto signature = sign(key_pair.private_key, data.data(), data.size());
+    ASSERT_TRUE(signature.ok()) << signature.error();
+
+    auto other_data = data;
+    other_data[0] ^= 1;
+
+    bool verified = verify(key_pair.public_key, *signature, other_data.data(), other_data.size());
+    ASSERT_FALSE(verified);
+}
+
+TEST_F(ComposKeyTest, WrongKeyDoesNotVerify) {
+    auto signature = sign(key_pair.private_key, data.data(), data.size());
+
+    auto other_key_pair = deriveKeyFromSecret(other_secret.data(), other_secret.size());
+    ASSERT_TRUE(other_key_pair.ok()) << other_key_pair.error();
+
+    bool verified = verify(other_key_pair->public_key, *signature, data.data(), data.size());
+    ASSERT_FALSE(verified);
+}
diff --git a/compos/compos_key_helper/tests/AndroidTest.xml b/compos/compos_key_helper/tests/AndroidTest.xml
new file mode 100644
index 0000000..3c1c657
--- /dev/null
+++ b/compos/compos_key_helper/tests/AndroidTest.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2022 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.
+-->
+<configuration description="Tests for compos_key">
+    <test class="com.android.tradefed.testtype.GTest" >
+        <option name="module-name" value="compos_key_tests" />
+    </test>
+</configuration>
diff --git a/compos/src/compilation.rs b/compos/src/compilation.rs
index ae4a29d..6049991 100644
--- a/compos/src/compilation.rs
+++ b/compos/src/compilation.rs
@@ -22,12 +22,9 @@
 use std::collections::HashMap;
 use std::env;
 use std::ffi::OsString;
-use std::fs::read_dir;
 use std::path::{self, Path, PathBuf};
 use std::process::Command;
 
-use crate::artifact_signer::ArtifactSigner;
-use crate::signing_key::DiceSigner;
 use authfs_aidl_interface::aidl::com::android::virt::fs::{
     AuthFsConfig::{
         AuthFsConfig, InputDirFdAnnotation::InputDirFdAnnotation,
@@ -105,12 +102,15 @@
     system_properties::read_bool(name, false).unwrap_or(false)
 }
 
-pub fn odrefresh(
+pub fn odrefresh<F>(
     odrefresh_path: &Path,
     context: OdrefreshContext,
     authfs_service: Strong<dyn IAuthFsService>,
-    signer: DiceSigner,
-) -> Result<ExitCode> {
+    success_fn: F,
+) -> Result<ExitCode>
+where
+    F: FnOnce(PathBuf) -> Result<()>,
+{
     // Mount authfs (via authfs_service). The authfs instance unmounts once the `authfs` variable
     // is out of scope.
     let authfs_config = AuthFsConfig {
@@ -183,13 +183,8 @@
     info!("odrefresh exited with {:?}", exit_code);
 
     if exit_code == ExitCode::CompilationSuccess {
-        // authfs only shows us the files we created, so it's ok to just sign everything under
-        // the target directory.
         let target_dir = art_apex_data.join(context.target_dir_name);
-        let mut artifact_signer = ArtifactSigner::new(&target_dir);
-        add_artifacts(&target_dir, &mut artifact_signer)?;
-
-        artifact_signer.write_info_and_signature(signer, &target_dir.join("compos.info"))?;
+        success_fn(target_dir)?;
     }
 
     Ok(exit_code)
@@ -245,24 +240,6 @@
     Ok(())
 }
 
-fn add_artifacts(target_dir: &Path, artifact_signer: &mut ArtifactSigner) -> Result<()> {
-    for entry in
-        read_dir(&target_dir).with_context(|| format!("Traversing {}", target_dir.display()))?
-    {
-        let entry = entry?;
-        let file_type = entry.file_type()?;
-        if file_type.is_dir() {
-            add_artifacts(&entry.path(), artifact_signer)?;
-        } else if file_type.is_file() {
-            artifact_signer.add_artifact(&entry.path())?;
-        } else {
-            // authfs shouldn't create anything else, but just in case
-            bail!("Unexpected file type in artifacts: {:?}", entry);
-        }
-    }
-    Ok(())
-}
-
 fn spawn_jailed_task(executable: &Path, args: &[String], env_vars: &[String]) -> Result<Minijail> {
     // TODO(b/185175567): Run in a more restricted sandbox.
     let jail = Minijail::new()?;
diff --git a/compos/src/compsvc.rs b/compos/src/compsvc.rs
index 3ec15dd..5f3ee62 100644
--- a/compos/src/compsvc.rs
+++ b/compos/src/compsvc.rs
@@ -18,17 +18,19 @@
 //! file descriptors backed by authfs (via authfs_service) and pass the file descriptors to the
 //! actual compiler.
 
-use anyhow::{Context, Result};
+use anyhow::{bail, Context, Result};
 use binder_common::new_binder_exception;
 use compos_common::binder::to_binder_result;
 use log::warn;
 use std::default::Default;
-use std::path::PathBuf;
+use std::fs::read_dir;
+use std::path::{Path, PathBuf};
 use std::sync::RwLock;
 
+use crate::artifact_signer::ArtifactSigner;
 use crate::compilation::{odrefresh, OdrefreshContext};
 use crate::dice::Dice;
-use crate::signing_key::{DiceSigner, DiceSigningKey};
+use crate::signing_key::DiceSigningKey;
 use authfs_aidl_interface::aidl::com::android::virt::fs::IAuthFsService::IAuthFsService;
 use compos_aidl_interface::aidl::com::android::compos::{
     CompOsKeyData::CompOsKeyData,
@@ -57,17 +59,6 @@
     key_blob: RwLock<Vec<u8>>,
 }
 
-impl CompOsService {
-    fn new_signer(&self) -> BinderResult<DiceSigner> {
-        let key = &*self.key_blob.read().unwrap();
-        if key.is_empty() {
-            Err(new_binder_exception(ExceptionCode::ILLEGAL_STATE, "Key is not initialized"))
-        } else {
-            to_binder_result(self.signing_key.new_signer(key))
-        }
-    }
-}
-
 impl Interface for CompOsService {}
 
 impl ICompOsService for CompOsService {
@@ -91,6 +82,14 @@
         zygote_arch: &str,
         system_server_compiler_filter: &str,
     ) -> BinderResult<i8> {
+        let key = &*self.key_blob.read().unwrap();
+        if key.is_empty() {
+            return Err(new_binder_exception(
+                ExceptionCode::ILLEGAL_STATE,
+                "Key is not initialized",
+            ));
+        }
+
         let context = to_binder_result(OdrefreshContext::new(
             compilation_mode,
             system_dir_fd,
@@ -103,8 +102,16 @@
 
         let authfs_service = get_authfs_service()?;
         let exit_code = to_binder_result(
-            odrefresh(&self.odrefresh_path, context, authfs_service, self.new_signer()?)
-                .context("odrefresh failed"),
+            odrefresh(&self.odrefresh_path, context, authfs_service, |output_dir| {
+                // authfs only shows us the files we created, so it's ok to just sign everything
+                // under the output directory.
+                let mut artifact_signer = ArtifactSigner::new(&output_dir);
+                add_artifacts(&output_dir, &mut artifact_signer)?;
+
+                let signer = to_binder_result(self.signing_key.new_signer(key))?;
+                artifact_signer.write_info_and_signature(signer, &output_dir.join("compos.info"))
+            })
+            .context("odrefresh failed"),
         )?;
         Ok(exit_code as i8)
     }
@@ -126,3 +133,21 @@
 fn get_authfs_service() -> BinderResult<Strong<dyn IAuthFsService>> {
     Ok(authfs_aidl_interface::binder::get_interface(AUTHFS_SERVICE_NAME)?)
 }
+
+fn add_artifacts(target_dir: &Path, artifact_signer: &mut ArtifactSigner) -> Result<()> {
+    for entry in
+        read_dir(&target_dir).with_context(|| format!("Traversing {}", target_dir.display()))?
+    {
+        let entry = entry?;
+        let file_type = entry.file_type()?;
+        if file_type.is_dir() {
+            add_artifacts(&entry.path(), artifact_signer)?;
+        } else if file_type.is_file() {
+            artifact_signer.add_artifact(&entry.path())?;
+        } else {
+            // authfs shouldn't create anything else, but just in case
+            bail!("Unexpected file type in artifacts: {:?}", entry);
+        }
+    }
+    Ok(())
+}
diff --git a/microdroid/dice/android.hardware.security.dice-service.microdroid.rc b/microdroid/dice/android.hardware.security.dice-service.microdroid.rc
index 162081e..7d9d441 100644
--- a/microdroid/dice/android.hardware.security.dice-service.microdroid.rc
+++ b/microdroid/dice/android.hardware.security.dice-service.microdroid.rc
@@ -1,3 +1,3 @@
 service vendor.dice-microdroid /vendor/bin/hw/android.hardware.security.dice-service.microdroid
-    class early_hal
-    user nobody
+    user diced
+    group diced
diff --git a/microdroid/dice/service.rs b/microdroid/dice/service.rs
index 3401654..8cb5cc3 100644
--- a/microdroid/dice/service.rs
+++ b/microdroid/dice/service.rs
@@ -14,17 +14,103 @@
 
 //! Main entry point for the microdroid IDiceDevice HAL implementation.
 
-use anyhow::Result;
+use anyhow::{bail, Error, Result};
+use byteorder::{NativeEndian, ReadBytesExt};
 use diced::{
     dice,
     hal_node::{DiceArtifacts, DiceDevice, ResidentHal, UpdatableDiceArtifacts},
 };
+use libc::{c_void, mmap, munmap, MAP_FAILED, MAP_PRIVATE, PROT_READ};
 use serde::{Deserialize, Serialize};
+use std::fs;
+use std::os::unix::io::AsRawFd;
 use std::panic;
+use std::path::{Path, PathBuf};
+use std::ptr::null_mut;
+use std::slice;
 use std::sync::Arc;
 
 const DICE_HAL_SERVICE_NAME: &str = "android.hardware.security.dice.IDiceDevice/default";
 
+/// Artifacts that are mapped into the process address space from the driver.
+struct MappedDriverArtifacts<'a> {
+    mmap_addr: *mut c_void,
+    mmap_size: usize,
+    cdi_attest: &'a [u8; dice::CDI_SIZE],
+    cdi_seal: &'a [u8; dice::CDI_SIZE],
+    bcc: &'a [u8],
+}
+
+impl MappedDriverArtifacts<'_> {
+    fn new(driver_path: &Path) -> Result<Self> {
+        let mut file = fs::File::open(driver_path)
+            .map_err(|error| Error::new(error).context("Opening driver"))?;
+        let mmap_size =
+            file.read_u64::<NativeEndian>()
+                .map_err(|error| Error::new(error).context("Reading driver"))? as usize;
+        // It's safe to map the driver as the service will only create a single
+        // mapping per process.
+        let mmap_addr = unsafe {
+            let fd = file.as_raw_fd();
+            mmap(null_mut(), mmap_size, PROT_READ, MAP_PRIVATE, fd, 0)
+        };
+        if mmap_addr == MAP_FAILED {
+            bail!("Failed to mmap {:?}", driver_path);
+        }
+        // The slice is created for the region of memory that was just
+        // successfully mapped into the process address space so it will be
+        // accessible and not referenced from anywhere else.
+        let mmap_buf =
+            unsafe { slice::from_raw_parts((mmap_addr as *const u8).as_ref().unwrap(), mmap_size) };
+        // Very inflexible parsing / validation of the BccHandover data. Assumes deterministically
+        // encoded CBOR.
+        //
+        // BccHandover = {
+        //   1 : bstr .size 32,     ; CDI_Attest
+        //   2 : bstr .size 32,     ; CDI_Seal
+        //   3 : Bcc,               ; Certificate chain
+        // }
+        if mmap_buf[0..4] != [0xa3, 0x01, 0x58, 0x20]
+            || mmap_buf[36..39] != [0x02, 0x58, 0x20]
+            || mmap_buf[71] != 0x03
+        {
+            bail!("BccHandover format mismatch");
+        }
+        Ok(Self {
+            mmap_addr,
+            mmap_size,
+            cdi_attest: mmap_buf[4..36].try_into().unwrap(),
+            cdi_seal: mmap_buf[39..71].try_into().unwrap(),
+            bcc: &mmap_buf[72..],
+        })
+    }
+}
+
+impl Drop for MappedDriverArtifacts<'_> {
+    fn drop(&mut self) {
+        // All references to the mapped region have the same lifetime as self.
+        // Since self is being dropped, so are all the references to the mapped
+        // region meaning its safe to unmap.
+        let ret = unsafe { munmap(self.mmap_addr, self.mmap_size) };
+        if ret != 0 {
+            log::warn!("Failed to munmap ({})", ret);
+        }
+    }
+}
+
+impl DiceArtifacts for MappedDriverArtifacts<'_> {
+    fn cdi_attest(&self) -> &[u8; dice::CDI_SIZE] {
+        self.cdi_attest
+    }
+    fn cdi_seal(&self) -> &[u8; dice::CDI_SIZE] {
+        self.cdi_seal
+    }
+    fn bcc(&self) -> Vec<u8> {
+        // The BCC only contains public information so it's fine to copy.
+        self.bcc.to_vec()
+    }
+}
+
 /// Artifacts that are kept in the process address space after the artifacts
 /// from the driver have been consumed.
 #[derive(Clone, Serialize, Deserialize)]
@@ -49,19 +135,26 @@
 
 #[derive(Clone, Serialize, Deserialize)]
 enum DriverArtifactManager {
+    Driver(PathBuf),
     Updated(RawArtifacts),
 }
 
 impl DriverArtifactManager {
-    fn new() -> Self {
-        // TODO(214231981): replace with true values passed by bootloader
-        let (cdi_attest, cdi_seal, bcc) = diced_sample_inputs::make_sample_bcc_and_cdis()
-            .expect("Failed to create sample dice artifacts.");
-        Self::Updated(RawArtifacts {
-            cdi_attest: cdi_attest[..].try_into().unwrap(),
-            cdi_seal: cdi_seal[..].try_into().unwrap(),
-            bcc,
-        })
+    fn new(driver_path: &Path) -> Self {
+        // TODO(218793274): fail if driver is missing in protected mode
+        if driver_path.exists() {
+            log::info!("Using DICE values from driver");
+            Self::Driver(driver_path.to_path_buf())
+        } else {
+            log::warn!("Using sample DICE values");
+            let (cdi_attest, cdi_seal, bcc) = diced_sample_inputs::make_sample_bcc_and_cdis()
+                .expect("Failed to create sample dice artifacts.");
+            Self::Updated(RawArtifacts {
+                cdi_attest: cdi_attest[..].try_into().unwrap(),
+                cdi_seal: cdi_seal[..].try_into().unwrap(),
+                bcc,
+            })
+        }
     }
 }
 
@@ -71,10 +164,17 @@
         F: FnOnce(&dyn DiceArtifacts) -> Result<T>,
     {
         match self {
+            Self::Driver(driver_path) => f(&MappedDriverArtifacts::new(driver_path.as_path())?),
             Self::Updated(raw_artifacts) => f(raw_artifacts),
         }
     }
     fn update(self, new_artifacts: &impl DiceArtifacts) -> Result<Self> {
+        if let Self::Driver(driver_path) = self {
+            // Writing to the device wipes the artifcates. The string is ignored
+            // by the driver but included for documentation.
+            fs::write(driver_path, "wipe")
+                .map_err(|error| Error::new(error).context("Wiping driver"))?;
+        }
         Ok(Self::Updated(RawArtifacts {
             cdi_attest: *new_artifacts.cdi_attest(),
             cdi_seal: *new_artifacts.cdi_seal(),
@@ -102,7 +202,7 @@
             // Safety: ResidentHal cannot be used in multi threaded processes.
             // This service does not start a thread pool. The main thread is the only thread
             // joining the thread pool, thereby keeping the process single threaded.
-            ResidentHal::new(DriverArtifactManager::new())
+            ResidentHal::new(DriverArtifactManager::new(Path::new("/dev/open-dice0")))
         }
         .expect("Failed to create ResidentHal implementation."),
     );
diff --git a/microdroid/ueventd.rc b/microdroid/ueventd.rc
index 037b8fc..340a1f7 100644
--- a/microdroid/ueventd.rc
+++ b/microdroid/ueventd.rc
@@ -27,3 +27,5 @@
 
 # Virtual console for logcat
 /dev/hvc2                 0660   logd       logd
+
+/dev/open-dice0           0660   diced      diced
diff --git a/microdroid_manager/src/instance.rs b/microdroid_manager/src/instance.rs
index 4d65421..267a0e3 100644
--- a/microdroid_manager/src/instance.rs
+++ b/microdroid_manager/src/instance.rs
@@ -322,7 +322,6 @@
     pub apk_data: ApkData,
     pub extra_apks_data: Vec<ApkData>,
     pub apex_data: Vec<ApexData>,
-    pub bootconfig: Box<[u8]>,
 }
 
 #[derive(Debug, Serialize, Deserialize, PartialEq)]
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index 827f9ff..005baf6 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -34,7 +34,6 @@
 use log::{error, info};
 use microdroid_metadata::{write_metadata, Metadata};
 use microdroid_payload_config::{Task, TaskType, VmPayloadConfig};
-use once_cell::sync::OnceCell;
 use payload::{get_apex_data_from_payload, load_metadata, to_metadata};
 use rand::Fill;
 use ring::digest;
@@ -42,7 +41,6 @@
 use rustutils::system_properties::PropertyWatcher;
 use std::convert::TryInto;
 use std::fs::{self, create_dir, File, OpenOptions};
-use std::io::BufRead;
 use std::os::unix::io::{FromRawFd, IntoRawFd};
 use std::path::Path;
 use std::process::{Child, Command, Stdio};
@@ -308,13 +306,6 @@
 ) -> Result<MicrodroidData> {
     let start_time = SystemTime::now();
 
-    if let Some(saved_bootconfig) = saved_data.map(|d| &d.bootconfig) {
-        ensure!(
-            saved_bootconfig.as_ref() == get_bootconfig()?.as_slice(),
-            MicrodroidError::PayloadChanged(String::from("Bootconfig has changed."))
-        );
-    }
-
     // Verify main APK
     let root_hash = saved_data.map(|d| &d.apk_data.root_hash);
     let root_hash_from_idsig = get_apk_root_hash_from_idsig(MAIN_APK_IDSIG_PATH)?;
@@ -455,7 +446,6 @@
         apk_data: ApkData { root_hash: root_hash_from_idsig, pubkey: main_apk_pubkey },
         extra_apks_data,
         apex_data: apex_data_from_payload,
-        bootconfig: get_bootconfig()?.clone().into_boxed_slice(),
     })
 }
 
@@ -506,35 +496,6 @@
     }
 }
 
-fn get_bootconfig() -> Result<&'static Vec<u8>> {
-    static VAL: OnceCell<Vec<u8>> = OnceCell::new();
-    VAL.get_or_try_init(|| -> Result<Vec<u8>> {
-        let f = File::open("/proc/bootconfig")?;
-
-        // Filter-out androidboot.vbmeta.device which contains UUID of the vbmeta partition. That
-        // UUID could change everytime when the same VM is started because the composite disk image
-        // is ephemeral. A change in UUID is okay as long as other configs (e.g.
-        // androidboot.vbmeta.digest) remain same.
-        Ok(std::io::BufReader::new(f)
-            .lines()
-            // note: this try_fold is to early return when we fail to read a line from the file
-            .try_fold(Vec::new(), |mut lines, line| {
-                line.map(|s| {
-                    lines.push(s);
-                    lines
-                })
-            })?
-            .into_iter()
-            .filter(|line| {
-                let tokens: Vec<&str> = line.splitn(2, '=').collect();
-                // note: if `line` doesn't have =, tokens[0] is the entire line.
-                tokens[0].trim() != "androidboot.vbmeta.device"
-            })
-            .flat_map(|line| (line + "\n").into_bytes())
-            .collect())
-    })
-}
-
 fn load_config(path: &Path) -> Result<VmPayloadConfig> {
     info!("loading config from {:?}...", path);
     let file = ioutil::wait_for_file(path, WAIT_TIMEOUT)?;
diff --git a/tests/hostside/Android.bp b/tests/hostside/Android.bp
index bc8a4a5..10bcbf4 100644
--- a/tests/hostside/Android.bp
+++ b/tests/hostside/Android.bp
@@ -16,5 +16,9 @@
         "VirtualizationTestHelper",
     ],
     per_testcase_directory: true,
-    data: [":MicrodroidTestApp"],
+    data: [
+        ":MicrodroidTestApp",
+        ":microdroid_general_sepolicy.conf",
+    ],
+    data_native_bins: ["sepolicy-analyze"],
 }
diff --git a/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java b/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java
index e15f1ae..e3f1968 100644
--- a/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java
+++ b/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java
@@ -149,9 +149,26 @@
     }
 
     public static CommandResult runOnMicrodroidForResult(String... cmd) {
-        final long timeout = 30000; // 30 sec. Microdroid is extremely slow on GCE-on-CF.
+        final long timeoutMs = 30000; // 30 sec. Microdroid is extremely slow on GCE-on-CF.
         return RunUtil.getDefault()
-                .runTimedCmd(timeout, "adb", "-s", MICRODROID_SERIAL, "shell", join(cmd));
+                .runTimedCmd(timeoutMs, "adb", "-s", MICRODROID_SERIAL, "shell", join(cmd));
+    }
+
+    public static void pullMicrodroidFile(String path, File target) {
+        final long timeoutMs = 30000; // 30 sec. Microdroid is extremely slow on GCE-on-CF.
+        CommandResult result =
+                RunUtil.getDefault()
+                        .runTimedCmd(
+                                timeoutMs,
+                                "adb",
+                                "-s",
+                                MICRODROID_SERIAL,
+                                "pull",
+                                path,
+                                target.getPath());
+        if (result.getStatus() != CommandStatus.SUCCESS) {
+            fail("pulling " + path + " has failed: " + result);
+        }
     }
 
     // Asserts the command will fail on Microdroid.
diff --git a/tests/hostside/java/android/virt/test/MicrodroidTestCase.java b/tests/hostside/java/android/virt/test/MicrodroidTestCase.java
index 6aa7566..6dacf23 100644
--- a/tests/hostside/java/android/virt/test/MicrodroidTestCase.java
+++ b/tests/hostside/java/android/virt/test/MicrodroidTestCase.java
@@ -17,18 +17,24 @@
 package android.virt.test;
 
 import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.CommandStatus;
+import com.android.tradefed.util.FileUtil;
+import com.android.tradefed.util.RunUtil;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.io.File;
 import java.util.Optional;
 
 @RunWith(DeviceJUnit4ClassRunner.class)
@@ -43,6 +49,16 @@
     private static final int NUM_VCPUS = 3;
     private static final String CPU_AFFINITY = "0,1,2";
 
+    // TODO(b/176805428): remove this
+    private boolean isCuttlefish() throws Exception {
+        String productName = getDevice().getProperty("ro.product.name");
+        return (null != productName)
+                && (productName.startsWith("aosp_cf_x86")
+                        || productName.startsWith("aosp_cf_arm")
+                        || productName.startsWith("cf_x86")
+                        || productName.startsWith("cf_arm"));
+    }
+
     private int minMemorySize() throws DeviceNotAvailableException {
         CommandRunner android = new CommandRunner(getDevice());
         String abi = android.run("getprop", "ro.product.cpu.abi");
@@ -103,6 +119,34 @@
         assertThat(runOnMicrodroid("cat /proc/cpuinfo | grep processor | wc -l"),
                 is(Integer.toString(NUM_VCPUS)));
 
+        // Check that selinux is enabled
+        assertThat(runOnMicrodroid("getenforce"), is("Enforcing"));
+
+        // TODO(b/176805428): adb is broken for nested VM
+        if (!isCuttlefish()) {
+            // Check neverallow rules on microdroid
+            File policyFile = FileUtil.createTempFile("microdroid_sepolicy", "");
+            pullMicrodroidFile("/sys/fs/selinux/policy", policyFile);
+
+            File generalPolicyConfFile = findTestFile("microdroid_general_sepolicy.conf");
+            File sepolicyAnalyzeBin = findTestFile("sepolicy-analyze");
+
+            CommandResult result =
+                    RunUtil.getDefault()
+                            .runTimedCmd(
+                                    10000,
+                                    sepolicyAnalyzeBin.getPath(),
+                                    policyFile.getPath(),
+                                    "neverallow",
+                                    "-w",
+                                    "-f",
+                                    generalPolicyConfFile.getPath());
+            assertEquals(
+                    "neverallow check failed: " + result.getStderr().trim(),
+                    result.getStatus(),
+                    CommandStatus.SUCCESS);
+        }
+
         shutdownMicrodroid(getDevice(), cid);
     }
 
diff --git a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
index 0587299..63fdca1 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -38,6 +38,7 @@
 import android.system.virtualmachine.VirtualMachineException;
 import android.system.virtualmachine.VirtualMachineManager;
 
+import androidx.annotation.CallSuper;
 import androidx.test.core.app.ApplicationProvider;
 
 import com.android.microdroid.testservice.ITestService;
@@ -114,13 +115,7 @@
         }
 
         void forceStop(VirtualMachine vm) {
-            try {
-                vm.stop();
-                this.onDied(vm, VirtualMachineCallback.DEATH_REASON_KILLED);
-                mExecutorService.shutdown();
-            } catch (VirtualMachineException e) {
-                throw new RuntimeException(e);
-            }
+            this.onDied(vm, VirtualMachineCallback.DEATH_REASON_KILLED);
         }
 
         @Override
@@ -136,7 +131,15 @@
         public void onError(VirtualMachine vm, int errorCode, String message) {}
 
         @Override
-        public void onDied(VirtualMachine vm, @DeathReason int reason) {}
+        @CallSuper
+        public void onDied(VirtualMachine vm, @DeathReason int reason) {
+            try {
+                vm.stop();
+                mExecutorService.shutdown();
+            } catch (VirtualMachineException e) {
+                throw new RuntimeException(e);
+            }
+        }
     }
 
     private static final int MIN_MEM_ARM64 = 135;
@@ -214,6 +217,7 @@
                     public void onDied(VirtualMachine vm, @DeathReason int reason) {
                         assertTrue(mPayloadReadyCalled);
                         assertTrue(mPayloadStartedCalled);
+                        super.onDied(vm, reason);
                     }
                 };
         listener.runToFinish(mInner.mVm);
@@ -277,6 +281,7 @@
                     public void onDied(VirtualMachine vm, @DeathReason int reason) {
                         assertFalse(mPayloadStarted);
                         assertTrue(mErrorOccurred);
+                        super.onDied(vm, reason);
                     }
                 };
         listener.runToFinish(mInner.mVm);
@@ -378,6 +383,7 @@
                     @Override
                     public void onDied(VirtualMachine vm, @DeathReason int reason) {
                         assertTrue(mPayloadReadyCalled);
+                        super.onDied(vm, reason);
                     }
                 };
         listener.runToFinish(mInner.mVm);
@@ -420,6 +426,7 @@
                     public void onDied(VirtualMachine vm, @DeathReason int reason) {
                         assertFalse(mPayloadStarted);
                         assertTrue(mErrorOccurred);
+                        super.onDied(vm, reason);
                     }
                 };
         listener.runToFinish(mInner.mVm);
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
index 7e0c634..89c6e8a 100644
--- a/virtualizationservice/src/aidl.rs
+++ b/virtualizationservice/src/aidl.rs
@@ -29,7 +29,6 @@
     IVirtualizationService::IVirtualizationService,
     Partition::Partition,
     PartitionType::PartitionType,
-    VirtualMachineAppConfig::DebugLevel::DebugLevel,
     VirtualMachineAppConfig::VirtualMachineAppConfig,
     VirtualMachineConfig::VirtualMachineConfig,
     VirtualMachineDebugInfo::VirtualMachineDebugInfo,
@@ -131,8 +130,8 @@
     ) -> binder::Result<Strong<dyn IVirtualMachine>> {
         check_manage_access()?;
         let state = &mut *self.state.lock().unwrap();
-        let mut console_fd = console_fd.map(clone_file).transpose()?;
-        let mut log_fd = log_fd.map(clone_file).transpose()?;
+        let console_fd = console_fd.map(clone_file).transpose()?;
+        let log_fd = log_fd.map(clone_file).transpose()?;
         let requester_uid = ThreadState::get_calling_uid();
         let requester_sid = get_calling_sid()?;
         let requester_debug_pid = ThreadState::get_calling_pid();
@@ -163,27 +162,7 @@
             )
         })?;
 
-        // Disable console logging if debug level != full. Note that kernel anyway doesn't use the
-        // console output when debug level != full. So, users won't be able to see the kernel
-        // output even without this overriding. This is to silence output from the bootloader which
-        // doesn't understand the bootconfig parameters.
-        if let VirtualMachineConfig::AppConfig(config) = config {
-            if config.debugLevel != DebugLevel::FULL {
-                console_fd = None;
-            }
-            if config.debugLevel == DebugLevel::NONE {
-                log_fd = None;
-            }
-        }
-
         let is_app_config = matches!(config, VirtualMachineConfig::AppConfig(_));
-        let is_debug_level_full = matches!(
-            config,
-            VirtualMachineConfig::AppConfig(VirtualMachineAppConfig {
-                debugLevel: DebugLevel::FULL,
-                ..
-            })
-        );
 
         let config = match config {
             VirtualMachineConfig::AppConfig(config) => BorrowedOrOwned::Owned(
@@ -201,14 +180,6 @@
         let config = config.as_ref();
         let protected = config.protectedVm;
 
-        // Debug level FULL is only supported for non-protected VMs.
-        if is_debug_level_full && protected {
-            return Err(new_binder_exception(
-                ExceptionCode::SERVICE_SPECIFIC,
-                "FULL debug level not supported for protected VMs.",
-            ));
-        };
-
         // Check if partition images are labeled incorrectly. This is to prevent random images
         // which are not protected by the Android Verified Boot (e.g. bits downloaded by apps) from
         // being loaded in a pVM.  Specifically, for images in the raw config, nothing is allowed