Merge "Implement time-based memory balloon inflation" into main
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/ImageArchive.kt b/android/TerminalApp/java/com/android/virtualization/terminal/ImageArchive.kt
index 017ff89..be1f922 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/ImageArchive.kt
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/ImageArchive.kt
@@ -65,6 +65,14 @@
}
}
+ /** Returns path to the archive. */
+ fun getPath(): String {
+ return when (source) {
+ is UrlSource -> source.value.toString()
+ is PathSource -> source.value.toString()
+ }
+ }
+
/** Returns size of the archive in bytes */
@Throws(IOException::class)
fun getSize(): Long {
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/InstallerService.kt b/android/TerminalApp/java/com/android/virtualization/terminal/InstallerService.kt
index 7180e87..01c3880 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/InstallerService.kt
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/InstallerService.kt
@@ -150,21 +150,26 @@
private fun downloadFromSdcard(): Boolean {
val archive = fromSdCard()
+ val archive_path = archive.getPath()
// Installing from sdcard is preferred, but only supported only in debuggable build.
- if (Build.isDebuggable() && archive.exists()) {
- Log.i(TAG, "trying to install /sdcard/linux/images.tar.gz")
+ if (!Build.isDebuggable()) {
+ Log.i(TAG, "Non-debuggable build doesn't support installation from $archive_path")
+ return false
+ }
+ if (!archive.exists()) {
+ return false
+ }
- val dest = getDefault(this).installDir
- try {
- archive.installTo(dest, null)
- Log.i(TAG, "image is installed from /sdcard/linux/images.tar.gz")
- return true
- } catch (e: IOException) {
- Log.i(TAG, "Failed to install /sdcard/linux/images.tar.gz", e)
- }
- } else {
- Log.i(TAG, "Non-debuggable build doesn't support installation from /sdcard/linux")
+ Log.i(TAG, "trying to install $archive_path")
+
+ val dest = getDefault(this).installDir
+ try {
+ archive.installTo(dest, null)
+ Log.i(TAG, "image is installed from $archive_path")
+ return true
+ } catch (e: IOException) {
+ Log.i(TAG, "Failed to install $archive_path", e)
}
return false
}
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/VmLauncherService.kt b/android/TerminalApp/java/com/android/virtualization/terminal/VmLauncherService.kt
index f0539d6..a9b4abe 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/VmLauncherService.kt
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/VmLauncherService.kt
@@ -188,10 +188,7 @@
val displaySize = intent.getParcelableExtra(EXTRA_DISPLAY_INFO, DisplayInfo::class.java)
customImageConfigBuilder.setAudioConfig(
- AudioConfig.Builder()
- .setUseSpeaker(true)
- .setUseMicrophone(true)
- .build()
+ AudioConfig.Builder().setUseSpeaker(true).setUseMicrophone(true).build()
)
if (overrideConfigIfNecessary(customImageConfigBuilder, displaySize)) {
configBuilder.setCustomImageConfig(customImageConfigBuilder.build())
@@ -307,6 +304,21 @@
)
Toast.makeText(this, R.string.virgl_enabled, Toast.LENGTH_SHORT).show()
changed = true
+ } else if (Files.exists(ImageArchive.getSdcardPathForTesting().resolve("gfxstream"))) {
+ // TODO: check if the configuration is right. current config comes from cuttlefish's one
+ builder.setGpuConfig(
+ VirtualMachineCustomImageConfig.GpuConfig.Builder()
+ .setBackend("gfxstream")
+ .setRendererUseEgl(false)
+ .setRendererUseGles(false)
+ .setRendererUseGlx(false)
+ .setRendererUseSurfaceless(true)
+ .setRendererUseVulkan(true)
+ .setContextTypes(arrayOf<String>("gfxstream-vulkan", "gfxstream-composer"))
+ .build()
+ )
+ Toast.makeText(this, "gfxstream", Toast.LENGTH_SHORT).show()
+ changed = true
}
// Set the initial display size
diff --git a/android/vm_demo_native/main.cpp b/android/vm_demo_native/main.cpp
index e1acc05..8fc14bf 100644
--- a/android/vm_demo_native/main.cpp
+++ b/android/vm_demo_native/main.cpp
@@ -329,11 +329,15 @@
&ARpcSession_free);
ARpcSession_setMaxIncomingThreads(session.get(), 1);
+ auto param = std::make_unique<std::shared_ptr<IVirtualMachine>>(std::move(vm));
+ auto paramDeleteFd = [](void* param) {
+ delete static_cast<std::shared_ptr<IVirtualMachine>*>(param);
+ };
+
AIBinder* binder = ARpcSession_setupPreconnectedClient(
session.get(),
[](void* param) {
- std::shared_ptr<IVirtualMachine> vm =
- *static_cast<std::shared_ptr<IVirtualMachine>*>(param);
+ IVirtualMachine* vm = static_cast<std::shared_ptr<IVirtualMachine>*>(param)->get();
ScopedFileDescriptor sock_fd;
ScopedAStatus ret = vm->connectVsock(ITestService::PORT, &sock_fd);
if (!ret.isOk()) {
@@ -341,7 +345,7 @@
}
return sock_fd.release();
},
- &vm);
+ param.release(), paramDeleteFd);
if (binder == nullptr) {
return Error() << "Failed to connect to vm payload";
}
diff --git a/build/apex/manifest.json b/build/apex/manifest.json
index b32aa7b..e596ce1 100644
--- a/build/apex/manifest.json
+++ b/build/apex/manifest.json
@@ -3,6 +3,7 @@
"version": 2,
"requireNativeLibs": [
"libEGL.so",
- "libGLESv2.so"
+ "libGLESv2.so",
+ "libvulkan.so"
]
}
diff --git a/build/compos/CompOSPayloadApp/Android.bp b/build/compos/CompOSPayloadApp/Android.bp
index c6192b9..04465b3 100644
--- a/build/compos/CompOSPayloadApp/Android.bp
+++ b/build/compos/CompOSPayloadApp/Android.bp
@@ -5,5 +5,6 @@
android_app {
name: "CompOSPayloadApp",
sdk_version: "current",
+ system_ext_specific: true,
apex_available: ["com.android.compos"],
}
diff --git a/guest/forwarder_guest_launcher/debian/service b/guest/forwarder_guest_launcher/debian/service
index 6824c70..ad57a26 100644
--- a/guest/forwarder_guest_launcher/debian/service
+++ b/guest/forwarder_guest_launcher/debian/service
@@ -11,6 +11,8 @@
RestartSec=1
User=root
Group=root
+StandardOutput=journal
+StandardError=journal
[Install]
WantedBy=multi-user.target
diff --git a/guest/shutdown_runner/debian/service b/guest/shutdown_runner/debian/service
index 7188d36..2668930 100644
--- a/guest/shutdown_runner/debian/service
+++ b/guest/shutdown_runner/debian/service
@@ -10,6 +10,8 @@
RestartSec=1
User=root
Group=root
+StandardOutput=journal
+StandardError=journal
[Install]
WantedBy=multi-user.target
diff --git a/libs/libservice_vm_requests/src/client_vm.rs b/libs/libservice_vm_requests/src/client_vm.rs
index 4e54510..8ad10fd 100644
--- a/libs/libservice_vm_requests/src/client_vm.rs
+++ b/libs/libservice_vm_requests/src/client_vm.rs
@@ -25,7 +25,7 @@
use core::result;
use coset::{AsCborValue, CborSerializable, CoseSign, CoseSign1};
use der::{Decode, Encode};
-use diced_open_dice::{DiceArtifacts, HASH_SIZE};
+use diced_open_dice::DiceArtifacts;
use log::{debug, error, info};
use microdroid_kernel_hashes::{HASH_SIZE as KERNEL_HASH_SIZE, OS_HASHES};
use service_vm_comm::{ClientVmAttestationParams, Csr, CsrPayload, RequestProcessingError};
@@ -252,7 +252,7 @@
Ok(false)
}
-fn expected_kernel_authority_hash(service_vm_entry: &Value) -> Result<[u8; HASH_SIZE]> {
+fn expected_kernel_authority_hash(service_vm_entry: &Value) -> Result<Vec<u8>> {
let cose_sign1 = CoseSign1::from_cbor_value(service_vm_entry.clone())?;
let payload = cose_sign1.payload.ok_or_else(|| {
error!("No payload found in the service VM DICE chain entry");
diff --git a/libs/libservice_vm_requests/src/dice.rs b/libs/libservice_vm_requests/src/dice.rs
index ef9d894..ba67450 100644
--- a/libs/libservice_vm_requests/src/dice.rs
+++ b/libs/libservice_vm_requests/src/dice.rs
@@ -19,8 +19,8 @@
use alloc::vec::Vec;
use bssl_avf::{ed25519_verify, Digester, EcKey};
use cbor_util::{
- cbor_value_type, get_label_value, get_label_value_as_bytes, value_to_array,
- value_to_byte_array, value_to_bytes, value_to_map, value_to_num, value_to_text,
+ cbor_value_type, get_label_value, get_label_value_as_bytes, value_to_array, value_to_bytes,
+ value_to_map, value_to_num, value_to_text,
};
use ciborium::value::Value;
use core::cell::OnceCell;
@@ -31,7 +31,7 @@
Algorithm, AsCborValue, CborSerializable, CoseError, CoseKey, CoseSign1, KeyOperation, KeyType,
Label,
};
-use diced_open_dice::{DiceMode, HASH_SIZE};
+use diced_open_dice::DiceMode;
use log::{debug, error, info};
use service_vm_comm::RequestProcessingError;
@@ -288,8 +288,8 @@
pub(crate) struct DiceChainEntryPayload {
pub(crate) subject_public_key: PublicKey,
mode: DiceMode,
- pub(crate) code_hash: [u8; HASH_SIZE],
- pub(crate) authority_hash: [u8; HASH_SIZE],
+ pub(crate) code_hash: Vec<u8>,
+ pub(crate) authority_hash: Vec<u8>,
config_descriptor: ConfigDescriptor,
}
@@ -327,12 +327,12 @@
}
MODE => builder.mode(to_mode(value)?)?,
CODE_HASH => {
- let code_hash = value_to_byte_array(value, "DiceChainEntryPayload code_hash")?;
+ let code_hash = value_to_bytes(value, "DiceChainEntryPayload code_hash")?;
builder.code_hash(code_hash)?;
}
AUTHORITY_HASH => {
let authority_hash =
- value_to_byte_array(value, "DiceChainEntryPayload authority_hash")?;
+ value_to_bytes(value, "DiceChainEntryPayload authority_hash")?;
builder.authority_hash(authority_hash)?;
}
CONFIG_DESC => {
@@ -524,8 +524,8 @@
struct PayloadBuilder {
subject_public_key: OnceCell<PublicKey>,
mode: OnceCell<DiceMode>,
- code_hash: OnceCell<[u8; HASH_SIZE]>,
- authority_hash: OnceCell<[u8; HASH_SIZE]>,
+ code_hash: OnceCell<Vec<u8>>,
+ authority_hash: OnceCell<Vec<u8>>,
config_descriptor: OnceCell<ConfigDescriptor>,
}
@@ -552,11 +552,11 @@
set_once(&self.mode, mode, "mode")
}
- fn code_hash(&mut self, code_hash: [u8; HASH_SIZE]) -> Result<()> {
+ fn code_hash(&mut self, code_hash: Vec<u8>) -> Result<()> {
set_once(&self.code_hash, code_hash, "code_hash")
}
- fn authority_hash(&mut self, authority_hash: [u8; HASH_SIZE]) -> Result<()> {
+ fn authority_hash(&mut self, authority_hash: Vec<u8>) -> Result<()> {
set_once(&self.authority_hash, authority_hash, "authority_hash")
}
@@ -570,7 +570,9 @@
// the Open Profile for DICE spec.
let mode = self.mode.take().unwrap_or(DiceMode::kDiceModeNotInitialized);
let code_hash = take_value(&mut self.code_hash, "code_hash")?;
+ validate_hash_size(code_hash.len(), "code_hash")?;
let authority_hash = take_value(&mut self.authority_hash, "authority_hash")?;
+ validate_hash_size(authority_hash.len(), "authority_hash")?;
let config_descriptor = take_value(&mut self.config_descriptor, "config_descriptor")?;
Ok(DiceChainEntryPayload {
subject_public_key,
@@ -581,3 +583,18 @@
})
}
}
+
+fn validate_hash_size(len: usize, name: &str) -> Result<()> {
+ // According to the Android Profile for DICE specification, SHA-256, SHA-384, and SHA-512
+ // are all acceptable hash algorithms.
+ const ACCEPTABLE_HASH_SIZES: [usize; 3] = [32, 48, 64];
+ if ACCEPTABLE_HASH_SIZES.contains(&len) {
+ Ok(())
+ } else {
+ error!(
+ "Invalid hash size for {}: {}. Acceptable hash sizes are: {:?}",
+ name, len, ACCEPTABLE_HASH_SIZES
+ );
+ Err(RequestProcessingError::InvalidDiceChain)
+ }
+}
diff --git a/libs/libvirtualization_jni/android_system_virtualmachine_VirtualMachine.cpp b/libs/libvirtualization_jni/android_system_virtualmachine_VirtualMachine.cpp
index 67a4716..8452344 100644
--- a/libs/libvirtualization_jni/android_system_virtualmachine_VirtualMachine.cpp
+++ b/libs/libvirtualization_jni/android_system_virtualmachine_VirtualMachine.cpp
@@ -59,28 +59,29 @@
JNIEnv *mEnv;
jobject mProvider;
jmethodID mMid;
- } state;
+ };
- state.mEnv = env;
- state.mProvider = provider;
- state.mMid = mid;
+ auto state = std::make_unique<State>(env, provider, mid);
using RequestFun = int (*)(void *);
RequestFun requestFunc = [](void *param) -> int {
- State *state = reinterpret_cast<State *>(param);
+ State *state = static_cast<State *>(param);
int ownedFd = state->mEnv->CallIntMethod(state->mProvider, state->mMid);
// FD is owned by PFD in Java layer, need to dupe it so that
// ARpcSession_setupPreconnectedClient can take ownership when it calls unique_fd internally
return fcntl(ownedFd, F_DUPFD_CLOEXEC, 0);
};
+ auto paramDeleteFunc = [](void *param) { delete static_cast<State *>(param); };
+
RpcSessionHandle session;
// We need a thread pool to be able to support linkToDeath, or callbacks
// (b/268335700). These threads are currently created eagerly, so we don't
// want too many. The number 1 is chosen after some discussion, and to match
// the server-side default (mMaxThreads on RpcServer).
ARpcSession_setMaxIncomingThreads(session.get(), 1);
- auto client = ARpcSession_setupPreconnectedClient(session.get(), requestFunc, &state);
+ auto client = ARpcSession_setupPreconnectedClient(session.get(), requestFunc, state.release(),
+ paramDeleteFunc);
return AIBinder_toJavaBinder(env, client);
}
diff --git a/tests/backcompat_test/Android.bp b/tests/backcompat_test/Android.bp
index aa1e089..d47487a 100644
--- a/tests/backcompat_test/Android.bp
+++ b/tests/backcompat_test/Android.bp
@@ -14,6 +14,7 @@
"libanyhow",
"liblibc",
"libnix",
+ "librustutils",
"libvmclient",
"liblog_rust",
],
diff --git a/tests/backcompat_test/src/main.rs b/tests/backcompat_test/src/main.rs
index b92049d..eaf3365 100644
--- a/tests/backcompat_test/src/main.rs
+++ b/tests/backcompat_test/src/main.rs
@@ -25,6 +25,7 @@
use anyhow::anyhow;
use anyhow::Context;
use anyhow::Error;
+use anyhow::Result;
use log::info;
use std::fs::read_to_string;
use std::fs::File;
@@ -46,11 +47,11 @@
/// Runs a protected VM and validates it against a golden device tree.
#[test]
-fn test_device_tree_protected_compat() -> Result<(), Error> {
+fn test_device_tree_protected_compat() -> Result<()> {
run_test(true, GOLDEN_DEVICE_TREE_PROTECTED)
}
-fn run_test(protected: bool, golden_dt: &str) -> Result<(), Error> {
+fn run_test(protected: bool, golden_dt: &str) -> Result<()> {
let kernel = Some(open_payload(VMBASE_EXAMPLE_KERNEL_PATH)?);
android_logger::init_once(
android_logger::Config::default()
@@ -142,7 +143,8 @@
{
return Err(anyhow!("failed to execute dtc"));
}
- let dtcompare_res = Command::new("./dtcompare")
+ let mut dtcompare_cmd = Command::new("./dtcompare");
+ dtcompare_cmd
.arg("--dt1")
.arg("dump_dt_golden.dtb")
.arg("--dt2")
@@ -162,12 +164,23 @@
.arg("/chosen/linux,initrd-start")
.arg("--ignore-path-value")
.arg("/chosen/linux,initrd-end")
- .arg("--ignore-path-value")
- .arg("/avf/secretkeeper_public_key")
.arg("--ignore-path")
- .arg("/avf/name")
- .output()
- .context("failed to execute dtcompare")?;
+ .arg("/avf/name");
+ // Check if Secretkeeper is advertised. If not, check the vendor API level. Secretkeeper is
+ // required as of 202504, and if missing, the test should fail.
+ // Otherwise, ignore the fields, as they are not required.
+ if service.isUpdatableVmSupported()? {
+ dtcompare_cmd.arg("--ignore-path-value").arg("/avf/secretkeeper_public_key");
+ } else if vsr_api_level()? >= 202504 {
+ return Err(anyhow!("Secretkeeper support missing on vendor API >= 202504. Secretkeeper needs to be implemented."));
+ } else {
+ dtcompare_cmd
+ .arg("--ignore-path")
+ .arg("/avf/secretkeeper_public_key")
+ .arg("--ignore-path")
+ .arg("/avf/untrusted/defer-rollback-protection");
+ }
+ let dtcompare_res = dtcompare_cmd.output().context("failed to execute dtcompare")?;
if !dtcompare_res.status.success() {
if !Command::new("./dtc_static")
.arg("-I")
@@ -202,7 +215,17 @@
Ok(())
}
-fn open_payload(path: &str) -> Result<ParcelFileDescriptor, Error> {
+fn open_payload(path: &str) -> Result<ParcelFileDescriptor> {
let file = File::open(path).with_context(|| format!("Failed to open VM image {path}"))?;
Ok(ParcelFileDescriptor::new(file))
}
+
+fn vsr_api_level() -> Result<i32> {
+ get_sysprop_i32("ro.vendor.api_level")
+}
+
+fn get_sysprop_i32(prop: &str) -> Result<i32> {
+ let res = rustutils::system_properties::read(prop)?;
+ res.map(|val| val.parse::<i32>().with_context(|| format!("Failed to read {prop}")))
+ .unwrap_or(Ok(-1))
+}