microdroid: apk is mounted via apkdmverity

In microdroid, APK and its idsig is used to dm-verity mount before
zipfuse mounts it into a filesystem.

Bug: 190343842
Test: MicrodroidHostTestCases
Change-Id: Icd48fb823eabc087c0266e46f9b3d302e90fd208
diff --git a/microdroid/init.rc b/microdroid/init.rc
index 2385d8f..4155da3 100644
--- a/microdroid/init.rc
+++ b/microdroid/init.rc
@@ -22,6 +22,8 @@
 
     start ueventd
 
+    # TODO(b/190343842) verify apexes/apk before mounting them.
+
     # Exec apexd in the VM mode to avoid unnecessary overhead of normal mode.
     # (e.g. session management)
     exec - root system -- /system/bin/apexd --vm
@@ -29,6 +31,7 @@
     perform_apex_config
     exec_start derive_sdk
 
+    exec - root system -- /system/bin/apkdmverity /dev/block/by-name/microdroid-apk /dev/block/by-name/microdroid-apk-idsig microdroid-apk
     mkdir /mnt/apk 0755 system system
     start zipfuse
 
diff --git a/microdroid/sepolicy/system/private/apkdmverity.te b/microdroid/sepolicy/system/private/apkdmverity.te
new file mode 100644
index 0000000..c6160be
--- /dev/null
+++ b/microdroid/sepolicy/system/private/apkdmverity.te
@@ -0,0 +1,29 @@
+# apkdmverity is a program that protects a signed APK file using dm-verity.
+
+type apkdmverity, domain, coredomain;
+type apkdmverity_exec, exec_type, file_type, system_file_type;
+
+# allow domain transition from init
+init_daemon_domain(apkdmverity)
+
+# apkdmverity accesses /dev/block/by-name/metadata which points to
+# a /dev/vd* block device file.
+allow apkdmverity block_device:dir r_dir_perms;
+allow apkdmverity block_device:lnk_file r_file_perms;
+allow apkdmverity vd_device:blk_file r_file_perms;
+
+# allow apkdmverity to create dm-verity devices
+allow apkdmverity dm_device:{chr_file blk_file} rw_file_perms;
+# sys_admin is required to access the device-mapper and mount
+allow apkdmverity self:global_capability_class_set sys_admin;
+
+# allow apkdmverity to create loop devices with /dev/loop-control
+allow apkdmverity loop_control_device:chr_file rw_file_perms;
+
+# allow apkdmverity to access loop devices
+allow apkdmverity loop_device:blk_file rw_file_perms;
+allowxperm apkdmverity loop_device:blk_file ioctl {
+  LOOP_SET_STATUS64
+  LOOP_SET_FD
+  LOOP_SET_DIRECT_IO
+};
diff --git a/microdroid/sepolicy/system/private/domain.te b/microdroid/sepolicy/system/private/domain.te
index c451fcf..4a59f73 100644
--- a/microdroid/sepolicy/system/private/domain.te
+++ b/microdroid/sepolicy/system/private/domain.te
@@ -501,7 +501,8 @@
 
 # Only init and otapreopt_chroot should be mounting filesystems on locations
 # labeled system or vendor (/product and /vendor respectively).
-neverallow { domain -init -otapreopt_chroot } { system_file_type vendor_file_type }:dir_file_class_set mounton;
+# In microdroid, zipfuse is allowed mounton /mnt/apk.
+neverallow { domain -init -otapreopt_chroot -zipfuse } { system_file_type vendor_file_type }:dir_file_class_set mounton;
 
 # Only allow init and vendor_init to read/write mm_events properties
 # NOTE: dumpstate is allowed to read any system property
diff --git a/microdroid/sepolicy/system/private/file_contexts b/microdroid/sepolicy/system/private/file_contexts
index 3c75e81..5615e75 100644
--- a/microdroid/sepolicy/system/private/file_contexts
+++ b/microdroid/sepolicy/system/private/file_contexts
@@ -377,6 +377,7 @@
 /system/bin/zipfuse              u:object_r:zipfuse_exec:s0
 /system/bin/microdroid_launcher  u:object_r:microdroid_launcher_exec:s0
 /system/bin/microdroid_manager   u:object_r:microdroid_manager_exec:s0
+/system/bin/apkdmverity          u:object_r:apkdmverity_exec:s0
 
 #############################
 # Vendor files
diff --git a/microdroid/sepolicy/system/private/kernel.te b/microdroid/sepolicy/system/private/kernel.te
index cfec715..2d49445 100644
--- a/microdroid/sepolicy/system/private/kernel.te
+++ b/microdroid/sepolicy/system/private/kernel.te
@@ -32,3 +32,8 @@
 
 allow kernel kmsg_device:chr_file write;
 allow kernel gsid:fd use;
+
+# apkdmverity attaches a loop device to idsig file
+# and the loop device is used by zipfuse later.
+# This requires kernel to use the fd opened by apkdmverity.
+allow kernel apkdmverity:fd use;
diff --git a/microdroid/sepolicy/system/private/zipfuse.te b/microdroid/sepolicy/system/private/zipfuse.te
index 65da9d3..fb7527b 100644
--- a/microdroid/sepolicy/system/private/zipfuse.te
+++ b/microdroid/sepolicy/system/private/zipfuse.te
@@ -17,7 +17,9 @@
 # /dev/block/by-name/*
 allow zipfuse block_device:dir r_dir_perms;
 allow zipfuse block_device:lnk_file r_file_perms;
-allow zipfuse vd_device:blk_file r_file_perms;
+
+# /dev/block/by-name/microdroid-apk is mapped to /dev/block/dm-*
+allow zipfuse dm_device:blk_file r_file_perms;
 
 # allow mounting on /mnt/apk
 allow zipfuse tmpfs:dir mounton;
diff --git a/tests/hostside/java/android/virt/test/MicrodroidTestCase.java b/tests/hostside/java/android/virt/test/MicrodroidTestCase.java
index 7f9b8de..268ee84 100644
--- a/tests/hostside/java/android/virt/test/MicrodroidTestCase.java
+++ b/tests/hostside/java/android/virt/test/MicrodroidTestCase.java
@@ -21,6 +21,7 @@
 import static org.hamcrest.CoreMatchers.not;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeThat;
 
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
@@ -79,9 +80,11 @@
         runOnMicrodroid("echo MicrodroidTest > /data/local/tmp/test.txt");
         assertThat(runOnMicrodroid("cat /data/local/tmp/test.txt"), is("MicrodroidTest"));
 
-        // Check if the APK partition exists
+        // 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 if the APK is mounted using zipfuse
         final String mountEntry = "zipfuse on /mnt/apk type fuse.zipfuse";
@@ -126,7 +129,9 @@
     // Run a shell command on Android
     private String runOnAndroid(String... cmd) throws Exception {
         CommandResult result = getDevice().executeShellV2Command(join(cmd));
-        assertThat(result.getStatus(), is(CommandStatus.SUCCESS));
+        if (result.getStatus() != CommandStatus.SUCCESS) {
+            fail(join(cmd) + " has failed: " + result);
+        }
         return result.getStdout().trim();
     }
 
@@ -142,7 +147,9 @@
         CommandResult result =
                 RunUtil.getDefault()
                         .runTimedCmd(timeout, "adb", "-s", MICRODROID_SERIAL, "shell", join(cmd));
-        assertThat(result.getStatus(), is(CommandStatus.SUCCESS));
+        if (result.getStatus() != CommandStatus.SUCCESS) {
+            fail(join(cmd) + " has failed: " + result);
+        }
         return result.getStdout().trim();
     }
 
@@ -177,21 +184,21 @@
         assertTrue(apkPath.startsWith("package:"));
         apkPath = apkPath.substring("package:".length());
 
+        // Push the idsig file to the device
+        File idsigOnHost = findTestFile(apkName + ".idsig");
+        final String apkIdsigPath = TEST_ROOT + apkName + ".idsig";
+        getDevice().pushFile(idsigOnHost, apkIdsigPath);
+
         // Create payload.json from the gathered data
         JSONObject payloadObject = new JSONObject();
         payloadObject.put("system_apexes", new JSONArray(apexNames));
         payloadObject.put("payload_config_path", "/mnt/apk/" + configPath);
         JSONObject apkObject = new JSONObject();
-        apkObject.put("path", apkPath);
         apkObject.put("name", packageName);
+        apkObject.put("path", apkPath);
+        apkObject.put("idsig_path", apkIdsigPath);
         payloadObject.put("apk", apkObject);
 
-        // Push the idsig file to the device
-        // TODO(b/190343842): pass path to this file to payloadObject
-        // File idsigOnHost = findTestFile(apkFile + ".idsig");
-        // final String testApkIdsig = TEST_ROOT + apkFile + ".idsig";
-        // getDevice().pushFile(idsigOnHost, testApkIdsig);
-
         // Copy the json file to Android
         File payloadJsonOnHost = File.createTempFile("payload", "json");
         FileWriter writer = new FileWriter(payloadJsonOnHost);
diff --git a/zipfuse/zipfuse.rc b/zipfuse/zipfuse.rc
index ccd94b6..1905705 100644
--- a/zipfuse/zipfuse.rc
+++ b/zipfuse/zipfuse.rc
@@ -1,2 +1,2 @@
-service zipfuse /system/bin/zipfuse -o fscontext=u:object_r:zipfusefs:s0,context=u:object_r:system_file:s0 /dev/block/by-name/microdroid-apk /mnt/apk
+service zipfuse /system/bin/zipfuse -o fscontext=u:object_r:zipfusefs:s0,context=u:object_r:system_file:s0 /dev/block/mapper/microdroid-apk /mnt/apk
     disabled