594 lines
26 KiB
Java
594 lines
26 KiB
Java
/*
|
|
* Copyright (C) 2021 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package android.virt.test;
|
|
|
|
import static com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestLogData;
|
|
|
|
import static org.hamcrest.CoreMatchers.containsString;
|
|
import static org.hamcrest.CoreMatchers.is;
|
|
import static org.junit.Assert.assertEquals;
|
|
import static org.junit.Assert.assertFalse;
|
|
import static org.junit.Assert.assertNotEquals;
|
|
import static org.junit.Assert.assertThat;
|
|
import static org.junit.Assert.assertTrue;
|
|
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;
|
|
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
|
|
import com.android.tradefed.testtype.junit4.DeviceTestRunOptions;
|
|
import com.android.tradefed.util.CommandResult;
|
|
import com.android.tradefed.util.CommandStatus;
|
|
import com.android.tradefed.util.FileUtil;
|
|
import com.android.tradefed.util.RunUtil;
|
|
import com.android.tradefed.util.xml.AbstractXmlParser;
|
|
|
|
import org.json.JSONArray;
|
|
import org.json.JSONObject;
|
|
import org.junit.After;
|
|
import org.junit.Before;
|
|
import org.junit.Rule;
|
|
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.util.ArrayList;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Optional;
|
|
import java.util.concurrent.Callable;
|
|
import java.util.regex.Matcher;
|
|
import java.util.regex.Pattern;
|
|
|
|
@RunWith(DeviceJUnit4ClassRunner.class)
|
|
public class MicrodroidTestCase extends VirtualizationTestCaseBase {
|
|
private static final String APK_NAME = "MicrodroidTestApp.apk";
|
|
private static final String PACKAGE_NAME = "com.android.microdroid.test";
|
|
|
|
private static final int MIN_MEM_ARM64 = 145;
|
|
private static final int MIN_MEM_X86_64 = 196;
|
|
|
|
// Number of vCPUs and their affinity to host CPUs for testing purpose
|
|
private static final int NUM_VCPUS = 3;
|
|
private static final String CPU_AFFINITY = "0,1,2";
|
|
|
|
@Rule public TestLogData mTestLogs = new TestLogData();
|
|
@Rule public TestName mTestName = new TestName();
|
|
|
|
private int minMemorySize() throws DeviceNotAvailableException {
|
|
CommandRunner android = new CommandRunner(getDevice());
|
|
String abi = android.run("getprop", "ro.product.cpu.abi");
|
|
assertTrue(abi != null && !abi.isEmpty());
|
|
if (abi.startsWith("arm64")) {
|
|
return MIN_MEM_ARM64;
|
|
} else if (abi.startsWith("x86_64")) {
|
|
return MIN_MEM_X86_64;
|
|
}
|
|
fail("Unsupported ABI: " + abi);
|
|
return 0;
|
|
}
|
|
|
|
private boolean isProtectedVmSupported() throws DeviceNotAvailableException {
|
|
return getDevice().getBooleanProperty("ro.boot.hypervisor.protected_vm.supported",
|
|
false);
|
|
}
|
|
|
|
// Wait until logd-init starts. The service is one of the last services that are started in
|
|
// the microdroid boot procedure. Therefore, waiting for the service means that we wait for
|
|
// the boot to complete. TODO: we need a better marker eventually.
|
|
private void waitForLogdInit() {
|
|
tryRunOnMicrodroid("watch -e \"getprop init.svc.logd-reinit | grep '^$'\"");
|
|
}
|
|
|
|
@Test
|
|
public void testCreateVmRequiresPermission() throws Exception {
|
|
// Revoke the MANAGE_VIRTUAL_MACHINE permission for the test app
|
|
CommandRunner android = new CommandRunner(getDevice());
|
|
android.run("pm", "revoke", PACKAGE_NAME, "android.permission.MANAGE_VIRTUAL_MACHINE");
|
|
|
|
// Run MicrodroidTests#connectToVmService test, which should fail
|
|
final DeviceTestRunOptions options = new DeviceTestRunOptions(PACKAGE_NAME)
|
|
.setTestClassName(PACKAGE_NAME + ".MicrodroidTests")
|
|
.setTestMethodName("connectToVmService[protectedVm=false]")
|
|
.setCheckResults(false);
|
|
assertFalse(runDeviceTests(options));
|
|
|
|
Map<TestDescription, TestResult> results = getLastDeviceRunResults().getTestResults();
|
|
assertThat(results.size(), is(1));
|
|
TestResult result = results.values().toArray(new TestResult[0])[0];
|
|
assertTrue("The test should fail with a permission error",
|
|
result.getStackTrace()
|
|
.contains("android.permission.MANAGE_VIRTUAL_MACHINE permission"));
|
|
}
|
|
|
|
private static JSONObject newPartition(String label, String path) {
|
|
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");
|
|
|
|
RunUtil runUtil = new RunUtil();
|
|
// Set the parent dir on the PATH (e.g. <workdir>/bin)
|
|
String separator = System.getProperty("path.separator");
|
|
String path = signVirtApex.getParentFile().getPath() + separator + System.getenv("PATH");
|
|
runUtil.setEnvVariable("PATH", path);
|
|
|
|
List<String> command = new ArrayList<String>();
|
|
command.add("sign_virt_apex");
|
|
for (Map.Entry<String, File> entry : keyOverrides.entrySet()) {
|
|
String filename = entry.getKey();
|
|
File overridingKey = entry.getValue();
|
|
command.add("--key_override " + filename + "=" + overridingKey.getPath());
|
|
}
|
|
command.add(signingKey.getPath());
|
|
command.add(virtApexDir.getPath());
|
|
|
|
CommandResult result = runUtil.runTimedCmd(
|
|
// sign_virt_apex is so slow on CI server that this often times
|
|
// out. Until we can make it fast, use 50s for timeout
|
|
50 * 1000,
|
|
"/bin/bash",
|
|
"-c",
|
|
String.join(" ", command));
|
|
String out = result.getStdout();
|
|
String err = result.getStderr();
|
|
assertEquals(
|
|
"resigning the Virt APEX failed:\n\tout: " + out + "\n\terr: " + err + "\n",
|
|
CommandStatus.SUCCESS, result.getStatus());
|
|
}
|
|
|
|
private static <T> void assertThatEventually(long timeoutMillis, Callable<T> callable,
|
|
org.hamcrest.Matcher<T> matcher) throws Exception {
|
|
long start = System.currentTimeMillis();
|
|
while (true) {
|
|
try {
|
|
assertThat(callable.call(), matcher);
|
|
return;
|
|
} catch (Throwable e) {
|
|
if (System.currentTimeMillis() - start < timeoutMillis) {
|
|
Thread.sleep(500);
|
|
} else {
|
|
throw e;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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 Exception {
|
|
CommandRunner android = new CommandRunner(getDevice());
|
|
|
|
File virtApexDir = FileUtil.createTempDir("virt_apex");
|
|
|
|
// Pull the virt apex's etc/ directory (which contains images and microdroid.json)
|
|
File virtApexEtcDir = new File(virtApexDir, "etc");
|
|
// We need only etc/ directory for images
|
|
assertTrue(virtApexEtcDir.mkdirs());
|
|
assertTrue(getDevice().pullDir(VIRT_APEX + "etc", virtApexEtcDir));
|
|
|
|
resignVirtApex(virtApexDir, key, keyOverrides);
|
|
|
|
// Push back re-signed virt APEX contents and updated microdroid.json
|
|
getDevice().pushDir(virtApexDir, TEST_ROOT);
|
|
|
|
// Create the idsig file for the APK
|
|
final String apkPath = getPathForPackage(PACKAGE_NAME);
|
|
final String idSigPath = TEST_ROOT + "idsig";
|
|
android.run(VIRT_APEX + "bin/vm", "create-idsig", apkPath, idSigPath);
|
|
|
|
// Create the instance image for the VM
|
|
final String instanceImgPath = TEST_ROOT + "instance.img";
|
|
android.run(VIRT_APEX + "bin/vm", "create-partition", "--type instance",
|
|
instanceImgPath, Integer.toString(10 * 1024 * 1024));
|
|
|
|
// payload-metadata is created on device
|
|
final String payloadMetadataPath = TEST_ROOT + "payload-metadata.img";
|
|
|
|
// 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
|
|
// a VM App config.
|
|
//
|
|
// 1. use etc/microdroid.json as base
|
|
// 2. add partitions: bootconfig, vbmeta, instance image
|
|
// 3. add a payload image disk with
|
|
// - payload-metadata
|
|
// - apexes
|
|
// - test apk
|
|
// - its idsig
|
|
|
|
// Load etc/microdroid.json
|
|
File microdroidConfigFile = new File(virtApexEtcDir, "microdroid.json");
|
|
JSONObject config = new JSONObject(FileUtil.readStringFromFile(microdroidConfigFile));
|
|
|
|
// Replace paths so that the config uses re-signed images from TEST_ROOT
|
|
config.put("bootloader", config.getString("bootloader").replace(VIRT_APEX, TEST_ROOT));
|
|
JSONArray disks = config.getJSONArray("disks");
|
|
for (int diskIndex = 0; diskIndex < disks.length(); diskIndex++) {
|
|
JSONObject disk = disks.getJSONObject(diskIndex);
|
|
JSONArray partitions = disk.getJSONArray("partitions");
|
|
for (int partIndex = 0; partIndex < partitions.length(); partIndex++) {
|
|
JSONObject part = partitions.getJSONObject(partIndex);
|
|
part.put("path", part.getString("path").replace(VIRT_APEX, TEST_ROOT));
|
|
}
|
|
}
|
|
|
|
// Add partitions to the second disk
|
|
final String vbmetaPath = TEST_ROOT + "etc/fs/microdroid_vbmeta_bootconfig.img";
|
|
final String bootconfigPath = TEST_ROOT + "etc/microdroid_bootconfig.full_debuggable";
|
|
disks.getJSONObject(1).getJSONArray("partitions")
|
|
.put(newPartition("vbmeta", vbmetaPath))
|
|
.put(newPartition("bootconfig", bootconfigPath))
|
|
.put(newPartition("vm-instance", instanceImgPath));
|
|
|
|
// Add payload image disk with partitions:
|
|
// - payload-metadata
|
|
// - apexes: com.android.os.statsd, com.android.adbd, [sharedlib apex](optional)
|
|
// - apk and idsig
|
|
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));
|
|
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);
|
|
|
|
// Write updated raw config
|
|
final String configPath = TEST_ROOT + "raw_config.json";
|
|
getDevice().pushString(config.toString(), configPath);
|
|
|
|
final String logPath = LOG_PATH;
|
|
final String ret = android.runWithTimeout(
|
|
60 * 1000,
|
|
VIRT_APEX + "bin/vm run",
|
|
daemonize ? "--daemonize" : "",
|
|
(consolePath != null) ? "--console " + consolePath : "",
|
|
"--log " + logPath,
|
|
configPath);
|
|
Pattern pattern = Pattern.compile("with CID (\\d+)");
|
|
Matcher matcher = pattern.matcher(ret);
|
|
assertTrue(matcher.find());
|
|
return matcher.group(1);
|
|
}
|
|
|
|
@Test
|
|
public void testBootFailsWhenProtectedVmStartsWithImagesSignedWithDifferentKey()
|
|
throws Exception {
|
|
assumeTrue(isProtectedVmSupported());
|
|
|
|
File key = findTestFile("test.com.android.virt.pem");
|
|
Map<String, File> keyOverrides = Map.of();
|
|
boolean isProtected = true;
|
|
boolean daemonize = false; // VM should shut down due to boot failure.
|
|
String consolePath = TEST_ROOT + "console";
|
|
runMicrodroidWithResignedImages(key, keyOverrides, isProtected, daemonize, consolePath);
|
|
assertThat(getDevice().pullFileContents(consolePath),
|
|
containsString("pvmfw boot failed"));
|
|
}
|
|
|
|
@Test
|
|
public void testBootSucceedsWhenNonProtectedVmStartsWithImagesSignedWithDifferentKey()
|
|
throws Exception {
|
|
File key = findTestFile("test.com.android.virt.pem");
|
|
Map<String, File> keyOverrides = Map.of();
|
|
boolean isProtected = false;
|
|
boolean daemonize = true;
|
|
String consolePath = TEST_ROOT + "console";
|
|
String cid = runMicrodroidWithResignedImages(key, keyOverrides, isProtected, daemonize,
|
|
consolePath);
|
|
// Adb connection to the microdroid means that boot succeeded.
|
|
adbConnectToMicrodroid(getDevice(), cid);
|
|
shutdownMicrodroid(getDevice(), cid);
|
|
}
|
|
|
|
@Test
|
|
public void testBootFailsWhenBootloaderAndVbMetaAreSignedWithDifferentKeys()
|
|
throws Exception {
|
|
// Sign everything with key1 except vbmeta
|
|
File key = findTestFile("test.com.android.virt.pem");
|
|
File key2 = findTestFile("test2.com.android.virt.pem");
|
|
Map<String, File> keyOverrides = Map.of(
|
|
"microdroid_vbmeta.img", key2);
|
|
boolean isProtected = false; // Not interested in pvwfw
|
|
boolean daemonize = true; // Bootloader fails and enters prompts.
|
|
// To be able to stop it, it should be a daemon.
|
|
String consolePath = TEST_ROOT + "console";
|
|
String cid = runMicrodroidWithResignedImages(key, keyOverrides, isProtected, daemonize,
|
|
consolePath);
|
|
// Wail for a while so that bootloader prints errors to console
|
|
assertThatEventually(10000, () -> getDevice().pullFileContents(consolePath),
|
|
containsString("Public key was rejected"));
|
|
shutdownMicrodroid(getDevice(), cid);
|
|
}
|
|
|
|
@Test
|
|
public void testBootSucceedsWhenBootloaderAndVbmetaHaveSameSigningKeys()
|
|
throws Exception {
|
|
// Sign everything with key1 except bootloader and vbmeta
|
|
File key = findTestFile("test.com.android.virt.pem");
|
|
File key2 = findTestFile("test2.com.android.virt.pem");
|
|
Map<String, File> keyOverrides = Map.of(
|
|
"microdroid_bootloader", key2,
|
|
"microdroid_vbmeta.img", key2,
|
|
"microdroid_vbmeta_bootconfig.img", key2);
|
|
boolean isProtected = false; // Not interested in pvwfw
|
|
boolean daemonize = true; // Bootloader should succeed.
|
|
// To be able to stop it, it should be a daemon.
|
|
String consolePath = TEST_ROOT + "console";
|
|
String cid = runMicrodroidWithResignedImages(key, keyOverrides, isProtected, daemonize,
|
|
consolePath);
|
|
// Adb connection to the microdroid means that boot succeeded.
|
|
adbConnectToMicrodroid(getDevice(), cid);
|
|
shutdownMicrodroid(getDevice(), cid);
|
|
}
|
|
|
|
@Test
|
|
public void testTombstonesAreBeingForwarded() throws Exception {
|
|
// This test requires rooting. Skip on user builds where rooting is impossible.
|
|
final String buildType = getDevice().getProperty("ro.build.type");
|
|
assumeTrue("userdebug".equals(buildType) || "eng".equals(buildType));
|
|
|
|
// Note this test relies on logcat values being printed by tombstone_transmit on
|
|
// and the reeceiver on host (virtualization_service)
|
|
final String configPath = "assets/vm_config.json"; // path inside the APK
|
|
final String cid =
|
|
startMicrodroid(
|
|
getDevice(),
|
|
getBuild(),
|
|
APK_NAME,
|
|
PACKAGE_NAME,
|
|
configPath,
|
|
/* debug */ true,
|
|
minMemorySize(),
|
|
Optional.of(NUM_VCPUS),
|
|
Optional.of(CPU_AFFINITY));
|
|
adbConnectToMicrodroid(getDevice(), cid);
|
|
waitForLogdInit();
|
|
runOnMicrodroid("logcat -c");
|
|
// We need root permission to write to /data/tombstones/
|
|
rootMicrodroid();
|
|
// Write a test tombstone file in /data/tombstones
|
|
runOnMicrodroid("echo -n \'Test tombstone in VM with 34 bytes\'"
|
|
+ "> /data/tombstones/transmit.txt");
|
|
// check if the tombstone have been tranferred from VM
|
|
assertNotEquals(runOnMicrodroid("timeout 15s logcat | grep -m 1 "
|
|
+ "'tombstone_transmit.microdroid:.*data/tombstones/transmit.txt'"),
|
|
"");
|
|
// Confirm that tombstone is received (from host logcat)
|
|
assertNotEquals(runOnHost("adb", "-s", getDevice().getSerialNumber(),
|
|
"logcat", "-d", "-e",
|
|
"Received 34 bytes from guest & wrote to tombstone file.*"),
|
|
"");
|
|
}
|
|
|
|
@Test
|
|
public void testMicrodroidBoots() throws Exception {
|
|
final String configPath = "assets/vm_config.json"; // path inside the APK
|
|
final String cid =
|
|
startMicrodroid(
|
|
getDevice(),
|
|
getBuild(),
|
|
APK_NAME,
|
|
PACKAGE_NAME,
|
|
configPath,
|
|
/* debug */ true,
|
|
minMemorySize(),
|
|
Optional.of(NUM_VCPUS),
|
|
Optional.of(CPU_AFFINITY));
|
|
adbConnectToMicrodroid(getDevice(), cid);
|
|
waitForLogdInit();
|
|
// Test writing to /data partition
|
|
runOnMicrodroid("echo MicrodroidTest > /data/local/tmp/test.txt");
|
|
assertThat(runOnMicrodroid("cat /data/local/tmp/test.txt"), is("MicrodroidTest"));
|
|
|
|
// Check if the APK & its idsig partitions exist
|
|
final String apkPartition = "/dev/block/by-name/microdroid-apk";
|
|
assertThat(runOnMicrodroid("ls", apkPartition), is(apkPartition));
|
|
final String apkIdsigPartition = "/dev/block/by-name/microdroid-apk-idsig";
|
|
assertThat(runOnMicrodroid("ls", apkIdsigPartition), is(apkIdsigPartition));
|
|
// Check the vm-instance partition as well
|
|
final String vmInstancePartition = "/dev/block/by-name/vm-instance";
|
|
assertThat(runOnMicrodroid("ls", vmInstancePartition), is(vmInstancePartition));
|
|
|
|
// Check if the native library in the APK is has correct filesystem info
|
|
final String[] abis = runOnMicrodroid("getprop", "ro.product.cpu.abilist").split(",");
|
|
assertThat(abis.length, is(1));
|
|
final String testLib = "/mnt/apk/lib/" + abis[0] + "/MicrodroidTestNativeLib.so";
|
|
final String label = "u:object_r:system_file:s0";
|
|
assertThat(runOnMicrodroid("ls", "-Z", testLib), is(label + " " + testLib));
|
|
|
|
// Check that no denials have happened so far
|
|
assertThat(runOnMicrodroid("logcat -d -e 'avc:[[:space:]]{1,2}denied'"), is(""));
|
|
|
|
assertThat(runOnMicrodroid("cat /proc/cpuinfo | grep processor | wc -l"),
|
|
is(Integer.toString(NUM_VCPUS)));
|
|
|
|
// Check that selinux is enabled
|
|
assertThat(runOnMicrodroid("getenforce"), is("Enforcing"));
|
|
|
|
// TODO(b/176805428): adb is broken for nested VM
|
|
if (!isCuttlefish()) {
|
|
// Check neverallow rules on microdroid
|
|
File policyFile = FileUtil.createTempFile("microdroid_sepolicy", "");
|
|
pullMicrodroidFile("/sys/fs/selinux/policy", policyFile);
|
|
|
|
File generalPolicyConfFile = findTestFile("microdroid_general_sepolicy.conf");
|
|
File sepolicyAnalyzeBin = findTestFile("sepolicy-analyze");
|
|
|
|
CommandResult result =
|
|
RunUtil.getDefault()
|
|
.runTimedCmd(
|
|
10000,
|
|
sepolicyAnalyzeBin.getPath(),
|
|
policyFile.getPath(),
|
|
"neverallow",
|
|
"-w",
|
|
"-f",
|
|
generalPolicyConfFile.getPath());
|
|
assertEquals(
|
|
"neverallow check failed: " + result.getStderr().trim(),
|
|
result.getStatus(),
|
|
CommandStatus.SUCCESS);
|
|
}
|
|
|
|
shutdownMicrodroid(getDevice(), cid);
|
|
}
|
|
|
|
@Before
|
|
public void setUp() throws Exception {
|
|
testIfDeviceIsCapable(getDevice());
|
|
|
|
prepareVirtualizationTestSetup(getDevice());
|
|
|
|
getDevice().installPackage(findTestFile(APK_NAME), /* reinstall */ false);
|
|
|
|
// clear the log
|
|
getDevice().executeShellV2Command("logcat -c");
|
|
}
|
|
|
|
@After
|
|
public void shutdown() throws Exception {
|
|
cleanUpVirtualizationTestSetup(getDevice());
|
|
|
|
archiveLogThenDelete(mTestLogs, getDevice(), LOG_PATH,
|
|
"vm.log-" + mTestName.getMethodName());
|
|
|
|
getDevice().uninstallPackage(PACKAGE_NAME);
|
|
}
|
|
}
|