Merge tag 'android-security-13.0.0_r8' into int/13/fp3
Android Security 13.0.0 Release 8 (10286630)
* tag 'android-security-13.0.0_r8':
Set avb_hash_algorithm=sha256 for system & vendor img
Change-Id: Ibe61f576819644274f0ff4440c7cbe5e3b085527
diff --git a/.prebuilt_info/prebuilt_info_pvmfw_pvmfw_img.asciipb b/.prebuilt_info/prebuilt_info_pvmfw_pvmfw_img.asciipb
index ea17dfc..d54adb6 100644
--- a/.prebuilt_info/prebuilt_info_pvmfw_pvmfw_img.asciipb
+++ b/.prebuilt_info/prebuilt_info_pvmfw_pvmfw_img.asciipb
@@ -1,6 +1,6 @@
drops {
android_build_drop {
- build_id: "8597076"
+ build_id: "9106195"
target: "u-boot_pvmfw"
source_file: "pvmfw.img"
}
@@ -8,5 +8,6 @@
version: ""
version_group: ""
git_project: "platform/packages/modules/Virtualization"
- git_branch: "master"
+ git_branch: "tm-qpr-dev"
+ transform: TRANSFORM_NONE
}
diff --git a/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java b/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
index 0e660f1..b18fbae 100644
--- a/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
+++ b/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
@@ -125,6 +125,11 @@
ITestDevice androidDevice = testInfo.getDevice();
sAndroid = new CommandRunner(androidDevice);
+ if (isCuttlefish(androidDevice)) {
+ sAssumptionFailed = true;
+ return;
+ }
+
try {
testIfDeviceIsCapable(androidDevice);
} catch (AssumptionViolatedException e) {
diff --git a/pvmfw/pvmfw.img b/pvmfw/pvmfw.img
index 9afb092..12e4c70 100644
--- a/pvmfw/pvmfw.img
+++ b/pvmfw/pvmfw.img
Binary files differ
diff --git a/tests/Android.bp b/tests/Android.bp
index 2c36a62..71a73f7 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -90,11 +90,3 @@
],
type: "cpio",
}
-
-genrule {
- name: "test-payload-metadata",
- tools: ["mk_payload"],
- cmd: "$(location mk_payload) --metadata-only $(in) $(out)",
- srcs: ["test-payload-metadata-config.json"],
- out: ["test-payload-metadata.img"],
-}
diff --git a/tests/hostside/Android.bp b/tests/hostside/Android.bp
index dfc2f2b..a7852d0 100644
--- a/tests/hostside/Android.bp
+++ b/tests/hostside/Android.bp
@@ -21,9 +21,6 @@
":microdroid_general_sepolicy.conf",
":test.com.android.virt.pem",
":test2.com.android.virt.pem",
- ":test-payload-metadata",
- ":com.android.adbd{.apex}",
- ":com.android.os.statsd{.apex}",
],
data_native_bins: [
"sepolicy-analyze",
@@ -32,6 +29,7 @@
"img2simg",
"lpmake",
"lpunpack",
+ "mk_payload",
"sign_virt_apex",
"simg2img",
],
diff --git a/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java b/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java
index 440ae18..8e2d0ee 100644
--- a/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java
+++ b/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java
@@ -399,4 +399,17 @@
// Check if it actually booted by reading a sysprop.
assertThat(runOnMicrodroid("getprop", "ro.hardware"), is("microdroid"));
}
+
+ protected boolean isCuttlefish() throws Exception {
+ return isCuttlefish(getDevice());
+ }
+
+ protected static boolean isCuttlefish(ITestDevice device) throws Exception {
+ String productName = device.getProperty("ro.product.name");
+ return (null != productName)
+ && (productName.startsWith("aosp_cf_x86")
+ || productName.startsWith("aosp_cf_arm")
+ || productName.startsWith("cf_x86")
+ || productName.startsWith("cf_arm"));
+ }
}
diff --git a/tests/hostside/java/android/virt/test/MicrodroidTestCase.java b/tests/hostside/java/android/virt/test/MicrodroidTestCase.java
index 67f48a3..bcf0a0d 100644
--- a/tests/hostside/java/android/virt/test/MicrodroidTestCase.java
+++ b/tests/hostside/java/android/virt/test/MicrodroidTestCase.java
@@ -28,6 +28,8 @@
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
+import static java.util.stream.Collectors.toList;
+
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.result.TestDescription;
import com.android.tradefed.result.TestResult;
@@ -37,9 +39,9 @@
import com.android.tradefed.util.CommandStatus;
import com.android.tradefed.util.FileUtil;
import com.android.tradefed.util.RunUtil;
+import com.android.tradefed.util.xml.AbstractXmlParser;
import org.json.JSONArray;
-import org.json.JSONException;
import org.json.JSONObject;
import org.junit.After;
import org.junit.Before;
@@ -47,9 +49,11 @@
import org.junit.Test;
import org.junit.rules.TestName;
import org.junit.runner.RunWith;
+import org.xml.sax.Attributes;
+import org.xml.sax.helpers.DefaultHandler;
+import java.io.ByteArrayInputStream;
import java.io.File;
-import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -73,16 +77,6 @@
@Rule public TestLogData mTestLogs = new TestLogData();
@Rule public TestName mTestName = new TestName();
- // TODO(b/176805428): remove this
- private boolean isCuttlefish() throws Exception {
- String productName = getDevice().getProperty("ro.product.name");
- return (null != productName)
- && (productName.startsWith("aosp_cf_x86")
- || productName.startsWith("aosp_cf_arm")
- || productName.startsWith("cf_x86")
- || productName.startsWith("cf_arm"));
- }
-
private int minMemorySize() throws DeviceNotAvailableException {
CommandRunner android = new CommandRunner(getDevice());
String abi = android.run("getprop", "ro.product.cpu.abi");
@@ -133,6 +127,47 @@
return new JSONObject(Map.of("label", label, "path", path));
}
+ private void createPayloadMetadata(List<ActiveApexInfo> apexes, File payloadMetadata)
+ throws Exception {
+ // mk_payload's config
+ File configFile = new File(payloadMetadata.getParentFile(), "payload_config.json");
+ JSONObject config = new JSONObject();
+ config.put("apk",
+ new JSONObject(Map.of("name", "microdroid-apk", "path", "", "idsig_path", "")));
+ config.put("payload_config_path", "/mnt/apk/assets/vm_config.json");
+ config.put("apexes",
+ new JSONArray(
+ apexes.stream()
+ .map(apex -> new JSONObject(Map.of("name", apex.name, "path", "")))
+ .collect(toList())));
+ FileUtil.writeToFile(config.toString(), configFile);
+
+ File mkPayload = findTestFile("mk_payload");
+ RunUtil runUtil = new RunUtil();
+ // Set the parent dir on the PATH (e.g. <workdir>/bin)
+ String separator = System.getProperty("path.separator");
+ String path = mkPayload.getParentFile().getPath() + separator + System.getenv("PATH");
+ runUtil.setEnvVariable("PATH", path);
+
+ List<String> command = new ArrayList<String>();
+ command.add("mk_payload");
+ command.add("--metadata-only");
+ command.add(configFile.toString());
+ command.add(payloadMetadata.toString());
+
+ CommandResult result = runUtil.runTimedCmd(
+ // mk_payload should run fast enough
+ 5 * 1000,
+ "/bin/bash",
+ "-c",
+ String.join(" ", command));
+ String out = result.getStdout();
+ String err = result.getStderr();
+ assertEquals(
+ "creating payload metadata failed:\n\tout: " + out + "\n\terr: " + err + "\n",
+ CommandStatus.SUCCESS, result.getStatus());
+ }
+
private void resignVirtApex(File virtApexDir, File signingKey, Map<String, File> keyOverrides) {
File signVirtApex = findTestFile("sign_virt_apex");
@@ -183,9 +218,62 @@
}
}
+ static class ActiveApexInfo {
+ public String name;
+ public String path;
+ public boolean provideSharedApexLibs;
+ ActiveApexInfo(String name, String path, boolean provideSharedApexLibs) {
+ this.name = name;
+ this.path = path;
+ this.provideSharedApexLibs = provideSharedApexLibs;
+ }
+ }
+
+ static class ActiveApexInfoList {
+ private List<ActiveApexInfo> mList;
+ ActiveApexInfoList(List<ActiveApexInfo> list) {
+ this.mList = list;
+ }
+ ActiveApexInfo get(String apexName) {
+ for (ActiveApexInfo info: mList) {
+ if (info.name.equals(apexName)) {
+ return info;
+ }
+ }
+ return null;
+ }
+ List<ActiveApexInfo> getSharedLibApexes() {
+ return mList.stream().filter(info -> info.provideSharedApexLibs).collect(toList());
+ }
+ }
+
+ private ActiveApexInfoList getActiveApexInfoList() throws Exception {
+ String apexInfoListXml = getDevice().pullFileContents("/apex/apex-info-list.xml");
+ List<ActiveApexInfo> list = new ArrayList<>();
+ new AbstractXmlParser() {
+ @Override
+ protected DefaultHandler createXmlHandler() {
+ return new DefaultHandler() {
+ @Override
+ public void startElement(String uri, String localName, String qName,
+ Attributes attributes) {
+ if (localName.equals("apex-info")
+ && attributes.getValue("isActive").equals("true")) {
+ String name = attributes.getValue("moduleName");
+ String path = attributes.getValue("modulePath");
+ String sharedApex = attributes.getValue("provideSharedApexLibs");
+ list.add(new ActiveApexInfo(name, path, "true".equals(sharedApex)));
+ }
+ }
+ };
+ }
+ }.parse(new ByteArrayInputStream(apexInfoListXml.getBytes()));
+ return new ActiveApexInfoList(list);
+ }
+
private String runMicrodroidWithResignedImages(File key, Map<String, File> keyOverrides,
boolean isProtected, boolean daemonize, String consolePath)
- throws DeviceNotAvailableException, IOException, JSONException {
+ throws Exception {
CommandRunner android = new CommandRunner(getDevice());
File virtApexDir = FileUtil.createTempDir("virt_apex");
@@ -211,15 +299,11 @@
android.run(VIRT_APEX + "bin/vm", "create-partition", "--type instance",
instanceImgPath, Integer.toString(10 * 1024 * 1024));
- // payload-metadata is prepared on host with the two APEXes and APK
+ // payload-metadata is created on device
final String payloadMetadataPath = TEST_ROOT + "payload-metadata.img";
- getDevice().pushFile(findTestFile("test-payload-metadata.img"), payloadMetadataPath);
- // push APEXes required for the VM.
- final String statsdApexPath = TEST_ROOT + "com.android.os.statsd.apex";
- final String adbdApexPath = TEST_ROOT + "com.android.adbd.apex";
- getDevice().pushFile(findTestFile("com.android.os.statsd.apex"), statsdApexPath);
- getDevice().pushFile(findTestFile("com.android.adbd.apex"), adbdApexPath);
+ // Load /apex/apex-info-list.xml to get paths to APEXes required for the VM.
+ ActiveApexInfoList list = getActiveApexInfoList();
// Since Java APP can't start a VM with a custom image, here, we start a VM using `vm run`
// command with a VM Raw config which is equiv. to what virtualizationservice creates with
@@ -259,14 +343,28 @@
// Add payload image disk with partitions:
// - payload-metadata
- // - apexes: com.android.os.statsd, com.android.adbd
+ // - apexes: com.android.os.statsd, com.android.adbd, [sharedlib apex](optional)
// - apk and idsig
- disks.put(new JSONObject().put("writable", false).put("partitions", new JSONArray()
- .put(newPartition("payload-metadata", payloadMetadataPath))
- .put(newPartition("microdroid-apex-0", statsdApexPath))
- .put(newPartition("microdroid-apex-1", adbdApexPath))
+ List<ActiveApexInfo> apexesForVm = new ArrayList<>();
+ apexesForVm.add(list.get("com.android.os.statsd"));
+ apexesForVm.add(list.get("com.android.adbd"));
+ apexesForVm.addAll(list.getSharedLibApexes());
+
+ final JSONArray partitions = new JSONArray();
+ partitions.put(newPartition("payload-metadata", payloadMetadataPath));
+ int apexIndex = 0;
+ for (ActiveApexInfo apex : apexesForVm) {
+ partitions.put(
+ newPartition(String.format("microdroid-apex-%d", apexIndex++), apex.path));
+ }
+ partitions
.put(newPartition("microdroid-apk", apkPath))
- .put(newPartition("microdroid-apk-idsig", idSigPath))));
+ .put(newPartition("microdroid-apk-idsig", idSigPath));
+ disks.put(new JSONObject().put("writable", false).put("partitions", partitions));
+
+ final File localPayloadMetadata = new File(virtApexDir, "payload-metadata.img");
+ createPayloadMetadata(apexesForVm, localPayloadMetadata);
+ getDevice().pushFile(localPayloadMetadata, payloadMetadataPath);
config.put("protected", isProtected);
diff --git a/tests/test-payload-metadata-config.json b/tests/test-payload-metadata-config.json
deleted file mode 100644
index 3c56e5f..0000000
--- a/tests/test-payload-metadata-config.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
- "_comment": "This file is to create a payload-metadata partition for payload.img which is for MicrodroidTestApp to run with assets/vm_config.json",
- "apexes": [
- {
- "name": "com.android.os.statsd",
- "path": ""
- },
- {
- "name": "com.android.adbd",
- "path": ""
- }
- ],
- "apk": {
- "name": "microdroid-apk",
- "path": "",
- "idsig_path": ""
- },
- "payload_config_path": "/mnt/apk/assets/vm_config.json"
-}
\ No newline at end of file
diff --git a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
index f7261d3..df26ebe 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -71,6 +71,7 @@
@Rule public Timeout globalTimeout = Timeout.seconds(300);
private static final String KERNEL_VERSION = SystemProperties.get("ro.kernel.version");
+ private static final String PRODUCT_NAME = SystemProperties.get("ro.product.name");
private static class Inner {
public boolean mProtectedVm;
@@ -143,6 +144,14 @@
mInner.mVm.delete();
}
+ private boolean isCuttlefish() {
+ return (null != PRODUCT_NAME)
+ && (PRODUCT_NAME.startsWith("aosp_cf_x86")
+ || PRODUCT_NAME.startsWith("aosp_cf_arm")
+ || PRODUCT_NAME.startsWith("cf_x86")
+ || PRODUCT_NAME.startsWith("cf_arm"));
+ }
+
private abstract static class VmEventListener implements VirtualMachineCallback {
private ExecutorService mExecutorService = Executors.newSingleThreadExecutor();
@@ -361,6 +370,7 @@
.withMessage("SKip on 5.4 kernel. b/218303240")
.that(KERNEL_VERSION)
.isNotEqualTo("5.4");
+ assume().withMessage("Skip on CF. Too Slow. b/257270529").that(isCuttlefish()).isFalse();
VmCdis first_boot_cdis = launchVmAndGetCdis("test_vm");
VmCdis second_boot_cdis = launchVmAndGetCdis("test_vm");
@@ -509,6 +519,8 @@
@Test
public void bootFailsWhenMicrodroidDataIsCompromised()
throws VirtualMachineException, InterruptedException, IOException {
+ assume().withMessage("Skip on CF. Too Slow. b/257270529").that(isCuttlefish()).isFalse();
+
assertThatBootFailsAfterCompromisingPartition(MICRODROID_PARTITION_UUID);
}
diff --git a/virtualizationservice/Android.bp b/virtualizationservice/Android.bp
index 7a8da96..8d27325 100644
--- a/virtualizationservice/Android.bp
+++ b/virtualizationservice/Android.bp
@@ -67,5 +67,8 @@
rust_test {
name: "virtualizationservice_device_test",
defaults: ["virtualizationservice_defaults"],
+ rustlibs: [
+ "libtempfile",
+ ],
test_suites: ["general-tests"],
}
diff --git a/virtualizationservice/src/payload.rs b/virtualizationservice/src/payload.rs
index 7b8cb7f..cd80efd 100644
--- a/virtualizationservice/src/payload.rs
+++ b/virtualizationservice/src/payload.rs
@@ -26,7 +26,9 @@
use microdroid_metadata::{ApexPayload, ApkPayload, Metadata};
use microdroid_payload_config::{ApexConfig, VmPayloadConfig};
use once_cell::sync::OnceCell;
-use packagemanager_aidl::aidl::android::content::pm::IPackageManagerNative::IPackageManagerNative;
+use packagemanager_aidl::aidl::android::content::pm::{
+ IPackageManagerNative::IPackageManagerNative, StagedApexInfo::StagedApexInfo,
+};
use regex::Regex;
use serde::Deserialize;
use serde_xml_rs::from_reader;
@@ -47,7 +49,7 @@
const PACKAGE_MANAGER_NATIVE_SERVICE: &str = "package_native";
/// Represents the list of APEXes
-#[derive(Clone, Debug, Deserialize)]
+#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
struct ApexInfoList {
#[serde(rename = "apex-info")]
list: Vec<ApexInfo>,
@@ -57,6 +59,8 @@
struct ApexInfo {
#[serde(rename = "moduleName")]
name: String,
+ #[serde(rename = "versionCode")]
+ version: u64,
#[serde(rename = "modulePath")]
path: PathBuf,
@@ -100,6 +104,40 @@
Ok(apex_info_list)
})
}
+
+ // Override apex info with the staged one
+ fn override_staged_apex(&mut self, staged_apex_info: &StagedApexInfo) -> Result<()> {
+ let mut need_to_add: Option<ApexInfo> = None;
+ for apex_info in self.list.iter_mut() {
+ if staged_apex_info.moduleName == apex_info.name {
+ if apex_info.is_active && apex_info.is_factory {
+ // Copy the entry to the end as factory/non-active after the loop
+ // to keep the factory version. Typically this step is unncessary,
+ // but some apexes (like sharedlibs) need to be kept even if it's inactive.
+ need_to_add.replace(ApexInfo { is_active: false, ..apex_info.clone() });
+ // And make this one as non-factory. Note that this one is still active
+ // and overridden right below.
+ apex_info.is_factory = false;
+ }
+ // Active one is overridden with the staged one.
+ if apex_info.is_active {
+ apex_info.version = staged_apex_info.versionCode as u64;
+ apex_info.path = PathBuf::from(&staged_apex_info.diskImagePath);
+ apex_info.has_classpath_jar = staged_apex_info.hasClassPathJars;
+ apex_info.last_update_seconds = last_updated(&apex_info.path)?;
+ }
+ }
+ }
+ if let Some(info) = need_to_add {
+ self.list.push(info);
+ }
+ Ok(())
+ }
+}
+
+fn last_updated<P: AsRef<Path>>(path: P) -> Result<u64> {
+ let metadata = metadata(path)?;
+ Ok(metadata.modified()?.duration_since(SystemTime::UNIX_EPOCH)?.as_secs())
}
impl ApexInfo {
@@ -134,18 +172,13 @@
let pm =
wait_for_interface::<dyn IPackageManagerNative>(PACKAGE_MANAGER_NATIVE_SERVICE)
.context("Failed to get service when prefer_staged is set.")?;
- let staged = pm.getStagedApexModuleNames()?;
- for apex_info in list.list.iter_mut() {
- if staged.contains(&apex_info.name) {
- if let Some(staged_apex_info) = pm.getStagedApexInfo(&apex_info.name)? {
- apex_info.path = PathBuf::from(staged_apex_info.diskImagePath);
- apex_info.has_classpath_jar = staged_apex_info.hasClassPathJars;
- let metadata = metadata(&apex_info.path)?;
- apex_info.last_update_seconds =
- metadata.modified()?.duration_since(SystemTime::UNIX_EPOCH)?.as_secs();
- // by definition, staged apex can't be a factory apex.
- apex_info.is_factory = false;
- }
+ let staged =
+ pm.getStagedApexModuleNames().context("getStagedApexModuleNames failed")?;
+ for name in staged {
+ if let Some(staged_apex_info) =
+ pm.getStagedApexInfo(&name).context("getStagedApexInfo failed")?
+ {
+ list.override_staged_apex(&staged_apex_info)?;
}
}
}
@@ -229,8 +262,13 @@
let apex_list = pm.get_apex_list(vm_payload_config.prefer_staged)?;
// collect APEXes from config
- let apex_infos =
+ let mut apex_infos =
collect_apex_infos(&apex_list, &vm_payload_config.apexes, app_config.debugLevel);
+
+ // Pass sorted list of apexes. Sorting key shouldn't use `path` because it will change after
+ // reboot with prefer_staged. `last_update_seconds` is added to distinguish "samegrade"
+ // update.
+ apex_infos.sort_by_key(|info| (&info.name, &info.version, &info.last_update_seconds));
info!("Microdroid payload APEXes: {:?}", apex_infos.iter().map(|ai| &ai.name));
let metadata_file =
@@ -391,6 +429,7 @@
#[cfg(test)]
mod tests {
use super::*;
+ use tempfile::NamedTempFile;
#[test]
fn test_find_apex_names_in_classpath() {
@@ -529,4 +568,90 @@
]
);
}
+
+ #[test]
+ fn test_prefer_staged_apex_with_factory_active_apex() {
+ let single_apex = ApexInfo {
+ name: "foo".to_string(),
+ version: 1,
+ path: PathBuf::from("foo.apex"),
+ is_factory: true,
+ is_active: true,
+ ..Default::default()
+ };
+ let mut apex_info_list = ApexInfoList { list: vec![single_apex.clone()] };
+
+ let staged = NamedTempFile::new().unwrap();
+ apex_info_list
+ .override_staged_apex(&StagedApexInfo {
+ moduleName: "foo".to_string(),
+ versionCode: 2,
+ diskImagePath: staged.path().to_string_lossy().to_string(),
+ ..Default::default()
+ })
+ .expect("should be ok");
+
+ assert_eq!(
+ apex_info_list,
+ ApexInfoList {
+ list: vec![
+ ApexInfo {
+ version: 2,
+ is_factory: false,
+ path: staged.path().to_owned(),
+ last_update_seconds: last_updated(staged.path()).unwrap(),
+ ..single_apex.clone()
+ },
+ ApexInfo { is_active: false, ..single_apex },
+ ],
+ }
+ );
+ }
+
+ #[test]
+ fn test_prefer_staged_apex_with_factory_and_inactive_apex() {
+ let factory_apex = ApexInfo {
+ name: "foo".to_string(),
+ version: 1,
+ path: PathBuf::from("foo.apex"),
+ is_factory: true,
+ ..Default::default()
+ };
+ let active_apex = ApexInfo {
+ name: "foo".to_string(),
+ version: 2,
+ path: PathBuf::from("foo.downloaded.apex"),
+ is_active: true,
+ ..Default::default()
+ };
+ let mut apex_info_list =
+ ApexInfoList { list: vec![factory_apex.clone(), active_apex.clone()] };
+
+ let staged = NamedTempFile::new().unwrap();
+ apex_info_list
+ .override_staged_apex(&StagedApexInfo {
+ moduleName: "foo".to_string(),
+ versionCode: 3,
+ diskImagePath: staged.path().to_string_lossy().to_string(),
+ ..Default::default()
+ })
+ .expect("should be ok");
+
+ assert_eq!(
+ apex_info_list,
+ ApexInfoList {
+ list: vec![
+ // factory apex isn't touched
+ factory_apex,
+ // update active one
+ ApexInfo {
+ version: 3,
+ path: staged.path().to_owned(),
+ last_update_seconds: last_updated(staged.path()).unwrap(),
+ ..active_apex
+ },
+ ],
+ }
+ );
+ }
}