Test: Protected VM fails if images are signed by unknown
Arrange: - prepare VM images signed with a test key Act: - start a protected VM Assert: - a boot process fails due to pubkey mismatch between pvmfw and bootloader Bug: 218934597 Test: atest MicrodroidHostTestCases Change-Id: I05755ddf32839ef531ca9a11b2939bbc251ff1fb
This commit is contained in:
parent
b7983a2683
commit
6afd667daf
|
@ -142,11 +142,16 @@ sh_test_host {
|
|||
],
|
||||
data: [
|
||||
":com.android.virt",
|
||||
"test.com.android.virt.pem",
|
||||
":test.com.android.virt.pem",
|
||||
],
|
||||
test_suites: ["general-tests"],
|
||||
}
|
||||
|
||||
filegroup {
|
||||
name: "test.com.android.virt.pem",
|
||||
srcs: ["test.com.android.virt.pem"],
|
||||
}
|
||||
|
||||
// custom tool to replace bytes in a file
|
||||
python_binary_host {
|
||||
name: "replace_bytes",
|
||||
|
|
|
@ -269,24 +269,34 @@ Result<void> MakePayload(const Config& config, const std::string& metadata_file,
|
|||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
if (argc != 3) {
|
||||
std::cerr << "Usage: " << argv[0] << " <config> <output>\n";
|
||||
if (argc < 3 || argc > 4) {
|
||||
std::cerr << "Usage: " << argv[0] << " [--metadata-only] <config> <output>\n";
|
||||
return 1;
|
||||
}
|
||||
int arg_index = 1;
|
||||
bool metadata_only = false;
|
||||
if (strcmp(argv[arg_index], "--metadata-only") == 0) {
|
||||
metadata_only = true;
|
||||
arg_index++;
|
||||
}
|
||||
|
||||
auto config = LoadConfig(argv[1]);
|
||||
auto config = LoadConfig(argv[arg_index++]);
|
||||
if (!config.ok()) {
|
||||
std::cerr << "bad config: " << config.error() << '\n';
|
||||
return 1;
|
||||
}
|
||||
|
||||
const std::string output_file(argv[2]);
|
||||
const std::string metadata_file = AppendFileName(output_file, "-metadata");
|
||||
const std::string output_file(argv[arg_index++]);
|
||||
const std::string metadata_file =
|
||||
metadata_only ? output_file : AppendFileName(output_file, "-metadata");
|
||||
|
||||
if (const auto res = MakeMetadata(*config, metadata_file); !res.ok()) {
|
||||
std::cerr << res.error() << '\n';
|
||||
return 1;
|
||||
}
|
||||
if (metadata_only) {
|
||||
return 0;
|
||||
}
|
||||
if (const auto res = MakePayload(*config, metadata_file, output_file); !res.ok()) {
|
||||
std::cerr << res.error() << '\n';
|
||||
return 1;
|
||||
|
|
|
@ -91,3 +91,11 @@ android_filesystem {
|
|||
],
|
||||
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"],
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ java_test_host {
|
|||
"general-tests",
|
||||
],
|
||||
libs: [
|
||||
"gson-prebuilt-jar",
|
||||
"tradefed",
|
||||
],
|
||||
static_libs: [
|
||||
|
@ -19,6 +20,31 @@ java_test_host {
|
|||
data: [
|
||||
":MicrodroidTestApp",
|
||||
":microdroid_general_sepolicy.conf",
|
||||
":test.com.android.virt.pem",
|
||||
":test-payload-metadata",
|
||||
],
|
||||
data_native_bins: [
|
||||
"sepolicy-analyze",
|
||||
// For re-sign test
|
||||
"avbtool",
|
||||
"img2simg",
|
||||
"lpmake",
|
||||
"lpunpack",
|
||||
"sign_virt_apex",
|
||||
"simg2img",
|
||||
],
|
||||
// java_test_host doesn't have data_native_libs but jni_libs can be used to put
|
||||
// native modules under ./lib directory.
|
||||
// This works because host tools have rpath (../lib and ./lib).
|
||||
jni_libs: [
|
||||
"libbase",
|
||||
"libc++",
|
||||
"libcrypto_utils",
|
||||
"libcrypto",
|
||||
"libext4_utils",
|
||||
"liblog",
|
||||
"liblp",
|
||||
"libsparse",
|
||||
"libz",
|
||||
],
|
||||
data_native_bins: ["sepolicy-analyze"],
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ import java.util.regex.Pattern;
|
|||
|
||||
public abstract class VirtualizationTestCaseBase extends BaseHostJUnit4Test {
|
||||
protected static final String TEST_ROOT = "/data/local/tmp/virt/";
|
||||
private static final String VIRT_APEX = "/apex/com.android.virt/";
|
||||
protected static final String VIRT_APEX = "/apex/com.android.virt/";
|
||||
private static final int TEST_VM_ADB_PORT = 8000;
|
||||
private static final String MICRODROID_SERIAL = "localhost:" + TEST_VM_ADB_PORT;
|
||||
private static final String INSTANCE_IMG = "instance.img";
|
||||
|
|
|
@ -16,12 +16,14 @@
|
|||
|
||||
package android.virt.test;
|
||||
|
||||
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.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.junit.Assume.assumeTrue;
|
||||
|
||||
import com.android.tradefed.device.DeviceNotAvailableException;
|
||||
import com.android.tradefed.result.TestDescription;
|
||||
|
@ -33,14 +35,23 @@ import com.android.tradefed.util.CommandStatus;
|
|||
import com.android.tradefed.util.FileUtil;
|
||||
import com.android.tradefed.util.RunUtil;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@RunWith(DeviceJUnit4ClassRunner.class)
|
||||
public class MicrodroidTestCase extends VirtualizationTestCaseBase {
|
||||
|
@ -77,6 +88,11 @@ public class MicrodroidTestCase extends VirtualizationTestCaseBase {
|
|||
return 0;
|
||||
}
|
||||
|
||||
private boolean isProtectedVmSupported() throws DeviceNotAvailableException {
|
||||
return getDevice().getBooleanProperty("ro.boot.hypervisor.protected_vm.supported",
|
||||
false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateVmRequiresPermission() throws Exception {
|
||||
// Revoke the MANAGE_VIRTUAL_MACHINE permission for the test app
|
||||
|
@ -98,6 +114,195 @@ public class MicrodroidTestCase extends VirtualizationTestCaseBase {
|
|||
.contains("android.permission.MANAGE_VIRTUAL_MACHINE permission"));
|
||||
}
|
||||
|
||||
// Helper classes for (de)serialization of VM raw configs
|
||||
static class VmRawConfig {
|
||||
String bootloader;
|
||||
List<Disk> disks;
|
||||
int memory_mib;
|
||||
@SerializedName("protected")
|
||||
boolean isProtected;
|
||||
}
|
||||
|
||||
static class Disk {
|
||||
List<Partition> partitions;
|
||||
boolean writable;
|
||||
public void addPartition(String label, String path) {
|
||||
if (partitions == null) {
|
||||
partitions = new ArrayList<Partition>();
|
||||
}
|
||||
Partition partition = new Partition();
|
||||
partition.label = label;
|
||||
partition.path = path;
|
||||
partitions.add(partition);
|
||||
}
|
||||
}
|
||||
|
||||
static class Partition {
|
||||
String label;
|
||||
String path;
|
||||
boolean writable;
|
||||
}
|
||||
|
||||
private String getPathForPackage(String packageName)
|
||||
throws DeviceNotAvailableException {
|
||||
CommandRunner android = new CommandRunner(getDevice());
|
||||
String pathLine = android.run("pm", "path", packageName);
|
||||
assertTrue("package not found", pathLine.startsWith("package:"));
|
||||
return pathLine.substring("package:".length());
|
||||
}
|
||||
|
||||
private void resignVirtApex(File virtApexDir, File signingKey) {
|
||||
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);
|
||||
|
||||
String resignCommand = String.format("sign_virt_apex %s %s",
|
||||
signingKey.getPath(),
|
||||
virtApexDir.getPath());
|
||||
CommandResult result = runUtil.runTimedCmd(
|
||||
20 * 1000,
|
||||
"/bin/bash",
|
||||
"-c",
|
||||
resignCommand);
|
||||
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 String runMicrodroidWithResignedImages(boolean isProtected, boolean daemonize,
|
||||
String consolePath) throws DeviceNotAvailableException, IOException {
|
||||
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));
|
||||
|
||||
File testKey = findTestFile("test.com.android.virt.pem");
|
||||
resignVirtApex(virtApexDir, testKey);
|
||||
|
||||
// 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 prepared on host with the two APEXes and APK
|
||||
final String payloadMetadataPath = TEST_ROOT + "payload-metadata.img";
|
||||
getDevice().pushFile(findTestFile("test-payload-metadata.img"), payloadMetadataPath);
|
||||
|
||||
// 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
|
||||
Gson gson = new Gson();
|
||||
File microdroidConfigFile = new File(virtApexEtcDir, "microdroid.json");
|
||||
VmRawConfig config = gson.fromJson(new FileReader(microdroidConfigFile),
|
||||
VmRawConfig.class);
|
||||
|
||||
// Replace paths so that the config uses re-signed images from TEST_ROOT
|
||||
config.bootloader = config.bootloader.replace(VIRT_APEX, TEST_ROOT);
|
||||
for (Disk disk : config.disks) {
|
||||
for (Partition part : disk.partitions) {
|
||||
part.path = part.path.replace(VIRT_APEX, TEST_ROOT);
|
||||
}
|
||||
}
|
||||
|
||||
// Add partitions to the second disk
|
||||
Disk secondDisk = config.disks.get(1);
|
||||
secondDisk.addPartition("vbmeta",
|
||||
TEST_ROOT + "etc/fs/microdroid_vbmeta_bootconfig.img");
|
||||
secondDisk.addPartition("bootconfig",
|
||||
TEST_ROOT + "etc/microdroid_bootconfig.full_debuggable");
|
||||
secondDisk.addPartition("vm-instance", instanceImgPath);
|
||||
|
||||
// Add payload image disk with partitions:
|
||||
// - payload-metadata
|
||||
// - apexes: com.android.os.statsd, com.android.adbd
|
||||
// - apk and idsig
|
||||
Disk payloadDisk = new Disk();
|
||||
payloadDisk.addPartition("payload-metadata", payloadMetadataPath);
|
||||
String[] apexes = {"com.android.os.statsd", "com.android.adbd"};
|
||||
for (int i = 0; i < apexes.length; i++) {
|
||||
String apexPath = getPathForPackage(apexes[i]);
|
||||
String filename = apexes[i] + ".apex";
|
||||
File localApexFile = new File(virtApexDir, filename);
|
||||
String remoteApexFile = TEST_ROOT + filename;
|
||||
// Since `adb shell vm` can't access apex_data_file, we `adb pull/push` apex files.
|
||||
getDevice().pullFile(apexPath, localApexFile);
|
||||
getDevice().pushFile(localApexFile, remoteApexFile);
|
||||
payloadDisk.addPartition("microdroid-apex-" + i, remoteApexFile);
|
||||
}
|
||||
payloadDisk.addPartition("microdroid-apk", apkPath);
|
||||
payloadDisk.addPartition("microdroid-apk-idsig", idSigPath);
|
||||
config.disks.add(payloadDisk);
|
||||
|
||||
config.isProtected = isProtected;
|
||||
|
||||
// Write updated raw config
|
||||
final String configPath = TEST_ROOT + "raw_config.json";
|
||||
getDevice().pushString(gson.toJson(config), configPath);
|
||||
|
||||
final String logPath = TEST_ROOT + "log";
|
||||
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());
|
||||
String consolePath = TEST_ROOT + "console";
|
||||
// Run VM without --daemonize. It will shut down due to boot failure.
|
||||
runMicrodroidWithResignedImages(/*protected=*/true, /*daemonize=*/false, consolePath);
|
||||
assertThat(getDevice().pullFileContents(consolePath),
|
||||
containsString("pvmfw boot failed"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBootSucceedsWhenNonProtectedVmStartsWithImagesSignedWithDifferentKey()
|
||||
throws Exception {
|
||||
String cid = runMicrodroidWithResignedImages(/*protected=*/false,
|
||||
/*daemonize=*/true, /*consolePath=*/null);
|
||||
// Adb connection to the microdroid means that boot succeeded.
|
||||
adbConnectToMicrodroid(getDevice(), cid);
|
||||
shutdownMicrodroid(getDevice(), cid);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMicrodroidBoots() throws Exception {
|
||||
final String configPath = "assets/vm_config.json"; // path inside the APK
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"_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"
|
||||
}
|
Loading…
Reference in New Issue