Merge "Fixes to API documentation" into main
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 1410534..77ccc1d 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -105,6 +105,9 @@
"path": "packages/modules/Virtualization/rialto"
},
{
+ "path": "packages/modules/Virtualization/service_vm/comm"
+ },
+ {
"path": "packages/modules/Virtualization/service_vm/requests"
},
{
diff --git a/microdroid/init.rc b/microdroid/init.rc
index f5f3f15..4cc0475 100644
--- a/microdroid/init.rc
+++ b/microdroid/init.rc
@@ -30,6 +30,11 @@
# We don't directly exec the binary to specify stdio_to_kmsg.
exec_start init_debug_policy
+ # Wait for ueventd to have finished cold boot.
+ # This is needed by prng-seeder (at least).
+ # (In Android this happens inside apexd-bootstrap.)
+ wait_for_prop ro.cold_boot_done true
+
on init
mkdir /mnt/apk 0755 root root
mkdir /mnt/extra-apk 0755 root root
diff --git a/microdroid_manager/Android.bp b/microdroid_manager/Android.bp
index 8710e54..93f49ef 100644
--- a/microdroid_manager/Android.bp
+++ b/microdroid_manager/Android.bp
@@ -46,10 +46,12 @@
"libserde",
"libserde_cbor",
"libserde_json",
+ "libservice_vm_comm",
"libthiserror",
"libuuid",
"libvsock",
"librand",
+ "libzeroize",
],
init_rc: ["microdroid_manager.rc"],
multilib: {
@@ -70,6 +72,7 @@
defaults: ["microdroid_manager_defaults"],
test_suites: ["general-tests"],
rustlibs: [
+ "libhwtrust",
"libtempfile",
],
multilib: {
diff --git a/microdroid_manager/aidl/Android.bp b/microdroid_manager/aidl/Android.bp
index 0aa8662..353e9cc 100644
--- a/microdroid_manager/aidl/Android.bp
+++ b/microdroid_manager/aidl/Android.bp
@@ -5,8 +5,12 @@
aidl_interface {
name: "android.system.virtualization.payload",
srcs: ["android/system/virtualization/payload/*.aidl"],
+ imports: ["android.system.virtualizationcommon"],
unstable: true,
backend: {
+ java: {
+ enabled: false,
+ },
rust: {
enabled: true,
apex_available: [
diff --git a/microdroid_manager/aidl/android/system/virtualization/payload/IVmPayloadService.aidl b/microdroid_manager/aidl/android/system/virtualization/payload/IVmPayloadService.aidl
index b9a7a64..51796f1 100644
--- a/microdroid_manager/aidl/android/system/virtualization/payload/IVmPayloadService.aidl
+++ b/microdroid_manager/aidl/android/system/virtualization/payload/IVmPayloadService.aidl
@@ -16,11 +16,17 @@
package android.system.virtualization.payload;
+import android.system.virtualizationcommon.Certificate;
+
/**
* This interface regroups the tasks that payloads delegate to
* Microdroid Manager for execution.
*/
interface IVmPayloadService {
+ /** The constants STATUS_* are status code returned by this service. */
+ /** Failed to prepare the CSR and key pair for attestation. */
+ const int STATUS_FAILED_TO_PREPARE_CSR_AND_KEY = 1;
+
/** Socket name of the service IVmPayloadService. */
const String VM_PAYLOAD_SERVICE_SOCKET_NAME = "vm_payload_service";
@@ -33,6 +39,32 @@
*/
const String ENCRYPTEDSTORE_MOUNTPOINT = "/mnt/encryptedstore";
+ /**
+ * An {@link AttestationResult} holds an attested private key and the remotely
+ * provisioned certificate chain covering its corresponding public key.
+ */
+ parcelable AttestationResult {
+ /**
+ * DER-encoded ECPrivateKey structure specified in [RFC 5915 s3] for the
+ * EC P-256 private key, which is attested.
+ *
+ * The corresponding public key is included in the leaf certificate of
+ * the certificate chain.
+ *
+ * [RFC 5915 s3]: https://datatracker.ietf.org/doc/html/rfc5915#section-3
+ */
+ byte[] privateKey;
+
+ /**
+ * Sequence of DER-encoded X.509 certificates that make up the attestation
+ * key's certificate chain.
+ *
+ * The certificate chain starts with a root certificate and ends with a leaf
+ * certificate covering the attested public key.
+ */
+ Certificate[] certificateChain;
+ }
+
/** Notifies that the payload is ready to serve. */
void notifyPayloadReady();
@@ -75,7 +107,9 @@
* serving as proof of the freshness of the result.
*
* @param challenge the maximum supported challenge size is 64 bytes.
- * @return the X.509 encoded certificate.
+ *
+ * @return An {@link AttestationResult} parcelable containing an attested key pair and its
+ * certification chain.
*/
- byte[] requestAttestation(in byte[] challenge);
+ AttestationResult requestAttestation(in byte[] challenge);
}
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index 7ba54f8..1b41e58 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -34,7 +34,7 @@
use crate::dice::dice_derivation;
use crate::dice_driver::DiceDriver;
-use crate::instance::{ApexData, InstanceDisk, MicrodroidData};
+use crate::instance::{InstanceDisk, MicrodroidData};
use crate::verify::verify_payload;
use crate::vm_payload_service::register_vm_payload_service;
use anyhow::{anyhow, bail, ensure, Context, Error, Result};
@@ -42,10 +42,10 @@
use keystore2_crypto::ZVec;
use libc::VMADDR_CID_HOST;
use log::{error, info};
-use microdroid_metadata::{write_metadata, PayloadMetadata};
+use microdroid_metadata::PayloadMetadata;
use microdroid_payload_config::{OsConfig, Task, TaskType, VmPayloadConfig};
use nix::sys::signal::Signal;
-use payload::{load_metadata, to_metadata};
+use payload::load_metadata;
use rpcbinder::RpcSession;
use rustutils::sockets::android_get_control_socket;
use rustutils::system_properties;
@@ -143,15 +143,6 @@
Ok(())
}
-fn get_vms_rpc_binder() -> Result<Strong<dyn IVirtualMachineService>> {
- // The host is running a VirtualMachineService for this VM on a port equal
- // to the CID of this VM.
- let port = vsock::get_local_cid().context("Could not determine local CID")?;
- RpcSession::new()
- .setup_vsock_client(VMADDR_CID_HOST, port)
- .context("Could not connect to IVirtualMachineService")
-}
-
fn main() -> Result<()> {
// If debuggable, print full backtrace to console log with stdio_to_kmsg
if is_debuggable()? {
@@ -174,25 +165,6 @@
})
}
-/// Prepares a socket file descriptor for the vm payload service.
-///
-/// # Safety
-///
-/// The caller must ensure that this function is the only place that claims ownership
-/// of the file descriptor and it is called only once.
-unsafe fn prepare_vm_payload_service_socket() -> Result<OwnedFd> {
- let raw_fd = android_get_control_socket(VM_PAYLOAD_SERVICE_SOCKET_NAME)?;
-
- // Creating OwnedFd for stdio FDs is not safe.
- if [libc::STDIN_FILENO, libc::STDOUT_FILENO, libc::STDERR_FILENO].contains(&raw_fd) {
- bail!("File descriptor {raw_fd} is standard I/O descriptor");
- }
- // SAFETY: Initializing OwnedFd for a RawFd created by the init.
- // We checked that the integer value corresponds to a valid FD and that the caller
- // ensures that this is the only place to claim its ownership.
- Ok(unsafe { OwnedFd::from_raw_fd(raw_fd) })
-}
-
fn try_main() -> Result<()> {
android_logger::init_once(
android_logger::Config::default()
@@ -245,71 +217,6 @@
}
}
-fn post_payload_work() -> Result<()> {
- // Sync the encrypted storage filesystem (flushes the filesystem caches).
- if Path::new(ENCRYPTEDSTORE_BACKING_DEVICE).exists() {
- let mountpoint = CString::new(ENCRYPTEDSTORE_MOUNTPOINT).unwrap();
-
- // SAFETY: `mountpoint` is a valid C string. `syncfs` and `close` are safe for any parameter
- // values.
- let ret = unsafe {
- let dirfd = libc::open(
- mountpoint.as_ptr(),
- libc::O_DIRECTORY | libc::O_RDONLY | libc::O_CLOEXEC,
- );
- ensure!(dirfd >= 0, "Unable to open {:?}", mountpoint);
- let ret = libc::syncfs(dirfd);
- libc::close(dirfd);
- ret
- };
- if ret != 0 {
- error!("failed to sync encrypted storage.");
- return Err(anyhow!(std::io::Error::last_os_error()));
- }
- }
- Ok(())
-}
-
-fn is_strict_boot() -> bool {
- Path::new(AVF_STRICT_BOOT).exists()
-}
-
-fn is_new_instance() -> bool {
- Path::new(AVF_NEW_INSTANCE).exists()
-}
-
-fn is_verified_boot() -> bool {
- !Path::new(DEBUG_MICRODROID_NO_VERIFIED_BOOT).exists()
-}
-
-fn is_debuggable() -> Result<bool> {
- Ok(system_properties::read_bool(DEBUGGABLE_PROP, true)?)
-}
-
-fn should_export_tombstones(config: &VmPayloadConfig) -> bool {
- match config.export_tombstones {
- Some(b) => b,
- None => is_debuggable().unwrap_or(false),
- }
-}
-
-/// Get debug policy value in bool. It's true iff the value is explicitly set to <1>.
-fn get_debug_policy_bool(path: &'static str) -> Result<Option<bool>> {
- let mut file = match File::open(path) {
- Ok(dp) => dp,
- Err(e) => {
- info!(
- "Assumes that debug policy is disabled because failed to read debug policy ({e:?})"
- );
- return Ok(Some(false));
- }
- };
- let mut log: [u8; 4] = Default::default();
- file.read_exact(&mut log).context("Malformed data in {path}")?;
- // DT spec uses big endian although Android is always little endian.
- Ok(Some(u32::from_be_bytes(log) == 1))
-}
-
fn try_run_payload(
service: &Strong<dyn IVirtualMachineService>,
vm_payload_service_fd: OwnedFd,
@@ -377,6 +284,13 @@
let dice_artifacts = dice_derivation(dice, &verified_data, &payload_metadata)?;
let vm_secret = VmSecret::new(dice_artifacts).context("Failed to create VM secrets")?;
+ if cfg!(dice_changes) {
+ // Now that the DICE derivation is done, it's ok to allow payload code to run.
+
+ // Start apexd to activate APEXes. This may allow code within them to run.
+ system_properties::write("ctl.start", "apexd-vm")?;
+ }
+
// Run encryptedstore binary to prepare the storage
let encryptedstore_child = if Path::new(ENCRYPTEDSTORE_BACKING_DEVICE).exists() {
info!("Preparing encryptedstore ...");
@@ -419,10 +333,12 @@
);
mount_extra_apks(&config, &mut zipfuse)?;
- // Wait until apex config is done. (e.g. linker configuration for apexes)
- wait_for_apex_config_done()?;
-
- setup_config_sysprops(&config)?;
+ register_vm_payload_service(
+ allow_restricted_apis,
+ service.clone(),
+ vm_secret,
+ vm_payload_service_fd,
+ )?;
// Set export_tombstones if enabled
if should_export_tombstones(&config) {
@@ -431,16 +347,20 @@
.context("set microdroid_manager.export_tombstones.enabled")?;
}
+ // Wait until apex config is done. (e.g. linker configuration for apexes)
+ wait_for_property_true(APEX_CONFIG_DONE_PROP).context("Failed waiting for apex config done")?;
+
+ // Trigger init post-fs-data. This will start authfs if we wask it to.
+ if config.enable_authfs {
+ system_properties::write("microdroid_manager.authfs.enabled", "1")
+ .context("failed to write microdroid_manager.authfs.enabled")?;
+ }
+ system_properties::write("microdroid_manager.config_done", "1")
+ .context("failed to write microdroid_manager.config_done")?;
+
// Wait until zipfuse has mounted the APKs so we can access the payload
zipfuse.wait_until_done()?;
- register_vm_payload_service(
- allow_restricted_apis,
- service.clone(),
- vm_secret,
- vm_payload_service_fd,
- )?;
-
// Wait for encryptedstore to finish mounting the storage (if enabled) before setting
// microdroid_manager.init_done. Reason is init stops uneventd after that.
// Encryptedstore, however requires ueventd
@@ -449,7 +369,10 @@
ensure!(exitcode.success(), "Unable to prepare encrypted storage. Exitcode={}", exitcode);
}
+ // Wait for init to have finished booting.
wait_for_property_true("dev.bootcomplete").context("failed waiting for dev.bootcomplete")?;
+
+ // And then tell it we're done so unnecessary services can be shut down.
system_properties::write("microdroid_manager.init_done", "1")
.context("set microdroid_manager.init_done")?;
@@ -457,6 +380,120 @@
exec_task(task, service).context("Failed to run payload")
}
+fn post_payload_work() -> Result<()> {
+ // Sync the encrypted storage filesystem (flushes the filesystem caches).
+ if Path::new(ENCRYPTEDSTORE_BACKING_DEVICE).exists() {
+ let mountpoint = CString::new(ENCRYPTEDSTORE_MOUNTPOINT).unwrap();
+
+ // SAFETY: `mountpoint` is a valid C string. `syncfs` and `close` are safe for any parameter
+ // values.
+ let ret = unsafe {
+ let dirfd = libc::open(
+ mountpoint.as_ptr(),
+ libc::O_DIRECTORY | libc::O_RDONLY | libc::O_CLOEXEC,
+ );
+ ensure!(dirfd >= 0, "Unable to open {:?}", mountpoint);
+ let ret = libc::syncfs(dirfd);
+ libc::close(dirfd);
+ ret
+ };
+ if ret != 0 {
+ error!("failed to sync encrypted storage.");
+ return Err(anyhow!(std::io::Error::last_os_error()));
+ }
+ }
+ Ok(())
+}
+
+fn mount_extra_apks(config: &VmPayloadConfig, zipfuse: &mut Zipfuse) -> Result<()> {
+ // For now, only the number of apks is important, as the mount point and dm-verity name is fixed
+ for i in 0..config.extra_apks.len() {
+ let mount_dir = format!("/mnt/extra-apk/{i}");
+ create_dir(Path::new(&mount_dir)).context("Failed to create mount dir for extra apks")?;
+
+ let mount_for_exec =
+ if cfg!(multi_tenant) { MountForExec::Allowed } else { MountForExec::Disallowed };
+ // These run asynchronously in parallel - we wait later for them to complete.
+ zipfuse.mount(
+ mount_for_exec,
+ "fscontext=u:object_r:zipfusefs:s0,context=u:object_r:extra_apk_file:s0",
+ Path::new(&format!("/dev/block/mapper/extra-apk-{i}")),
+ Path::new(&mount_dir),
+ format!("microdroid_manager.extra_apk.mounted.{i}"),
+ )?;
+ }
+
+ Ok(())
+}
+
+fn get_vms_rpc_binder() -> Result<Strong<dyn IVirtualMachineService>> {
+ // The host is running a VirtualMachineService for this VM on a port equal
+ // to the CID of this VM.
+ let port = vsock::get_local_cid().context("Could not determine local CID")?;
+ RpcSession::new()
+ .setup_vsock_client(VMADDR_CID_HOST, port)
+ .context("Could not connect to IVirtualMachineService")
+}
+
+/// Prepares a socket file descriptor for the vm payload service.
+///
+/// # Safety
+///
+/// The caller must ensure that this function is the only place that claims ownership
+/// of the file descriptor and it is called only once.
+unsafe fn prepare_vm_payload_service_socket() -> Result<OwnedFd> {
+ let raw_fd = android_get_control_socket(VM_PAYLOAD_SERVICE_SOCKET_NAME)?;
+
+ // Creating OwnedFd for stdio FDs is not safe.
+ if [libc::STDIN_FILENO, libc::STDOUT_FILENO, libc::STDERR_FILENO].contains(&raw_fd) {
+ bail!("File descriptor {raw_fd} is standard I/O descriptor");
+ }
+ // SAFETY: Initializing OwnedFd for a RawFd created by the init.
+ // We checked that the integer value corresponds to a valid FD and that the caller
+ // ensures that this is the only place to claim its ownership.
+ Ok(unsafe { OwnedFd::from_raw_fd(raw_fd) })
+}
+
+fn is_strict_boot() -> bool {
+ Path::new(AVF_STRICT_BOOT).exists()
+}
+
+fn is_new_instance() -> bool {
+ Path::new(AVF_NEW_INSTANCE).exists()
+}
+
+fn is_verified_boot() -> bool {
+ !Path::new(DEBUG_MICRODROID_NO_VERIFIED_BOOT).exists()
+}
+
+fn is_debuggable() -> Result<bool> {
+ Ok(system_properties::read_bool(DEBUGGABLE_PROP, true)?)
+}
+
+fn should_export_tombstones(config: &VmPayloadConfig) -> bool {
+ match config.export_tombstones {
+ Some(b) => b,
+ None => is_debuggable().unwrap_or(false),
+ }
+}
+
+/// Get debug policy value in bool. It's true iff the value is explicitly set to <1>.
+fn get_debug_policy_bool(path: &'static str) -> Result<Option<bool>> {
+ let mut file = match File::open(path) {
+ Ok(dp) => dp,
+ Err(e) => {
+ info!(
+ "Assumes that debug policy is disabled because failed to read debug policy ({e:?})"
+ );
+ return Ok(Some(false));
+ }
+ };
+ let mut log: [u8; 4] = Default::default();
+ file.read_exact(&mut log).context("Malformed data in {path}")?;
+ // DT spec uses big endian although Android is always little endian.
+ Ok(Some(u32::from_be_bytes(log) == 1))
+}
+
enum MountForExec {
Allowed,
Disallowed,
@@ -504,65 +541,6 @@
}
}
-fn write_apex_payload_data(
- saved_data: Option<&MicrodroidData>,
- apex_data_from_payload: &[ApexData],
-) -> Result<()> {
- if let Some(saved_apex_data) = saved_data.map(|d| &d.apex_data) {
- // We don't support APEX updates. (assuming that update will change root digest)
- ensure!(
- saved_apex_data == apex_data_from_payload,
- MicrodroidError::PayloadChanged(String::from("APEXes have changed."))
- );
- let apex_metadata = to_metadata(apex_data_from_payload);
- // Pass metadata(with public keys and root digests) to apexd so that it uses the passed
- // metadata instead of the default one (/dev/block/by-name/payload-metadata)
- OpenOptions::new()
- .create_new(true)
- .write(true)
- .open("/apex/vm-payload-metadata")
- .context("Failed to open /apex/vm-payload-metadata")
- .and_then(|f| write_metadata(&apex_metadata, f))?;
- }
- Ok(())
-}
-
-fn mount_extra_apks(config: &VmPayloadConfig, zipfuse: &mut Zipfuse) -> Result<()> {
- // For now, only the number of apks is important, as the mount point and dm-verity name is fixed
- for i in 0..config.extra_apks.len() {
- let mount_dir = format!("/mnt/extra-apk/{i}");
- create_dir(Path::new(&mount_dir)).context("Failed to create mount dir for extra apks")?;
-
- let mount_for_exec =
- if cfg!(multi_tenant) { MountForExec::Allowed } else { MountForExec::Disallowed };
- // These run asynchronously in parallel - we wait later for them to complete.
- zipfuse.mount(
- mount_for_exec,
- "fscontext=u:object_r:zipfusefs:s0,context=u:object_r:extra_apk_file:s0",
- Path::new(&format!("/dev/block/mapper/extra-apk-{i}")),
- Path::new(&mount_dir),
- format!("microdroid_manager.extra_apk.mounted.{i}"),
- )?;
- }
-
- Ok(())
-}
-
-fn setup_config_sysprops(config: &VmPayloadConfig) -> Result<()> {
- if config.enable_authfs {
- system_properties::write("microdroid_manager.authfs.enabled", "1")
- .context("failed to write microdroid_manager.authfs.enabled")?;
- }
- system_properties::write("microdroid_manager.config_done", "1")
- .context("failed to write microdroid_manager.config_done")?;
- Ok(())
-}
-
-// Waits until linker config is generated
-fn wait_for_apex_config_done() -> Result<()> {
- wait_for_property_true(APEX_CONFIG_DONE_PROP).context("Failed waiting for apex config done")
-}
-
fn wait_for_property_true(property_name: &str) -> Result<()> {
let mut prop = PropertyWatcher::new(property_name)?;
loop {
diff --git a/microdroid_manager/src/verify.rs b/microdroid_manager/src/verify.rs
index 22f3414..e63530b 100644
--- a/microdroid_manager/src/verify.rs
+++ b/microdroid_manager/src/verify.rs
@@ -12,18 +12,19 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-use crate::instance::{ApkData, MicrodroidData, RootHash};
-use crate::payload::get_apex_data_from_payload;
-use crate::{is_strict_boot, is_verified_boot, write_apex_payload_data, MicrodroidError};
+use crate::instance::{ApexData, ApkData, MicrodroidData, RootHash};
+use crate::payload::{get_apex_data_from_payload, to_metadata};
+use crate::{is_strict_boot, is_verified_boot, MicrodroidError};
use anyhow::{anyhow, ensure, Context, Result};
use apkmanifest::get_manifest_info;
use apkverify::{get_public_key_der, verify, V4Signature};
use glob::glob;
use itertools::sorted;
use log::{info, warn};
-use microdroid_metadata::Metadata;
+use microdroid_metadata::{write_metadata, Metadata};
use rand::Fill;
use rustutils::system_properties;
+use std::fs::OpenOptions;
use std::path::Path;
use std::process::{Child, Command};
use std::str;
@@ -134,8 +135,10 @@
write_apex_payload_data(saved_data, &apex_data_from_payload)?;
}
- // Start apexd to activate APEXes
- system_properties::write("ctl.start", "apexd-vm")?;
+ if cfg!(not(dice_changes)) {
+ // Start apexd to activate APEXes
+ system_properties::write("ctl.start", "apexd-vm")?;
+ }
// TODO(inseob): add timeout
apkdmverity_child.wait()?;
@@ -207,6 +210,29 @@
})
}
+fn write_apex_payload_data(
+ saved_data: Option<&MicrodroidData>,
+ apex_data_from_payload: &[ApexData],
+) -> Result<()> {
+ if let Some(saved_apex_data) = saved_data.map(|d| &d.apex_data) {
+ // We don't support APEX updates. (assuming that update will change root digest)
+ ensure!(
+ saved_apex_data == apex_data_from_payload,
+ MicrodroidError::PayloadChanged(String::from("APEXes have changed."))
+ );
+ let apex_metadata = to_metadata(apex_data_from_payload);
+ // Pass metadata(with public keys and root digests) to apexd so that it uses the passed
+ // metadata instead of the default one (/dev/block/by-name/payload-metadata)
+ OpenOptions::new()
+ .create_new(true)
+ .write(true)
+ .open("/apex/vm-payload-metadata")
+ .context("Failed to open /apex/vm-payload-metadata")
+ .and_then(|f| write_metadata(&apex_metadata, f))?;
+ }
+ Ok(())
+}
+
fn get_apk_root_hash_from_idsig<P: AsRef<Path>>(idsig_path: P) -> Result<Box<RootHash>> {
Ok(V4Signature::from_idsig_path(idsig_path)?.hashing_info.raw_root_hash)
}
diff --git a/microdroid_manager/src/vm_payload_service.rs b/microdroid_manager/src/vm_payload_service.rs
index 5b5fb9e..0661314 100644
--- a/microdroid_manager/src/vm_payload_service.rs
+++ b/microdroid_manager/src/vm_payload_service.rs
@@ -15,16 +15,38 @@
//! Implementation of the AIDL interface `IVmPayloadService`.
use android_system_virtualization_payload::aidl::android::system::virtualization::payload::IVmPayloadService::{
- BnVmPayloadService, IVmPayloadService, VM_PAYLOAD_SERVICE_SOCKET_NAME};
+ BnVmPayloadService, IVmPayloadService, VM_PAYLOAD_SERVICE_SOCKET_NAME, AttestationResult::AttestationResult,
+ STATUS_FAILED_TO_PREPARE_CSR_AND_KEY
+};
use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::IVirtualMachineService::IVirtualMachineService;
use anyhow::{anyhow, Context, Result};
use avflog::LogResult;
-use binder::{Interface, BinderFeatures, ExceptionCode, Strong, IntoBinderResult};
-use diced_open_dice::DiceArtifacts;
+use binder::{Interface, BinderFeatures, ExceptionCode, Strong, IntoBinderResult, Status};
+use diced_open_dice::{DiceArtifacts, derive_cdi_leaf_priv, PrivateKey, sign};
use log::info;
use rpcbinder::RpcServer;
+
+use crate::vm_secret::VmSecret;
+use coset::{
+ iana, CborSerializable, CoseKey, CoseKeyBuilder, CoseSign, CoseSignBuilder, CoseSignature,
+ CoseSignatureBuilder, HeaderBuilder,
+};
+use openssl::{
+ bn::{BigNum, BigNumContext},
+ ec::{EcGroup, EcKey, EcKeyRef},
+ ecdsa::EcdsaSig,
+ nid::Nid,
+ pkey::Private,
+ sha::sha256,
+};
+use service_vm_comm::{Csr, CsrPayload};
use std::os::unix::io::OwnedFd;
-use crate::vm_secret::{VmSecret};
+use zeroize::Zeroizing;
+
+const ATTESTATION_KEY_NID: Nid = Nid::X9_62_PRIME256V1; // NIST P-256 curve
+const ATTESTATION_KEY_ALGO: iana::Algorithm = iana::Algorithm::ES256;
+const ATTESTATION_KEY_CURVE: iana::EllipticCurve = iana::EllipticCurve::P_256;
+const ATTESTATION_KEY_AFFINE_COORDINATE_SIZE: i32 = 32;
/// Implementation of `IVmPayloadService`.
struct VmPayloadService {
@@ -66,12 +88,111 @@
Ok(self.secret.dice().cdi_attest().to_vec())
}
- fn requestAttestation(&self, challenge: &[u8]) -> binder::Result<Vec<u8>> {
+ fn requestAttestation(&self, challenge: &[u8]) -> binder::Result<AttestationResult> {
self.check_restricted_apis_allowed()?;
- self.virtual_machine_service.requestAttestation(challenge)
+ let (private_key, csr) = generate_attestation_key_and_csr(challenge, self.secret.dice())
+ .map_err(|e| {
+ Status::new_service_specific_error_str(
+ STATUS_FAILED_TO_PREPARE_CSR_AND_KEY,
+ Some(format!("Failed to prepare the CSR and key pair: {e:?}")),
+ )
+ })
+ .with_log()?;
+ let cert_chain = self.virtual_machine_service.requestAttestation(&csr)?;
+ Ok(AttestationResult {
+ privateKey: private_key.as_slice().to_vec(),
+ certificateChain: cert_chain,
+ })
}
}
+fn generate_attestation_key_and_csr(
+ challenge: &[u8],
+ dice_artifacts: &dyn DiceArtifacts,
+) -> Result<(Zeroizing<Vec<u8>>, Vec<u8>)> {
+ let group = EcGroup::from_curve_name(ATTESTATION_KEY_NID)?;
+ let attestation_key = EcKey::generate(&group)?;
+ let csr = build_csr(challenge, attestation_key.as_ref(), dice_artifacts)?;
+
+ let csr = csr.into_cbor_vec().context("Failed to serialize CSR")?;
+ let private_key = attestation_key.private_key_to_der()?;
+ Ok((Zeroizing::new(private_key), csr))
+}
+
+fn build_csr(
+ challenge: &[u8],
+ attestation_key: &EcKeyRef<Private>,
+ dice_artifacts: &dyn DiceArtifacts,
+) -> Result<Csr> {
+ // Builds CSR Payload to be signed.
+ let public_key =
+ to_cose_public_key(attestation_key)?.to_vec().context("Failed to serialize public key")?;
+ let csr_payload = CsrPayload { public_key, challenge: challenge.to_vec() };
+ let csr_payload = csr_payload.into_cbor_vec()?;
+
+ // Builds signed CSR Payload.
+ let cdi_leaf_priv = derive_cdi_leaf_priv(dice_artifacts)?;
+ let signed_csr_payload = build_signed_data(csr_payload, &cdi_leaf_priv, attestation_key)?
+ .to_vec()
+ .context("Failed to serialize signed CSR payload")?;
+
+ // Builds CSR.
+ let dice_cert_chain = dice_artifacts.bcc().ok_or(anyhow!("bcc is none"))?.to_vec();
+ Ok(Csr { dice_cert_chain, signed_csr_payload })
+}
+
+fn build_signed_data(
+ payload: Vec<u8>,
+ cdi_leaf_priv: &PrivateKey,
+ attestation_key: &EcKeyRef<Private>,
+) -> Result<CoseSign> {
+ let cdi_leaf_sig_headers = build_signature_headers(iana::Algorithm::EdDSA);
+ let attestation_key_sig_headers = build_signature_headers(ATTESTATION_KEY_ALGO);
+ let aad = &[];
+ let signed_data = CoseSignBuilder::new()
+ .payload(payload)
+ .try_add_created_signature(cdi_leaf_sig_headers, aad, |message| {
+ sign(message, cdi_leaf_priv.as_array()).map(|v| v.to_vec())
+ })?
+ .try_add_created_signature(attestation_key_sig_headers, aad, |message| {
+ ecdsa_sign(message, attestation_key)
+ })?
+ .build();
+ Ok(signed_data)
+}
+
+/// Builds a signature with headers filled with the provided algorithm.
+/// The signature data will be filled later when building the signed data.
+fn build_signature_headers(alg: iana::Algorithm) -> CoseSignature {
+ let protected = HeaderBuilder::new().algorithm(alg).build();
+ CoseSignatureBuilder::new().protected(protected).build()
+}
+
+fn ecdsa_sign(message: &[u8], key: &EcKeyRef<Private>) -> Result<Vec<u8>> {
+ let digest = sha256(message);
+ // Passes the digest to `ECDSA_do_sign` as recommended in the spec:
+ // https://commondatastorage.googleapis.com/chromium-boringssl-docs/ecdsa.h.html#ECDSA_do_sign
+ let sig = EcdsaSig::sign::<Private>(&digest, key)?;
+ Ok(sig.to_der()?)
+}
+
+fn get_affine_coordinates(key: &EcKeyRef<Private>) -> Result<(Vec<u8>, Vec<u8>)> {
+ let mut ctx = BigNumContext::new()?;
+ let mut x = BigNum::new()?;
+ let mut y = BigNum::new()?;
+ key.public_key().affine_coordinates_gfp(key.group(), &mut x, &mut y, &mut ctx)?;
+ let x = x.to_vec_padded(ATTESTATION_KEY_AFFINE_COORDINATE_SIZE)?;
+ let y = y.to_vec_padded(ATTESTATION_KEY_AFFINE_COORDINATE_SIZE)?;
+ Ok((x, y))
+}
+
+fn to_cose_public_key(key: &EcKeyRef<Private>) -> Result<CoseKey> {
+ let (x, y) = get_affine_coordinates(key)?;
+ Ok(CoseKeyBuilder::new_ec2_pub_key(ATTESTATION_KEY_CURVE, x, y)
+ .algorithm(ATTESTATION_KEY_ALGO)
+ .build())
+}
+
impl Interface for VmPayloadService {}
impl VmPayloadService {
@@ -116,3 +237,106 @@
});
Ok(())
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use anyhow::bail;
+ use ciborium::Value;
+ use coset::{iana::EnumI64, Label};
+ use hwtrust::{dice, session::Session};
+ use openssl::pkey::Public;
+
+ /// The following data is generated randomly with urandom.
+ const CHALLENGE: [u8; 16] = [
+ 0xb3, 0x66, 0xfa, 0x72, 0x92, 0x32, 0x2c, 0xd4, 0x99, 0xcb, 0x00, 0x1f, 0x0e, 0xe0, 0xc7,
+ 0x41,
+ ];
+
+ #[test]
+ fn csr_and_private_key_have_correct_format() -> Result<()> {
+ let dice_artifacts = diced_sample_inputs::make_sample_bcc_and_cdis()?;
+
+ let (private_key, csr) = generate_attestation_key_and_csr(&CHALLENGE, &dice_artifacts)?;
+ let ec_private_key = EcKey::private_key_from_der(&private_key)?;
+ let csr = Csr::from_cbor_slice(&csr).unwrap();
+ let cose_sign = CoseSign::from_slice(&csr.signed_csr_payload).unwrap();
+ let aad = &[];
+
+ // Checks CSR payload.
+ let csr_payload =
+ cose_sign.payload.as_ref().and_then(|v| CsrPayload::from_cbor_slice(v).ok()).unwrap();
+ let public_key = to_cose_public_key(&ec_private_key)?.to_vec().unwrap();
+ let expected_csr_payload = CsrPayload { challenge: CHALLENGE.to_vec(), public_key };
+ assert_eq!(expected_csr_payload, csr_payload);
+
+ // Checks the first signature is signed with CDI_Leaf_Priv.
+ let session = Session::default();
+ let chain = dice::Chain::from_cbor(&session, &csr.dice_cert_chain)?;
+ let public_key = chain.leaf().subject_public_key();
+ cose_sign
+ .verify_signature(0, aad, |signature, message| public_key.verify(signature, message))?;
+
+ // Checks the second signature is signed with attestation key.
+ let attestation_public_key = CoseKey::from_slice(&csr_payload.public_key).unwrap();
+ let ec_public_key = to_ec_public_key(&attestation_public_key)?;
+ cose_sign.verify_signature(1, aad, |signature, message| {
+ ecdsa_verify(signature, message, &ec_public_key)
+ })?;
+
+ // Verifies that private key and the public key form a valid key pair.
+ let message = b"test message";
+ let signature = ecdsa_sign(message, &ec_private_key)?;
+ ecdsa_verify(&signature, message, &ec_public_key)?;
+
+ Ok(())
+ }
+
+ fn ecdsa_verify(
+ signature: &[u8],
+ message: &[u8],
+ ec_public_key: &EcKeyRef<Public>,
+ ) -> Result<()> {
+ let sig = EcdsaSig::from_der(signature)?;
+ let digest = sha256(message);
+ if sig.verify(&digest, ec_public_key)? {
+ Ok(())
+ } else {
+ bail!("Signature does not match")
+ }
+ }
+
+ fn to_ec_public_key(cose_key: &CoseKey) -> Result<EcKey<Public>> {
+ check_ec_key_params(cose_key)?;
+ let group = EcGroup::from_curve_name(ATTESTATION_KEY_NID)?;
+ let x = get_label_value_as_bignum(cose_key, Label::Int(iana::Ec2KeyParameter::X.to_i64()))?;
+ let y = get_label_value_as_bignum(cose_key, Label::Int(iana::Ec2KeyParameter::Y.to_i64()))?;
+ let key = EcKey::from_public_key_affine_coordinates(&group, &x, &y)?;
+ key.check_key()?;
+ Ok(key)
+ }
+
+ fn check_ec_key_params(cose_key: &CoseKey) -> Result<()> {
+ assert_eq!(coset::KeyType::Assigned(iana::KeyType::EC2), cose_key.kty);
+ assert_eq!(Some(coset::Algorithm::Assigned(ATTESTATION_KEY_ALGO)), cose_key.alg);
+ let crv = get_label_value(cose_key, Label::Int(iana::Ec2KeyParameter::Crv.to_i64()))?;
+ assert_eq!(&Value::from(ATTESTATION_KEY_CURVE.to_i64()), crv);
+ Ok(())
+ }
+
+ fn get_label_value_as_bignum(key: &CoseKey, label: Label) -> Result<BigNum> {
+ get_label_value(key, label)?
+ .as_bytes()
+ .map(|v| BigNum::from_slice(&v[..]).unwrap())
+ .ok_or_else(|| anyhow!("Value not a bstr."))
+ }
+
+ fn get_label_value(key: &CoseKey, label: Label) -> Result<&Value> {
+ Ok(&key
+ .params
+ .iter()
+ .find(|(k, _)| k == &label)
+ .ok_or_else(|| anyhow!("Label {:?} not found", label))?
+ .1)
+ }
+}
diff --git a/pvmfw/src/device_assignment.rs b/pvmfw/src/device_assignment.rs
index a2816c4..7eae09f 100644
--- a/pvmfw/src/device_assignment.rs
+++ b/pvmfw/src/device_assignment.rs
@@ -28,32 +28,18 @@
use core::mem;
use libfdt::{Fdt, FdtError, FdtNode};
-// TODO(b/308694211): Move this to the vmbase
-macro_rules! const_cstr {
- ($str:literal) => {{
- #[allow(unused_unsafe)] // In case the macro is used within an unsafe block.
- // SAFETY: Trailing null is guaranteed by concat!()
- unsafe {
- CStr::from_bytes_with_nul_unchecked(concat!($str, "\0").as_bytes())
- }
- }};
-}
-
// TODO(b/308694211): Use cstr! from vmbase instead.
macro_rules! cstr {
($str:literal) => {{
- CStr::from_bytes_with_nul(concat!($str, "\0").as_bytes()).unwrap()
+ const S: &str = concat!($str, "\0");
+ const C: &::core::ffi::CStr = match ::core::ffi::CStr::from_bytes_with_nul(S.as_bytes()) {
+ Ok(v) => v,
+ Err(_) => panic!("string contains interior NUL"),
+ };
+ C
}};
}
-const FILTERED_VM_DTBO_PROP: [&CStr; 3] = [
- const_cstr!("android,pvmfw,phy-reg"),
- const_cstr!("android,pvmfw,phy-iommu"),
- const_cstr!("android,pvmfw,phy-sid"),
-];
-
-const REG_PROP_NAME: &CStr = const_cstr!("reg");
-const INTERRUPTS_PROP_NAME: &CStr = const_cstr!("interrupts");
// TODO(b/277993056): Keep constants derived from platform.dts in one place.
const CELLS_PER_INTERRUPT: usize = 3; // from /intc node in platform.dts
@@ -102,10 +88,6 @@
pub struct VmDtbo(Fdt);
impl VmDtbo {
- const OVERLAY_NODE_NAME: &CStr = const_cstr!("__overlay__");
- const TARGET_PATH_PROP: &CStr = const_cstr!("target-path");
- const SYMBOLS_NODE_PATH: &CStr = const_cstr!("/__symbols__");
-
/// Wraps a mutable slice containing a VM DTBO.
///
/// Fails if the VM DTBO does not pass validation.
@@ -150,7 +132,7 @@
let fragment_node = node.supernode_at_depth(1)?;
let target_path = fragment_node
- .getprop_str(Self::TARGET_PATH_PROP)?
+ .getprop_str(cstr!("target-path"))?
.ok_or(DeviceAssignmentError::InvalidDtbo)?;
if target_path != cstr!("/") {
return Err(DeviceAssignmentError::UnsupportedOverlayTarget);
@@ -161,7 +143,7 @@
.filter(|&component| !component.is_empty())
.skip(1);
let overlay_node_name = components.next();
- if overlay_node_name != Some(Self::OVERLAY_NODE_NAME.to_bytes()) {
+ if overlay_node_name != Some(b"__overlay__") {
return Err(DeviceAssignmentError::InvalidDtbo);
}
let mut overlaid_path = Vec::with_capacity(dtbo_node_path_bytes.len());
@@ -206,7 +188,7 @@
// Validation: Validate if interrupts cell numbers are multiple of #interrupt-cells.
// We can't know how many interrupts would exist.
let interrupts_cells = node
- .getprop_cells(INTERRUPTS_PROP_NAME)?
+ .getprop_cells(cstr!("interrupts"))?
.ok_or(DeviceAssignmentError::InvalidInterrupts)?
.count();
if interrupts_cells % CELLS_PER_INTERRUPT != 0 {
@@ -214,7 +196,7 @@
}
// Once validated, keep the raw bytes so patch can be done with setprop()
- Ok(node.getprop(INTERRUPTS_PROP_NAME).unwrap().unwrap().into())
+ Ok(node.getprop(cstr!("interrupts")).unwrap().unwrap().into())
}
// TODO(b/277993056): Read and validate iommu
@@ -224,7 +206,7 @@
let Some(node) = fdt.node(&node_path)? else { return Ok(None) };
// TODO(b/277993056): Validate reg with HVC, and keep reg with FdtNode::reg()
- let reg = node.getprop(REG_PROP_NAME).unwrap().unwrap();
+ let reg = node.getprop(cstr!("reg")).unwrap().unwrap();
let interrupts = Self::parse_interrupts(&node)?;
@@ -238,8 +220,8 @@
fn patch(&self, fdt: &mut Fdt) -> Result<()> {
let mut dst = fdt.node_mut(&self.node_path)?.unwrap();
- dst.setprop(REG_PROP_NAME, &self.reg)?;
- dst.setprop(INTERRUPTS_PROP_NAME, &self.interrupts)?;
+ dst.setprop(cstr!("reg"), &self.reg)?;
+ dst.setprop(cstr!("interrupts"), &self.interrupts)?;
// TODO(b/277993056): Read and patch iommu
Ok(())
}
@@ -275,7 +257,7 @@
filtered_dtbo_paths.push(dtbo_node_path.into());
}
}
- filtered_dtbo_paths.push(VmDtbo::SYMBOLS_NODE_PATH.into());
+ filtered_dtbo_paths.push(CString::new("/__symbols__").unwrap());
if assigned_devices.is_empty() {
return Ok(None);
@@ -299,7 +281,12 @@
node.nop()?;
}
- // Filters unused properties in assigned device node
+ // Filters pvmfw-specific properties in assigned device node.
+ const FILTERED_VM_DTBO_PROP: [&CStr; 3] = [
+ cstr!("android,pvmfw,phy-reg"),
+ cstr!("android,pvmfw,phy-iommu"),
+ cstr!("android,pvmfw,phy-sid"),
+ ];
for assigned_device in &self.assigned_devices {
let mut node = vm_dtbo.node_mut(&assigned_device.dtbo_node_path).unwrap().unwrap();
for prop in FILTERED_VM_DTBO_PROP {
diff --git a/service_vm/comm/Android.bp b/service_vm/comm/Android.bp
index 3a18052..6e05587 100644
--- a/service_vm/comm/Android.bp
+++ b/service_vm/comm/Android.bp
@@ -43,3 +43,31 @@
"std",
],
}
+
+rust_defaults {
+ name: "libservice_vm_comm_test_defaults",
+ crate_name: "diced_open_dice_test",
+ srcs: ["tests/*.rs"],
+ test_suites: ["general-tests"],
+ prefer_rlib: true,
+ rustlibs: [
+ "libdiced_sample_inputs",
+ "libdiced_open_dice",
+ ],
+}
+
+rust_test {
+ name: "libservice_vm_comm.test",
+ defaults: ["libservice_vm_comm_test_defaults"],
+ rustlibs: [
+ "libservice_vm_comm",
+ ],
+}
+
+rust_test {
+ name: "libservice_vm_comm_nostd.test",
+ defaults: ["libservice_vm_comm_test_defaults"],
+ rustlibs: [
+ "libservice_vm_comm_nostd",
+ ],
+}
diff --git a/service_vm/comm/TEST_MAPPING b/service_vm/comm/TEST_MAPPING
new file mode 100644
index 0000000..e677ba2
--- /dev/null
+++ b/service_vm/comm/TEST_MAPPING
@@ -0,0 +1,12 @@
+// When adding or removing tests here, don't forget to amend _all_modules list in
+// wireless/android/busytown/ath_config/configs/prod/avf/tests.gcl
+{
+ "avf-presubmit" : [
+ {
+ "name" : "libservice_vm_comm.test"
+ },
+ {
+ "name" : "libservice_vm_comm_nostd.test"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/service_vm/comm/src/csr.rs b/service_vm/comm/src/csr.rs
new file mode 100644
index 0000000..5e1cbad
--- /dev/null
+++ b/service_vm/comm/src/csr.rs
@@ -0,0 +1,121 @@
+// Copyright 2023, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! This module contains the structs related to the CSR (Certificate Signing Request)
+//! sent from the client VM to the service VM for attestation.
+
+use alloc::vec;
+use alloc::vec::Vec;
+use ciborium::Value;
+use coset::{self, CborSerializable, CoseError};
+
+/// Represents a CSR sent from the client VM to the service VM for attestation.
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub struct Csr {
+ /// The DICE certificate chain of the client VM.
+ pub dice_cert_chain: Vec<u8>,
+
+ /// The signed CSR payload in COSE_Sign structure, which includes two signatures:
+ /// - one by CDI_Leaf_Priv of the client VM's DICE chain,
+ /// - another by the private key corresponding to the public key.
+ pub signed_csr_payload: Vec<u8>,
+}
+
+impl Csr {
+ /// Serializes this object to a CBOR-encoded vector.
+ pub fn into_cbor_vec(self) -> coset::Result<Vec<u8>> {
+ let value = Value::Array(vec![
+ Value::Bytes(self.dice_cert_chain),
+ Value::Bytes(self.signed_csr_payload),
+ ]);
+ value.to_vec()
+ }
+
+ /// Creates an object instance from the provided CBOR-encoded slice.
+ pub fn from_cbor_slice(data: &[u8]) -> coset::Result<Self> {
+ let value = Value::from_slice(data)?;
+ let Value::Array(mut arr) = value else {
+ return Err(CoseError::UnexpectedItem(cbor_value_type(&value), "array"));
+ };
+ if arr.len() != 2 {
+ return Err(CoseError::UnexpectedItem("array", "array with 2 items"));
+ }
+ Ok(Self {
+ signed_csr_payload: try_as_bytes(arr.remove(1))?,
+ dice_cert_chain: try_as_bytes(arr.remove(0))?,
+ })
+ }
+}
+
+/// Represents the data to be signed and sent from the client VM to the service VM
+/// for attestation.
+///
+/// It will be signed by both CDI_Leaf_Priv of the client VM's DICE chain and
+/// the private key corresponding to the public key to be attested.
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub struct CsrPayload {
+ /// COSE_Key encoded EC P-256 public key to be attested.
+ pub public_key: Vec<u8>,
+
+ /// A random array with a length between 0 and 64.
+ /// It will be included in the certificate chain in the attestation result,
+ /// serving as proof of the freshness of the result.
+ pub challenge: Vec<u8>,
+}
+
+impl CsrPayload {
+ /// Serializes this object to a CBOR-encoded vector.
+ pub fn into_cbor_vec(self) -> coset::Result<Vec<u8>> {
+ let value = Value::Array(vec![Value::Bytes(self.public_key), Value::Bytes(self.challenge)]);
+ value.to_vec()
+ }
+
+ /// Creates an object instance from the provided CBOR-encoded slice.
+ pub fn from_cbor_slice(data: &[u8]) -> coset::Result<Self> {
+ let value = Value::from_slice(data)?;
+ let Value::Array(mut arr) = value else {
+ return Err(CoseError::UnexpectedItem(cbor_value_type(&value), "array"));
+ };
+ if arr.len() != 2 {
+ return Err(CoseError::UnexpectedItem("array", "array with 2 items"));
+ }
+ Ok(Self {
+ challenge: try_as_bytes(arr.remove(1))?,
+ public_key: try_as_bytes(arr.remove(0))?,
+ })
+ }
+}
+
+fn try_as_bytes(v: Value) -> coset::Result<Vec<u8>> {
+ if let Value::Bytes(data) = v {
+ Ok(data)
+ } else {
+ Err(CoseError::UnexpectedItem(cbor_value_type(&v), "bytes"))
+ }
+}
+
+fn cbor_value_type(v: &Value) -> &'static str {
+ match v {
+ Value::Integer(_) => "int",
+ Value::Bytes(_) => "bstr",
+ Value::Float(_) => "float",
+ Value::Text(_) => "tstr",
+ Value::Bool(_) => "bool",
+ Value::Null => "nul",
+ Value::Tag(_, _) => "tag",
+ Value::Array(_) => "array",
+ Value::Map(_) => "map",
+ _ => "other",
+ }
+}
diff --git a/service_vm/comm/src/lib.rs b/service_vm/comm/src/lib.rs
index d8f7bd7..0818f24 100644
--- a/service_vm/comm/src/lib.rs
+++ b/service_vm/comm/src/lib.rs
@@ -19,9 +19,11 @@
extern crate alloc;
+mod csr;
mod message;
mod vsock;
+pub use csr::{Csr, CsrPayload};
pub use message::{
EcdsaP256KeyPair, GenerateCertificateRequestParams, Request, RequestProcessingError, Response,
ServiceVmRequest,
diff --git a/service_vm/comm/tests/api_test.rs b/service_vm/comm/tests/api_test.rs
new file mode 100644
index 0000000..44a3ef9
--- /dev/null
+++ b/service_vm/comm/tests/api_test.rs
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2023 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 diced_open_dice::DiceArtifacts;
+use service_vm_comm::{Csr, CsrPayload};
+
+/// The following test data are generated with urandom
+const DATA1: [u8; 32] = [
+ 0x8b, 0x09, 0xc0, 0x7e, 0x20, 0x3c, 0xa2, 0x11, 0x7e, 0x7f, 0x0b, 0xdd, 0x2b, 0x68, 0x98, 0xb0,
+ 0x2b, 0x34, 0xb5, 0x63, 0x39, 0x01, 0x90, 0x06, 0xaf, 0x5f, 0xdd, 0xb7, 0x81, 0xca, 0xc7, 0x46,
+];
+const DATA2: [u8; 16] = [
+ 0x6c, 0xb9, 0x39, 0x86, 0x9b, 0x2f, 0x12, 0xd8, 0x45, 0x92, 0x57, 0x44, 0x65, 0xce, 0x94, 0x63,
+];
+
+#[test]
+fn csr_payload_cbor_serialization() {
+ let csr_payload = CsrPayload { public_key: DATA1.to_vec(), challenge: DATA2.to_vec() };
+ let expected_csr_payload = csr_payload.clone();
+ let cbor_vec = csr_payload.into_cbor_vec().unwrap();
+ let deserialized_csr_payload = CsrPayload::from_cbor_slice(&cbor_vec).unwrap();
+
+ assert_eq!(expected_csr_payload, deserialized_csr_payload);
+}
+
+#[test]
+fn csr_cbor_serialization() {
+ let dice_artifacts = diced_sample_inputs::make_sample_bcc_and_cdis().unwrap();
+ let dice_cert_chain = dice_artifacts.bcc().unwrap().to_vec();
+ let csr = Csr { signed_csr_payload: DATA1.to_vec(), dice_cert_chain };
+ let expected_csr = csr.clone();
+ let cbor_vec = csr.into_cbor_vec().unwrap();
+ let deserialized_csr = Csr::from_cbor_slice(&cbor_vec).unwrap();
+
+ assert_eq!(expected_csr, deserialized_csr);
+}
diff --git a/service_vm/test_apk/src/main.rs b/service_vm/test_apk/src/main.rs
index 7d8416f..ba65aca 100644
--- a/service_vm/test_apk/src/main.rs
+++ b/service_vm/test_apk/src/main.rs
@@ -14,10 +14,20 @@
//! Main executable of Service VM client for manual testing.
-use anyhow::Result;
+use anyhow::{anyhow, ensure, Result};
use log::{error, info};
-use std::{ffi::c_void, panic};
-use vm_payload_bindgen::AVmPayload_requestAttestation;
+use std::{
+ ffi::{c_void, CStr},
+ panic,
+ ptr::{self, NonNull},
+ result,
+};
+use vm_payload_bindgen::{
+ attestation_status_t, AVmAttestationResult, AVmAttestationResult_free,
+ AVmAttestationResult_getCertificateAt, AVmAttestationResult_getCertificateCount,
+ AVmAttestationResult_getPrivateKey, AVmAttestationResult_resultToString,
+ AVmAttestationResult_sign, AVmPayload_requestAttestation,
+};
/// Entry point of the Service VM client.
#[allow(non_snake_case)]
@@ -40,38 +50,180 @@
fn try_main() -> Result<()> {
info!("Welcome to Service VM Client!");
+
+ let too_big_challenge = &[0u8; 66];
+ let res = AttestationResult::request_attestation(too_big_challenge);
+ ensure!(res.is_err());
+ let status = res.unwrap_err();
+ ensure!(
+ status == attestation_status_t::ATTESTATION_ERROR_INVALID_CHALLENGE,
+ "Unexpected status: {:?}",
+ status
+ );
+ info!("Status: {:?}", status_to_cstr(status));
+
// The data below is only a placeholder generated randomly with urandom
let challenge = &[
0x6c, 0xad, 0x52, 0x50, 0x15, 0xe7, 0xf4, 0x1d, 0xa5, 0x60, 0x7e, 0xd2, 0x7d, 0xf1, 0x51,
0x67, 0xc3, 0x3e, 0x73, 0x9b, 0x30, 0xbd, 0x04, 0x20, 0x2e, 0xde, 0x3b, 0x1d, 0xc8, 0x07,
0x11, 0x7b,
];
- info!("Sending challenge: {:?}", challenge);
- let certificate = request_attestation(challenge);
- info!("Certificate: {:?}", certificate);
+ let res = AttestationResult::request_attestation(challenge)
+ .map_err(|e| anyhow!("Unexpected status: {:?}", status_to_cstr(e)))?;
+
+ let cert_chain = res.certificate_chain()?;
+ info!("Attestation result certificateChain = {:?}", cert_chain);
+
+ let private_key = res.private_key()?;
+ info!("Attestation result privateKey = {:?}", private_key);
+
+ let message = b"Hello from Service VM client";
+ info!("Signing message: {:?}", message);
+ let signature = res.sign(message)?;
+ info!("Signature: {:?}", signature);
+
Ok(())
}
-fn request_attestation(challenge: &[u8]) -> Vec<u8> {
- // SAFETY: It is safe as we only request the size of the certificate in this call.
- let certificate_size = unsafe {
- AVmPayload_requestAttestation(
- challenge.as_ptr() as *const c_void,
- challenge.len(),
- [].as_mut_ptr(),
+#[derive(Debug)]
+struct AttestationResult(NonNull<AVmAttestationResult>);
+
+impl AttestationResult {
+ fn request_attestation(challenge: &[u8]) -> result::Result<Self, attestation_status_t> {
+ let mut res: *mut AVmAttestationResult = ptr::null_mut();
+ // SAFETY: It is safe as we only read the challenge within its bounds and the
+ // function does not retain any reference to it.
+ let status = unsafe {
+ AVmPayload_requestAttestation(
+ challenge.as_ptr() as *const c_void,
+ challenge.len(),
+ &mut res,
+ )
+ };
+ if status == attestation_status_t::ATTESTATION_OK {
+ info!("Attestation succeeds. Status: {:?}", status_to_cstr(status));
+ let res = NonNull::new(res).expect("The attestation result is null");
+ Ok(Self(res))
+ } else {
+ Err(status)
+ }
+ }
+
+ fn certificate_chain(&self) -> Result<Vec<Box<[u8]>>> {
+ let num_certs = get_certificate_count(self.as_ref());
+ let mut certs = Vec::with_capacity(num_certs);
+ for i in 0..num_certs {
+ certs.push(get_certificate_at(self.as_ref(), i)?);
+ }
+ Ok(certs)
+ }
+
+ fn private_key(&self) -> Result<Box<[u8]>> {
+ get_private_key(self.as_ref())
+ }
+
+ fn sign(&self, message: &[u8]) -> Result<Box<[u8]>> {
+ sign_with_attested_key(self.as_ref(), message)
+ }
+}
+
+impl AsRef<AVmAttestationResult> for AttestationResult {
+ fn as_ref(&self) -> &AVmAttestationResult {
+ // SAFETY: This field is private, and only populated with a successful call to
+ // `AVmPayload_requestAttestation`.
+ unsafe { self.0.as_ref() }
+ }
+}
+
+impl Drop for AttestationResult {
+ fn drop(&mut self) {
+ // SAFETY: This field is private, and only populated with a successful call to
+ // `AVmPayload_requestAttestation`, and not freed elsewhere.
+ unsafe { AVmAttestationResult_free(self.0.as_ptr()) };
+ }
+}
+
+fn get_certificate_count(res: &AVmAttestationResult) -> usize {
+ // SAFETY: The result is returned by `AVmPayload_requestAttestation` and should be valid
+ // before getting freed.
+ unsafe { AVmAttestationResult_getCertificateCount(res) }
+}
+
+fn get_certificate_at(res: &AVmAttestationResult, index: usize) -> Result<Box<[u8]>> {
+ let size =
+ // SAFETY: The result is returned by `AVmPayload_requestAttestation` and should be valid
+ // before getting freed.
+ unsafe { AVmAttestationResult_getCertificateAt(res, index, ptr::null_mut(), 0) };
+ let mut cert = vec![0u8; size];
+ // SAFETY: The result is returned by `AVmPayload_requestAttestation` and should be valid
+ // before getting freed. This function only writes within the bounds of `cert`.
+ // And `cert` cannot overlap `res` because we just allocated it.
+ let size = unsafe {
+ AVmAttestationResult_getCertificateAt(
+ res,
+ index,
+ cert.as_mut_ptr() as *mut c_void,
+ cert.len(),
+ )
+ };
+ ensure!(size == cert.len());
+ Ok(cert.into_boxed_slice())
+}
+
+fn get_private_key(res: &AVmAttestationResult) -> Result<Box<[u8]>> {
+ let size =
+ // SAFETY: The result is returned by `AVmPayload_requestAttestation` and should be valid
+ // before getting freed.
+ unsafe { AVmAttestationResult_getPrivateKey(res, ptr::null_mut(), 0) };
+ let mut private_key = vec![0u8; size];
+ // SAFETY: The result is returned by `AVmPayload_requestAttestation` and should be valid
+ // before getting freed. This function only writes within the bounds of `private_key`.
+ // And `private_key` cannot overlap `res` because we just allocated it.
+ let size = unsafe {
+ AVmAttestationResult_getPrivateKey(
+ res,
+ private_key.as_mut_ptr() as *mut c_void,
+ private_key.len(),
+ )
+ };
+ ensure!(size == private_key.len());
+ Ok(private_key.into_boxed_slice())
+}
+
+fn sign_with_attested_key(res: &AVmAttestationResult, message: &[u8]) -> Result<Box<[u8]>> {
+ // SAFETY: The result is returned by `AVmPayload_requestAttestation` and should be valid
+ // before getting freed.
+ let size = unsafe {
+ AVmAttestationResult_sign(
+ res,
+ message.as_ptr() as *const c_void,
+ message.len(),
+ ptr::null_mut(),
0,
)
};
- let mut certificate = vec![0u8; certificate_size];
- // SAFETY: It is safe as we only write the data into the given buffer within the buffer
- // size in this call.
- unsafe {
- AVmPayload_requestAttestation(
- challenge.as_ptr() as *const c_void,
- challenge.len(),
- certificate.as_mut_ptr() as *mut c_void,
- certificate.len(),
- );
+ let mut signature = vec![0u8; size];
+ // SAFETY: The result is returned by `AVmPayload_requestAttestation` and should be valid
+ // before getting freed. This function only writes within the bounds of `signature`.
+ // And `signature` cannot overlap `res` because we just allocated it.
+ let size = unsafe {
+ AVmAttestationResult_sign(
+ res,
+ message.as_ptr() as *const c_void,
+ message.len(),
+ signature.as_mut_ptr() as *mut c_void,
+ signature.len(),
+ )
};
- certificate
+ ensure!(size == signature.len());
+ Ok(signature.into_boxed_slice())
+}
+
+fn status_to_cstr(status: attestation_status_t) -> &'static CStr {
+ // SAFETY: The function only reads the given enum status and returns a pointer to a
+ // static string.
+ let message = unsafe { AVmAttestationResult_resultToString(status) };
+ // SAFETY: The pointer returned by `AVmAttestationResult_resultToString` is guaranteed to
+ // point to a valid C String.
+ unsafe { CStr::from_ptr(message) }
}
diff --git a/virtualizationmanager/src/aidl.rs b/virtualizationmanager/src/aidl.rs
index da7dffe..4024b04 100644
--- a/virtualizationmanager/src/aidl.rs
+++ b/virtualizationmanager/src/aidl.rs
@@ -24,6 +24,7 @@
use crate::selinux::{getfilecon, SeContext};
use android_os_permissions_aidl::aidl::android::os::IPermissionController;
use android_system_virtualizationcommon::aidl::android::system::virtualizationcommon::{
+ Certificate::Certificate,
DeathReason::DeathReason,
ErrorCode::ErrorCode,
};
@@ -1245,7 +1246,7 @@
}
}
- fn requestAttestation(&self, csr: &[u8]) -> binder::Result<Vec<u8>> {
+ fn requestAttestation(&self, csr: &[u8]) -> binder::Result<Vec<Certificate>> {
GLOBAL_SERVICE.requestAttestation(csr)
}
}
diff --git a/virtualizationservice/aidl/android/system/virtualizationcommon/Certificate.aidl b/virtualizationservice/aidl/android/system/virtualizationcommon/Certificate.aidl
new file mode 100644
index 0000000..d587541
--- /dev/null
+++ b/virtualizationservice/aidl/android/system/virtualizationcommon/Certificate.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2023 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 android.system.virtualizationcommon;
+
+/**
+ * This encodes a X.509 certificate returned in the pVM remote attestation.
+ */
+parcelable Certificate {
+ /**
+ * Contains the bytes of a DER-encoded X.509 certificate.
+ */
+ byte[] encodedCertificate;
+}
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl b/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
index 099a2c0..2592135 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
@@ -15,6 +15,7 @@
*/
package android.system.virtualizationservice_internal;
+import android.system.virtualizationcommon.Certificate;
import android.system.virtualizationservice.AssignableDevice;
import android.system.virtualizationservice.VirtualMachineDebugInfo;
import android.system.virtualizationservice_internal.AtomVmBooted;
@@ -62,7 +63,7 @@
* @return A sequence of DER-encoded X.509 certificates that make up the attestation
* key's certificate chain. The attestation key is provided in the CSR.
*/
- byte[] requestAttestation(in byte[] csr);
+ Certificate[] requestAttestation(in byte[] csr);
/**
* Get a list of assignable devices.
diff --git a/virtualizationservice/aidl/android/system/virtualmachineservice/IVirtualMachineService.aidl b/virtualizationservice/aidl/android/system/virtualmachineservice/IVirtualMachineService.aidl
index 87d3056..3c60478 100644
--- a/virtualizationservice/aidl/android/system/virtualmachineservice/IVirtualMachineService.aidl
+++ b/virtualizationservice/aidl/android/system/virtualmachineservice/IVirtualMachineService.aidl
@@ -15,6 +15,7 @@
*/
package android.system.virtualmachineservice;
+import android.system.virtualizationcommon.Certificate;
import android.system.virtualizationcommon.ErrorCode;
/** {@hide} */
@@ -52,5 +53,5 @@
* @return A sequence of DER-encoded X.509 certificates that make up the attestation
* key's certificate chain. The attestation key is provided in the CSR.
*/
- byte[] requestAttestation(in byte[] csr);
+ Certificate[] requestAttestation(in byte[] csr);
}
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
index 4daa0cf..2be2b19 100644
--- a/virtualizationservice/src/aidl.rs
+++ b/virtualizationservice/src/aidl.rs
@@ -18,6 +18,7 @@
use crate::atom::{forward_vm_booted_atom, forward_vm_creation_atom, forward_vm_exited_atom};
use crate::rkpvm::request_attestation;
use android_os_permissions_aidl::aidl::android::os::IPermissionController;
+use android_system_virtualizationcommon::aidl::android::system::virtualizationcommon::Certificate::Certificate;
use android_system_virtualizationservice::{
aidl::android::system::virtualizationservice::AssignableDevice::AssignableDevice,
aidl::android::system::virtualizationservice::VirtualMachineDebugInfo::VirtualMachineDebugInfo,
@@ -158,7 +159,7 @@
Ok(cids)
}
- fn requestAttestation(&self, csr: &[u8]) -> binder::Result<Vec<u8>> {
+ fn requestAttestation(&self, csr: &[u8]) -> binder::Result<Vec<Certificate>> {
check_manage_access()?;
info!("Received csr. Requestting attestation...");
if cfg!(remote_attestation) {
diff --git a/virtualizationservice/src/rkpvm.rs b/virtualizationservice/src/rkpvm.rs
index 443b280..8f1de6b 100644
--- a/virtualizationservice/src/rkpvm.rs
+++ b/virtualizationservice/src/rkpvm.rs
@@ -17,18 +17,23 @@
//! serves as a trusted platform to attest a client VM.
use android_hardware_security_rkp::aidl::android::hardware::security::keymint::MacedPublicKey::MacedPublicKey;
+use android_system_virtualizationcommon::aidl::android::system::virtualizationcommon::Certificate::Certificate;
use anyhow::{bail, Context, Result};
use service_vm_comm::{GenerateCertificateRequestParams, Request, Response};
use service_vm_manager::ServiceVm;
-pub(crate) fn request_attestation(csr: &[u8]) -> Result<Vec<u8>> {
+pub(crate) fn request_attestation(csr: &[u8]) -> Result<Vec<Certificate>> {
let mut vm = ServiceVm::start()?;
// TODO(b/271275206): Send the correct request type with client VM's
// information to be attested.
let request = Request::Reverse(csr.to_vec());
match vm.process_request(request).context("Failed to process request")? {
- Response::Reverse(cert) => Ok(cert),
+ // TODO(b/271275206): Adjust the response type.
+ Response::Reverse(cert) => {
+ let cert = Certificate { encodedCertificate: cert };
+ Ok(vec![cert])
+ }
_ => bail!("Incorrect response type"),
}
}
diff --git a/vm_payload/Android.bp b/vm_payload/Android.bp
index b5322a8..286612c 100644
--- a/vm_payload/Android.bp
+++ b/vm_payload/Android.bp
@@ -8,7 +8,7 @@
crate_name: "vm_payload",
defaults: ["avf_build_flags_rust"],
visibility: ["//visibility:private"],
- srcs: ["src/*.rs"],
+ srcs: ["src/lib.rs"],
include_dirs: ["include"],
prefer_rlib: true,
rustlibs: [
@@ -19,11 +19,26 @@
"liblazy_static",
"liblibc",
"liblog_rust",
+ "libopenssl",
"librpcbinder_rs",
+ "libvm_payload_status_bindgen",
"libvsock",
],
}
+rust_bindgen {
+ name: "libvm_payload_status_bindgen",
+ wrapper_src: "include/vm_payload.h",
+ crate_name: "vm_payload_status_bindgen",
+ defaults: ["avf_build_flags_rust"],
+ source_stem: "bindings",
+ bindgen_flags: [
+ "--default-enum-style rust",
+ "--allowlist-type=attestation_status_t",
+ ],
+ visibility: [":__subpackages__"],
+}
+
// Rust wrappers round the C API for Rust clients.
// (Yes, this involves going Rust -> C -> Rust.)
rust_bindgen {
@@ -33,6 +48,9 @@
defaults: ["avf_build_flags_rust"],
source_stem: "bindings",
apex_available: ["com.android.compos"],
+ bindgen_flags: [
+ "--default-enum-style rust",
+ ],
visibility: [
"//packages/modules/Virtualization/compos",
"//packages/modules/Virtualization/service_vm/test_apk",
@@ -49,6 +67,7 @@
"libbinder_ndk",
"libbinder_rpc_unstable",
"liblog",
+ "libcrypto",
],
whole_static_libs: ["libvm_payload_impl"],
export_static_lib_headers: ["libvm_payload_impl"],
diff --git a/vm_payload/include-restricted/vm_payload_restricted.h b/vm_payload/include-restricted/vm_payload_restricted.h
index ee92366..15c37ed 100644
--- a/vm_payload/include-restricted/vm_payload_restricted.h
+++ b/vm_payload/include-restricted/vm_payload_restricted.h
@@ -55,23 +55,4 @@
*/
size_t AVmPayload_getDiceAttestationCdi(void* _Nullable data, size_t size);
-/**
- * Requests the remote attestation of the client VM.
- *
- * The challenge will be included in the certificate chain in the attestation result,
- * serving as proof of the freshness of the result.
- *
- * \param challenge A pointer to the challenge buffer.
- * \param challenge_size size of the challenge, the maximum supported challenge size is
- * 64 bytes. An error will be returned if an invalid challenge is
- * passed.
- * \param buffer A pointer to the certificate buffer.
- * \param size number of bytes that can be written to the certificate buffer.
- *
- * \return the total size of the certificate
- */
-size_t AVmPayload_requestAttestation(const void* _Nonnull challenge, size_t challenge_size,
- void* _Nullable buffer, size_t size)
- __INTRODUCED_IN(__ANDROID_API_V__);
-
__END_DECLS
diff --git a/vm_payload/include/vm_payload.h b/vm_payload/include/vm_payload.h
index c28cd42..951b57f 100644
--- a/vm_payload/include/vm_payload.h
+++ b/vm_payload/include/vm_payload.h
@@ -30,6 +30,30 @@
typedef struct AIBinder AIBinder;
/**
+ * Introduced in API 35.
+ * Remote attestation result if the attestation succeeds.
+ */
+struct AVmAttestationResult;
+
+/**
+ * Introduced in API 35.
+ * Remote attestation status types returned from remote attestation functions.
+ */
+typedef enum attestation_status_t : int32_t {
+ /** The remote attestation completes successfully. */
+ ATTESTATION_OK = 0,
+
+ /** The challenge size is not between 0 and 64. */
+ ATTESTATION_ERROR_INVALID_CHALLENGE = -10001,
+
+ /** Failed to attest the VM. Please retry at a later time. */
+ ATTESTATION_ERROR_ATTESTATION_FAILED = -10002,
+
+ /** Remote attestation is not supported in the current environment. */
+ ATTESTATION_ERROR_UNSUPPORTED = -10003,
+} attestation_status_t;
+
+/**
* Notifies the host that the payload is ready.
*
* If the host app has set a `VirtualMachineCallback` for the VM, its
@@ -112,4 +136,129 @@
*/
const char* _Nullable AVmPayload_getEncryptedStoragePath(void);
+/**
+ * Requests the remote attestation of the client VM.
+ *
+ * The challenge will be included in the certificate chain in the attestation result,
+ * serving as proof of the freshness of the result.
+ *
+ * \param challenge A pointer to the challenge buffer.
+ * \param challenge_size size of the challenge. The maximum supported challenge size is
+ * 64 bytes. The status ATTESTATION_ERROR_INVALID_CHALLENGE will be returned if
+ * an invalid challenge is passed.
+ * \param result The remote attestation result will be filled here if the attestation
+ * succeeds. The result remains valid until it is freed with
+ * `AVmPayload_freeAttestationResult`.
+ *
+ * \return ATTESTATION_OK upon successful attestation.
+ */
+attestation_status_t AVmPayload_requestAttestation(
+ const void* _Nonnull challenge, size_t challenge_size,
+ struct AVmAttestationResult* _Nullable* _Nonnull result) __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Converts the return value from `AVmPayload_requestAttestation` to a text string
+ * representing the status code.
+ *
+ * \return a constant string value representing the status code. The string should not
+ * be deleted or freed by the application and remains valid for the lifetime of the VM.
+ */
+const char* _Nonnull AVmAttestationResult_resultToString(attestation_status_t status)
+ __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Frees all the data owned by the provided attestation result, including the result itself.
+ *
+ * Callers should ensure to invoke this API only once on a valid attestation result
+ * returned by `AVmPayload_requestAttestation` to avoid undefined behavior.
+ *
+ * \param result A pointer to the attestation result.
+ */
+void AVmAttestationResult_free(struct AVmAttestationResult* _Nullable result)
+ __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Reads the DER-encoded ECPrivateKey structure specified in [RFC 5915 s3] for the
+ * EC P-256 private key from the provided attestation result.
+ *
+ * \param result A pointer to the attestation result filled in
+ * `AVmPayload_requestAttestation` when the attestation succeeds.
+ * \param data A pointer to the memory where the private key will be written
+ * (can be null if size is 0).
+ * \param size The maximum number of bytes that can be written to the data buffer.
+ * If `size` is smaller than the total size of the private key, the key data will be
+ * truncated to this `size`.
+ *
+ * \return The total size of the private key.
+ *
+ * [RFC 5915 s3]: https://datatracker.ietf.org/doc/html/rfc5915#section-3
+ */
+size_t AVmAttestationResult_getPrivateKey(const struct AVmAttestationResult* _Nonnull result,
+ void* _Nullable data, size_t size)
+ __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Signs the given message using ECDSA P-256, the message is first hashed with SHA-256 and
+ * then it is signed with the attested EC P-256 private key in the attestation result.
+ *
+ * \param result A pointer to the attestation result filled in
+ * `AVmPayload_requestAttestation` when the attestation succeeds.
+ * \param message A pointer to the message buffer.
+ * \param message_size size of the message.
+ * \param data A pointer to the memory where the signature will be written
+ * (can be null if size is 0). The signature is a DER-encoded ECDSASignature structure
+ * detailed in the [RFC 6979].
+ * \param size The maximum number of bytes that can be written to the data buffer.
+ * If `size` is smaller than the total size of the signature, the signature will be
+ * truncated to this `size`.
+ *
+ * \return The total size of the signature.
+ *
+ * [RFC 6979]: https://datatracker.ietf.org/doc/html/rfc6979
+ */
+size_t AVmAttestationResult_sign(const struct AVmAttestationResult* _Nonnull result,
+ const void* _Nonnull message, size_t message_size,
+ void* _Nullable data, size_t size)
+ __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Gets the number of certificates in the certificate chain.
+ *
+ * The certificate chain consists of a sequence of DER-encoded X.509 certificates that form
+ * the attestation key's certificate chain. It starts with a root certificate and ends with a
+ * leaf certificate covering the attested public key.
+ *
+ * \param result A pointer to the attestation result obtained from `AVmPayload_requestAttestation`
+ * when the attestation succeeds.
+ *
+ * \return The number of certificates in the certificate chain.
+ */
+size_t AVmAttestationResult_getCertificateCount(const struct AVmAttestationResult* _Nonnull result)
+ __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Retrieves the certificate at the given `index` from the certificate chain in the provided
+ * attestation result.
+ *
+ * The certificate chain consists of a sequence of DER-encoded X.509 certificates that form
+ * the attestation key's certificate chain. It starts with a root certificate and ends with a
+ * leaf certificate covering the attested public key.
+ *
+ * \param result A pointer to the attestation result obtained from `AVmPayload_requestAttestation`
+ * when the attestation succeeds.
+ * \param index Index of the certificate to retrieve. The `index` must be within the range of
+ * [0, number of certificates). The number of certificates can be obtained with
+ * `AVmAttestationResult_getCertificateCount`.
+ * \param data A pointer to the memory where the certificate will be written
+ * (can be null if size is 0).
+ * \param size The maximum number of bytes that can be written to the data buffer. If `size`
+ * is smaller than the total size of the certificate, the certificate will be
+ * truncated to this `size`.
+ *
+ * \return The total size of the certificate at the given `index`.
+ */
+size_t AVmAttestationResult_getCertificateAt(const struct AVmAttestationResult* _Nonnull result,
+ size_t index, void* _Nullable data, size_t size)
+ __INTRODUCED_IN(__ANDROID_API_V__);
+
__END_DECLS
diff --git a/vm_payload/libvm_payload.map.txt b/vm_payload/libvm_payload.map.txt
index 32dd33b..975a5a3 100644
--- a/vm_payload/libvm_payload.map.txt
+++ b/vm_payload/libvm_payload.map.txt
@@ -8,6 +8,12 @@
AVmPayload_getApkContentsPath; # systemapi introduced=UpsideDownCake
AVmPayload_getEncryptedStoragePath; # systemapi introduced=UpsideDownCake
AVmPayload_requestAttestation; # systemapi introduced=VanillaIceCream
+ AVmAttestationResult_getPrivateKey; # systemapi introduced=VanillaIceCream
+ AVmAttestationResult_sign; # systemapi introduced=VanillaIceCream
+ AVmAttestationResult_free; # systemapi introduced=VanillaIceCream
+ AVmAttestationResult_resultToString; # systemapi introduced=VanillaIceCream
+ AVmAttestationResult_getCertificateCount; # systemapi introduced=VanillaIceCream
+ AVmAttestationResult_getCertificateAt; # systemapi introduced=VanillaIceCream
local:
*;
};
diff --git a/vm_payload/src/api.rs b/vm_payload/src/api.rs
index 93dbd1c..c76f2d3 100644
--- a/vm_payload/src/api.rs
+++ b/vm_payload/src/api.rs
@@ -14,20 +14,30 @@
//! This module handles the interaction with virtual machine payload service.
-use android_system_virtualization_payload::aidl::android::system::virtualization::payload::IVmPayloadService::{
- ENCRYPTEDSTORE_MOUNTPOINT, IVmPayloadService, VM_PAYLOAD_SERVICE_SOCKET_NAME, VM_APK_CONTENTS_PATH};
-use anyhow::{ensure, bail, Context, Result};
-use binder::{Strong, unstable_api::{AIBinder, new_spibinder}};
+use android_system_virtualization_payload::aidl::android::system::virtualization::payload:: IVmPayloadService::{
+ IVmPayloadService, ENCRYPTEDSTORE_MOUNTPOINT, VM_APK_CONTENTS_PATH,
+ VM_PAYLOAD_SERVICE_SOCKET_NAME, AttestationResult::AttestationResult,
+};
+use anyhow::{bail, ensure, Context, Result};
+use binder::{
+ unstable_api::{new_spibinder, AIBinder},
+ Strong, ExceptionCode,
+};
use lazy_static::lazy_static;
use log::{error, info, Level};
-use rpcbinder::{RpcSession, RpcServer};
+use rpcbinder::{RpcServer, RpcSession};
+use openssl::{ec::EcKey, sha::sha256, ecdsa::EcdsaSig};
use std::convert::Infallible;
-use std::ffi::CString;
+use std::ffi::{CString, CStr};
use std::fmt::Debug;
use std::os::raw::{c_char, c_void};
use std::path::Path;
-use std::ptr;
-use std::sync::{Mutex, atomic::{AtomicBool, Ordering}};
+use std::ptr::{self, NonNull};
+use std::sync::{
+ atomic::{AtomicBool, Ordering},
+ Mutex,
+};
+use vm_payload_status_bindgen::attestation_status_t;
lazy_static! {
static ref VM_APK_CONTENTS_PATH_C: CString =
@@ -263,42 +273,223 @@
/// Behavior is undefined if any of the following conditions are violated:
///
/// * `challenge` must be [valid] for reads of `challenge_size` bytes.
-/// * `buffer` must be [valid] for writes of `size` bytes. `buffer` can be null if `size` is 0.
+/// * `res` must be [valid] to write the attestation result.
+/// * The region of memory beginning at `challenge` with `challenge_size` bytes must not
+/// overlap with the region of memory `res` points to.
///
/// [valid]: ptr#safety
#[no_mangle]
pub unsafe extern "C" fn AVmPayload_requestAttestation(
challenge: *const u8,
challenge_size: usize,
- buffer: *mut u8,
+ res: &mut *mut AttestationResult,
+) -> attestation_status_t {
+ initialize_logging();
+ const MAX_CHALLENGE_SIZE: usize = 64;
+ if challenge_size > MAX_CHALLENGE_SIZE {
+ return attestation_status_t::ATTESTATION_ERROR_INVALID_CHALLENGE;
+ }
+ let challenge = if challenge_size == 0 {
+ &[]
+ } else {
+ // SAFETY: The caller guarantees that `challenge` is valid for reads of
+ // `challenge_size` bytes and `challenge_size` is not zero.
+ unsafe { std::slice::from_raw_parts(challenge, challenge_size) }
+ };
+ let service = unwrap_or_abort(get_vm_payload_service());
+ match service.requestAttestation(challenge) {
+ Ok(attestation_res) => {
+ *res = Box::into_raw(Box::new(attestation_res));
+ attestation_status_t::ATTESTATION_OK
+ }
+ Err(e) => {
+ error!("Remote attestation failed: {e:?}");
+ binder_status_to_attestation_status(e)
+ }
+ }
+}
+
+fn binder_status_to_attestation_status(status: binder::Status) -> attestation_status_t {
+ match status.exception_code() {
+ ExceptionCode::UNSUPPORTED_OPERATION => attestation_status_t::ATTESTATION_ERROR_UNSUPPORTED,
+ _ => attestation_status_t::ATTESTATION_ERROR_ATTESTATION_FAILED,
+ }
+}
+
+/// Converts the return value from `AVmPayload_requestAttestation` to a text string
+/// representing the error code.
+#[no_mangle]
+pub extern "C" fn AVmAttestationResult_resultToString(
+ status: attestation_status_t,
+) -> *const c_char {
+ let message = match status {
+ attestation_status_t::ATTESTATION_OK => {
+ CStr::from_bytes_with_nul(b"The remote attestation completes successfully.\0").unwrap()
+ }
+ attestation_status_t::ATTESTATION_ERROR_INVALID_CHALLENGE => {
+ CStr::from_bytes_with_nul(b"The challenge size is not between 0 and 64.\0").unwrap()
+ }
+ attestation_status_t::ATTESTATION_ERROR_ATTESTATION_FAILED => {
+ CStr::from_bytes_with_nul(b"Failed to attest the VM. Please retry at a later time.\0")
+ .unwrap()
+ }
+ attestation_status_t::ATTESTATION_ERROR_UNSUPPORTED => CStr::from_bytes_with_nul(
+ b"Remote attestation is not supported in the current environment.\0",
+ )
+ .unwrap(),
+ };
+ message.as_ptr()
+}
+
+/// Reads the DER-encoded ECPrivateKey structure specified in [RFC 5915 s3] for the
+/// EC P-256 private key from the provided attestation result.
+///
+/// # Safety
+///
+/// Behavior is undefined if any of the following conditions are violated:
+///
+/// * `data` must be [valid] for writes of `size` bytes, if size > 0.
+/// * The region of memory beginning at `data` with `size` bytes must not overlap with the
+/// region of memory `res` points to.
+///
+/// [valid]: ptr#safety
+/// [RFC 5915 s3]: https://datatracker.ietf.org/doc/html/rfc5915#section-3
+#[no_mangle]
+pub unsafe extern "C" fn AVmAttestationResult_getPrivateKey(
+ res: &AttestationResult,
+ data: *mut u8,
size: usize,
) -> usize {
- initialize_logging();
+ let private_key = &res.privateKey;
+ if size != 0 {
+ let data = NonNull::new(data).expect("data must not be null when size > 0");
+ // SAFETY: See the requirements on `data` above. The number of bytes copied doesn't exceed
+ // the length of either buffer, and the caller ensures that `private_key` cannot overlap
+ // `data`. We allow data to be null, which is never valid, but only if size == 0
+ // which is checked above.
+ unsafe {
+ ptr::copy_nonoverlapping(
+ private_key.as_ptr(),
+ data.as_ptr(),
+ std::cmp::min(private_key.len(), size),
+ )
+ };
+ }
+ private_key.len()
+}
- // SAFETY: See the requirements on `challenge` above.
- let challenge = unsafe { std::slice::from_raw_parts(challenge, challenge_size) };
- let certificate = unwrap_or_abort(try_request_attestation(challenge));
+/// Signs the given message using ECDSA P-256, the message is first hashed with SHA-256 and
+/// then it is signed with the attested EC P-256 private key in the attestation result.
+///
+/// # Safety
+///
+/// Behavior is undefined if any of the following conditions are violated:
+///
+/// * `message` must be [valid] for reads of `message_size` bytes.
+/// * `data` must be [valid] for writes of `size` bytes, if size > 0.
+/// * The region of memory beginning at `data` with `size` bytes must not overlap with the
+/// region of memory `res` or `message` point to.
+///
+///
+/// [valid]: ptr#safety
+#[no_mangle]
+pub unsafe extern "C" fn AVmAttestationResult_sign(
+ res: &AttestationResult,
+ message: *const u8,
+ message_size: usize,
+ data: *mut u8,
+ size: usize,
+) -> usize {
+ if message_size == 0 {
+ panic!("Message to be signed must not be empty.")
+ }
+ // SAFETY: See the requirements on `message` above.
+ let message = unsafe { std::slice::from_raw_parts(message, message_size) };
+ let signature = unwrap_or_abort(try_ecdsa_sign(message, &res.privateKey));
+ if size != 0 {
+ let data = NonNull::new(data).expect("data must not be null when size > 0");
+ // SAFETY: See the requirements on `data` above. The number of bytes copied doesn't exceed
+ // the length of either buffer, and the caller ensures that `signature` cannot overlap
+ // `data`. We allow data to be null, which is never valid, but only if size == 0
+ // which is checked above.
+ unsafe {
+ ptr::copy_nonoverlapping(
+ signature.as_ptr(),
+ data.as_ptr(),
+ std::cmp::min(signature.len(), size),
+ )
+ };
+ }
+ signature.len()
+}
- if size != 0 || buffer.is_null() {
- // SAFETY: See the requirements on `buffer` above. The number of bytes copied doesn't exceed
- // the length of either buffer, and `certificate` cannot overlap `buffer` because we just
- // allocated it.
+fn try_ecdsa_sign(message: &[u8], der_encoded_ec_private_key: &[u8]) -> Result<Vec<u8>> {
+ let private_key = EcKey::private_key_from_der(der_encoded_ec_private_key)?;
+ let digest = sha256(message);
+ let sig = EcdsaSig::sign(&digest, &private_key)?;
+ Ok(sig.to_der()?)
+}
+
+/// Gets the number of certificates in the certificate chain.
+#[no_mangle]
+pub extern "C" fn AVmAttestationResult_getCertificateCount(res: &AttestationResult) -> usize {
+ res.certificateChain.len()
+}
+
+/// Retrieves the certificate at the given `index` from the certificate chain in the provided
+/// attestation result.
+///
+/// # Safety
+///
+/// Behavior is undefined if any of the following conditions are violated:
+///
+/// * `data` must be [valid] for writes of `size` bytes, if size > 0.
+/// * `index` must be within the range of [0, number of certificates). The number of certificates
+/// can be obtained with `AVmAttestationResult_getCertificateCount`.
+/// * The region of memory beginning at `data` with `size` bytes must not overlap with the
+/// region of memory `res` points to.
+///
+/// [valid]: ptr#safety
+#[no_mangle]
+pub unsafe extern "C" fn AVmAttestationResult_getCertificateAt(
+ res: &AttestationResult,
+ index: usize,
+ data: *mut u8,
+ size: usize,
+) -> usize {
+ let certificate =
+ &res.certificateChain.get(index).expect("The index is out of bounds.").encodedCertificate;
+ if size != 0 {
+ let data = NonNull::new(data).expect("data must not be null when size > 0");
+ // SAFETY: See the requirements on `data` above. The number of bytes copied doesn't exceed
+ // the length of either buffer, and the caller ensures that `certificate` cannot overlap
+ // `data`. We allow data to be null, which is never valid, but only if size == 0
+ // which is checked above.
unsafe {
ptr::copy_nonoverlapping(
certificate.as_ptr(),
- buffer,
+ data.as_ptr(),
std::cmp::min(certificate.len(), size),
- );
- }
+ )
+ };
}
certificate.len()
}
-fn try_request_attestation(challenge: &[u8]) -> Result<Vec<u8>> {
- let certificate = get_vm_payload_service()?
- .requestAttestation(challenge)
- .context("Failed to request attestation")?;
- Ok(certificate)
+/// Frees all the data owned by given attestation result and result itself.
+///
+/// # Safety
+///
+/// Behavior is undefined if any of the following conditions are violated:
+///
+/// * `res` must point to a valid `AttestationResult` and has not been freed before.
+#[no_mangle]
+pub unsafe extern "C" fn AVmAttestationResult_free(res: *mut AttestationResult) {
+ if !res.is_null() {
+ // SAFETY: The result is only freed once is ensured by the caller.
+ let res = unsafe { Box::from_raw(res) };
+ drop(res)
+ }
}
/// Gets the path to the APK contents.
diff --git a/vm_payload/src/lib.rs b/vm_payload/src/lib.rs
index 4d059d1..9e10895 100644
--- a/vm_payload/src/lib.rs
+++ b/vm_payload/src/lib.rs
@@ -17,7 +17,9 @@
mod api;
pub use api::{
- AVmPayload_getCertificate, AVmPayload_getDiceAttestationCdi,
- AVmPayload_getDiceAttestationChain, AVmPayload_getVmInstanceSecret,
- AVmPayload_notifyPayloadReady,
+ AVmAttestationResult_free, AVmAttestationResult_getCertificateAt,
+ AVmAttestationResult_getCertificateCount, AVmAttestationResult_getPrivateKey,
+ AVmAttestationResult_resultToString, AVmAttestationResult_sign,
+ AVmPayload_getDiceAttestationCdi, AVmPayload_getDiceAttestationChain,
+ AVmPayload_getVmInstanceSecret, AVmPayload_notifyPayloadReady, AVmPayload_requestAttestation,
};
diff --git a/vmbase/src/memory/dbm.rs b/vmbase/src/memory/dbm.rs
index 401022e..108cd5d 100644
--- a/vmbase/src/memory/dbm.rs
+++ b/vmbase/src/memory/dbm.rs
@@ -14,7 +14,7 @@
//! Hardware management of the access flag and dirty state.
-use super::page_table::{is_leaf_pte, PageTable};
+use super::page_table::PageTable;
use super::util::flush_region;
use crate::{dsb, isb, read_sysreg, tlbi, write_sysreg};
use aarch64_paging::paging::{Attributes, Descriptor, MemoryRegion};
@@ -52,14 +52,10 @@
/// Flushes a memory range the descriptor refers to, if the descriptor is in writable-dirty state.
pub(super) fn flush_dirty_range(
va_range: &MemoryRegion,
- desc: &mut Descriptor,
- level: usize,
+ desc: &Descriptor,
+ _level: usize,
) -> Result<(), ()> {
- // Only flush ranges corresponding to dirty leaf PTEs.
let flags = desc.flags().ok_or(())?;
- if !is_leaf_pte(&flags, level) {
- return Ok(());
- }
if !flags.contains(Attributes::READ_ONLY) {
flush_region(va_range.start().0, va_range.len());
}
@@ -71,12 +67,9 @@
pub(super) fn mark_dirty_block(
va_range: &MemoryRegion,
desc: &mut Descriptor,
- level: usize,
+ _level: usize,
) -> Result<(), ()> {
let flags = desc.flags().ok_or(())?;
- if !is_leaf_pte(&flags, level) {
- return Ok(());
- }
if flags.contains(Attributes::DBM) {
assert!(flags.contains(Attributes::READ_ONLY), "unexpected PTE writable state");
desc.modify_flags(Attributes::empty(), Attributes::READ_ONLY);
diff --git a/vmbase/src/memory/page_table.rs b/vmbase/src/memory/page_table.rs
index e067e96..dc346e7 100644
--- a/vmbase/src/memory/page_table.rs
+++ b/vmbase/src/memory/page_table.rs
@@ -16,7 +16,7 @@
use crate::read_sysreg;
use aarch64_paging::idmap::IdMap;
-use aarch64_paging::paging::{Attributes, MemoryRegion, PteUpdater};
+use aarch64_paging::paging::{Attributes, Constraints, Descriptor, MemoryRegion};
use aarch64_paging::MapError;
use core::result;
@@ -83,7 +83,9 @@
/// code being currently executed. Otherwise, the Rust execution model (on which the borrow
/// checker relies) would be violated.
pub unsafe fn activate(&mut self) {
- self.idmap.activate()
+ // SAFETY: the caller of this unsafe function asserts that switching to a different
+ // translation is safe
+ unsafe { self.idmap.activate() }
}
/// Maps the given range of virtual addresses to the physical addresses as lazily mapped
@@ -107,7 +109,15 @@
/// Maps the given range of virtual addresses to the physical addresses as non-executable,
/// read-only and writable-clean normal memory.
pub fn map_data_dbm(&mut self, range: &MemoryRegion) -> Result<()> {
- self.idmap.map_range(range, DATA_DBM)
+ // Map the region down to pages to minimize the size of the regions that will be marked
+ // dirty once a store hits them, but also to ensure that we can clear the read-only
+ // attribute while the mapping is live without causing break-before-make (BBM) violations.
+ // The latter implies that we must avoid the use of the contiguous hint as well.
+ self.idmap.map_range_with_constraints(
+ range,
+ DATA_DBM,
+ Constraints::NO_BLOCK_MAPPINGS | Constraints::NO_CONTIGUOUS_HINT,
+ )
}
/// Maps the given range of virtual addresses to the physical addresses as read-only
@@ -124,18 +134,20 @@
/// Applies the provided updater function to a number of PTEs corresponding to a given memory
/// range.
- pub fn modify_range(&mut self, range: &MemoryRegion, f: &PteUpdater) -> Result<()> {
+ pub fn modify_range<F>(&mut self, range: &MemoryRegion, f: &F) -> Result<()>
+ where
+ F: Fn(&MemoryRegion, &mut Descriptor, usize) -> result::Result<(), ()>,
+ {
self.idmap.modify_range(range, f)
}
-}
-/// Checks whether a PTE at given level is a page or block descriptor.
-#[inline]
-pub(super) fn is_leaf_pte(flags: &Attributes, level: usize) -> bool {
- const LEAF_PTE_LEVEL: usize = 3;
- if flags.contains(Attributes::TABLE_OR_PAGE) {
- level == LEAF_PTE_LEVEL
- } else {
- level < LEAF_PTE_LEVEL
+ /// Applies the provided callback function to a number of PTEs corresponding to a given memory
+ /// range.
+ pub fn walk_range<F>(&self, range: &MemoryRegion, f: &F) -> Result<()>
+ where
+ F: Fn(&MemoryRegion, &Descriptor, usize) -> result::Result<(), ()>,
+ {
+ let mut callback = |mr: &MemoryRegion, d: &Descriptor, l: usize| f(mr, d, l);
+ self.idmap.walk_range(range, &mut callback)
}
}
diff --git a/vmbase/src/memory/shared.rs b/vmbase/src/memory/shared.rs
index 6c8a844..dd433d4 100644
--- a/vmbase/src/memory/shared.rs
+++ b/vmbase/src/memory/shared.rs
@@ -16,12 +16,14 @@
use super::dbm::{flush_dirty_range, mark_dirty_block, set_dbm_enabled};
use super::error::MemoryTrackerError;
-use super::page_table::{is_leaf_pte, PageTable, MMIO_LAZY_MAP_FLAG};
+use super::page_table::{PageTable, MMIO_LAZY_MAP_FLAG};
use super::util::{page_4kb_of, virt_to_phys};
use crate::dsb;
use crate::exceptions::HandleExceptionError;
use crate::util::RangeExt as _;
-use aarch64_paging::paging::{Attributes, Descriptor, MemoryRegion as VaRange, VirtualAddress};
+use aarch64_paging::paging::{
+ Attributes, Descriptor, MemoryRegion as VaRange, VirtualAddress, BITS_PER_LEVEL, PAGE_SIZE,
+};
use alloc::alloc::{alloc_zeroed, dealloc, handle_alloc_error};
use alloc::boxed::Box;
use alloc::vec::Vec;
@@ -253,7 +255,7 @@
if get_mmio_guard().is_some() {
for range in &self.mmio_regions {
self.page_table
- .modify_range(&get_va_range(range), &mmio_guard_unmap_page)
+ .walk_range(&get_va_range(range), &mmio_guard_unmap_page)
.map_err(|_| MemoryTrackerError::FailedToUnmap)?;
}
}
@@ -319,14 +321,24 @@
/// table entry and MMIO guard mapping the block. Breaks apart a block entry if required.
fn handle_mmio_fault(&mut self, addr: VirtualAddress) -> Result<()> {
let page_start = VirtualAddress(page_4kb_of(addr.0));
+ assert_eq!(page_start.0 % MMIO_GUARD_GRANULE_SIZE, 0);
let page_range: VaRange = (page_start..page_start + MMIO_GUARD_GRANULE_SIZE).into();
let mmio_guard = get_mmio_guard().unwrap();
+ // This must be safe and free from break-before-make (BBM) violations, given that the
+ // initial lazy mapping has the valid bit cleared, and each newly created valid descriptor
+ // created inside the mapping has the same size and alignment.
self.page_table
- .modify_range(&page_range, &verify_lazy_mapped_block)
+ .modify_range(&page_range, &|_: &VaRange, desc: &mut Descriptor, _: usize| {
+ let flags = desc.flags().expect("Unsupported PTE flags set");
+ if flags.contains(MMIO_LAZY_MAP_FLAG) && !flags.contains(Attributes::VALID) {
+ desc.modify_flags(Attributes::VALID, Attributes::empty());
+ Ok(())
+ } else {
+ Err(())
+ }
+ })
.map_err(|_| MemoryTrackerError::InvalidPte)?;
- mmio_guard.map(page_start.0)?;
- // Maps a single device page, breaking up block mappings if necessary.
- self.page_table.map_device(&page_range).map_err(|_| MemoryTrackerError::FailedToMap)
+ Ok(mmio_guard.map(page_start.0)?)
}
/// Flush all memory regions marked as writable-dirty.
@@ -340,7 +352,7 @@
// Now flush writable-dirty pages in those regions.
for range in writable_regions.chain(self.payload_range.as_ref().into_iter()) {
self.page_table
- .modify_range(&get_va_range(range), &flush_dirty_range)
+ .walk_range(&get_va_range(range), &flush_dirty_range)
.map_err(|_| MemoryTrackerError::FlushRegionFailed)?;
}
Ok(())
@@ -467,33 +479,13 @@
}
}
-/// Checks whether block flags indicate it should be MMIO guard mapped.
-fn verify_lazy_mapped_block(
- _range: &VaRange,
- desc: &mut Descriptor,
- level: usize,
-) -> result::Result<(), ()> {
- let flags = desc.flags().expect("Unsupported PTE flags set");
- if !is_leaf_pte(&flags, level) {
- return Ok(()); // Skip table PTEs as they aren't tagged with MMIO_LAZY_MAP_FLAG.
- }
- if flags.contains(MMIO_LAZY_MAP_FLAG) && !flags.contains(Attributes::VALID) {
- Ok(())
- } else {
- Err(())
- }
-}
-
/// MMIO guard unmaps page
fn mmio_guard_unmap_page(
va_range: &VaRange,
- desc: &mut Descriptor,
+ desc: &Descriptor,
level: usize,
) -> result::Result<(), ()> {
let flags = desc.flags().expect("Unsupported PTE flags set");
- if !is_leaf_pte(&flags, level) {
- return Ok(());
- }
// This function will be called on an address range that corresponds to a device. Only if a
// page has been accessed (written to or read from), will it contain the VALID flag and be MMIO
// guard mapped. Therefore, we can skip unmapping invalid pages, they were never MMIO guard
@@ -503,9 +495,11 @@
flags.contains(MMIO_LAZY_MAP_FLAG),
"Attempting MMIO guard unmap for non-device pages"
);
+ const MMIO_GUARD_GRANULE_SHIFT: u32 = MMIO_GUARD_GRANULE_SIZE.ilog2() - PAGE_SIZE.ilog2();
+ const MMIO_GUARD_GRANULE_LEVEL: usize =
+ 3 - (MMIO_GUARD_GRANULE_SHIFT as usize / BITS_PER_LEVEL);
assert_eq!(
- va_range.len(),
- MMIO_GUARD_GRANULE_SIZE,
+ level, MMIO_GUARD_GRANULE_LEVEL,
"Failed to break down block mapping before MMIO guard mapping"
);
let page_base = va_range.start().0;