Add support for encrypted storage expansion

Capability to configure the encrypted storage size
Partition resizing to the required size upon boot
New unit tests to validate this functionality

Bug: 381067202
Test: atest MicrodroidTests
Change-Id: I6f5737ee601e7c511bdd316b180bf50e3d102ab1
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 e4a3ff6..6fd0885 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -129,6 +129,7 @@
     private static final String TEST_APP_PACKAGE_NAME = "com.android.microdroid.test";
     private static final String VM_ATTESTATION_PAYLOAD_PATH = "libvm_attestation_test_payload.so";
     private static final String VM_ATTESTATION_MESSAGE = "Hello RKP from AVF!";
+    private static final long TOLERANCE_BYTES = 400_000;
     private static final int ENCRYPTED_STORAGE_BYTES = 4_000_000;
 
     private static final String RELAXED_ROLLBACK_PROTECTION_SCHEME_TEST_PACKAGE_NAME =
@@ -741,11 +742,6 @@
         // so in the API spec.
         assertConfigCompatible(baseline, newBaselineBuilder().setApkPath("/different")).isTrue();
 
-        // Changes that are currently incompatible for ease of implementation, but this might change
-        // in the future.
-        assertConfigCompatible(baseline, newBaselineBuilder().setEncryptedStorageBytes(100_000))
-                .isFalse();
-
         VirtualMachineConfig.Builder debuggableBuilder =
                 newBaselineBuilder().setDebugLevel(DEBUG_LEVEL_FULL);
         VirtualMachineConfig debuggable = debuggableBuilder.build();
@@ -1866,6 +1862,169 @@
         assertThat(testResults.mFileContent).isEqualTo(EXAMPLE_STRING);
     }
 
+    @Test
+    @CddTest
+    public void encryptedStorageSupportsExpansion() throws Exception {
+        assumeSupportedDevice();
+
+        VirtualMachineConfig config =
+                newVmConfigBuilderWithPayloadBinary("MicrodroidTestNativeLib.so")
+                        .setEncryptedStorageBytes(ENCRYPTED_STORAGE_BYTES)
+                        .build();
+
+        VirtualMachine vm = forceCreateNewVirtualMachine("test_vm", config);
+        TestResults testResults =
+                runVmTestService(
+                        TAG,
+                        vm,
+                        (ts, tr) -> {
+                            tr.mEncryptedStorageSize = ts.getEncryptedStorageSize();
+                        });
+        testResults.assertNoException();
+        assertThat(testResults.mEncryptedStorageSize)
+            .isWithin(TOLERANCE_BYTES)
+            .of(ENCRYPTED_STORAGE_BYTES);
+
+        // Re-run the VM with more storage size & verify the file persisted.
+        // Note, the previous `runVmTestService` stopped the VM
+        config = newVmConfigBuilderWithPayloadBinary("MicrodroidTestNativeLib.so")
+                    .setEncryptedStorageBytes(ENCRYPTED_STORAGE_BYTES * 2)
+                    .build();
+        vm.setConfig(config);
+        assertThat(vm.getConfig().getEncryptedStorageBytes())
+            .isEqualTo(ENCRYPTED_STORAGE_BYTES * 2);
+
+        testResults =
+                runVmTestService(
+                        TAG,
+                        vm,
+                        (ts, tr) -> {
+                            tr.mEncryptedStorageSize = ts.getEncryptedStorageSize();
+                        });
+        testResults.assertNoException();
+        assertThat(testResults.mEncryptedStorageSize)
+            .isWithin(TOLERANCE_BYTES)
+            .of(ENCRYPTED_STORAGE_BYTES * 2);
+    }
+
+    @Test
+    @CddTest
+    public void encryptedStorageExpansionIsPersistent() throws Exception {
+        assumeSupportedDevice();
+
+        VirtualMachineConfig config =
+                newVmConfigBuilderWithPayloadBinary("MicrodroidTestNativeLib.so")
+                        .setEncryptedStorageBytes(ENCRYPTED_STORAGE_BYTES)
+                        .build();
+
+        VirtualMachine vm = forceCreateNewVirtualMachine("test_vm", config);
+        TestResults testResults =
+                runVmTestService(
+                        TAG,
+                        vm,
+                        (ts, tr) -> {
+                            ts.writeToFile(
+                                    /* content= */ EXAMPLE_STRING,
+                                    /* path= */ "/mnt/encryptedstore/test_file");
+                        });
+        testResults.assertNoException();
+
+        // Re-run the VM with more storage size & verify the file persisted.
+        // Note, the previous `runVmTestService` stopped the VM
+        config = newVmConfigBuilderWithPayloadBinary("MicrodroidTestNativeLib.so")
+                    .setEncryptedStorageBytes(ENCRYPTED_STORAGE_BYTES * 2)
+                    .build();
+        vm.setConfig(config);
+
+        testResults =
+                runVmTestService(
+                        TAG,
+                        vm,
+                        (ts, tr) -> {
+                            tr.mFileContent = ts.readFromFile("/mnt/encryptedstore/test_file");
+                        });
+        testResults.assertNoException();
+        assertThat(testResults.mFileContent).isEqualTo(EXAMPLE_STRING);
+    }
+
+    @Test
+    @CddTest
+    public void encryptedStorageSizeUnchanged() throws Exception {
+        assumeSupportedDevice();
+
+        VirtualMachineConfig config =
+                newVmConfigBuilderWithPayloadBinary("MicrodroidTestNativeLib.so")
+                        .setEncryptedStorageBytes(ENCRYPTED_STORAGE_BYTES)
+                        .build();
+
+        VirtualMachine vm = forceCreateNewVirtualMachine("test_vm", config);
+        TestResults testResults =
+                runVmTestService(
+                        TAG,
+                        vm,
+                        (ts, tr) -> {
+                            tr.mEncryptedStorageSize = ts.getEncryptedStorageSize();
+                        });
+        testResults.assertNoException();
+        assertThat(testResults.mEncryptedStorageSize)
+            .isWithin(TOLERANCE_BYTES)
+            .of(ENCRYPTED_STORAGE_BYTES);
+
+        // Re-run the VM with more storage size & verify the file persisted.
+        // Note, the previous `runVmTestService` stopped the VM
+        config = newVmConfigBuilderWithPayloadBinary("MicrodroidTestNativeLib.so")
+                    .setEncryptedStorageBytes(ENCRYPTED_STORAGE_BYTES)
+                    .build();
+        vm.setConfig(config);
+        assertThat(vm.getConfig().getEncryptedStorageBytes())
+            .isEqualTo(ENCRYPTED_STORAGE_BYTES);
+
+        testResults =
+                runVmTestService(
+                        TAG,
+                        vm,
+                        (ts, tr) -> {
+                            tr.mEncryptedStorageSize = ts.getEncryptedStorageSize();
+                        });
+        testResults.assertNoException();
+        assertThat(testResults.mEncryptedStorageSize)
+            .isWithin(TOLERANCE_BYTES)
+            .of(ENCRYPTED_STORAGE_BYTES);
+    }
+
+    @Test
+    @CddTest
+    public void encryptedStorageShrinkFails() throws Exception {
+        assumeSupportedDevice();
+
+        VirtualMachineConfig config =
+                newVmConfigBuilderWithPayloadBinary("MicrodroidTestNativeLib.so")
+                        .setEncryptedStorageBytes(ENCRYPTED_STORAGE_BYTES)
+                        .build();
+
+        VirtualMachine vm = forceCreateNewVirtualMachine("test_vm", config);
+        TestResults testResults =
+                runVmTestService(
+                        TAG,
+                        vm,
+                        (ts, tr) -> {
+                            tr.mEncryptedStorageSize = ts.getEncryptedStorageSize();
+                        });
+        testResults.assertNoException();
+        assertThat(testResults.mEncryptedStorageSize)
+            .isWithin(TOLERANCE_BYTES)
+            .of(ENCRYPTED_STORAGE_BYTES);
+
+        // Re-run the VM with more storage size & verify the file persisted.
+        // Note, the previous `runVmTestService` stopped the VM
+        VirtualMachineConfig newConfig =
+            newVmConfigBuilderWithPayloadBinary("MicrodroidTestNativeLib.so")
+                    .setEncryptedStorageBytes(ENCRYPTED_STORAGE_BYTES / 2)
+                    .build();
+        assertThrowsVmExceptionContaining(
+            () -> vm.setConfig(newConfig), "incompatible config");
+    }
+
     private boolean deviceCapableOfProtectedVm() {
         int capabilities = getVirtualMachineManager().getCapabilities();
         if ((capabilities & CAPABILITY_PROTECTED_VM) != 0) {
diff --git a/tests/testapk/src/native/testbinary.cpp b/tests/testapk/src/native/testbinary.cpp
index 355cfb1..13eafce 100644
--- a/tests/testapk/src/native/testbinary.cpp
+++ b/tests/testapk/src/native/testbinary.cpp
@@ -29,6 +29,7 @@
 #include <stdint.h>
 #include <stdio.h>
 #include <sys/capability.h>
+#include <sys/statvfs.h>
 #include <sys/system_properties.h>
 #ifdef __MICRODROID_TEST_PAYLOAD_USES_LIBICU__
 #include <unicode/uchar.h>
@@ -232,6 +233,23 @@
             return ScopedAStatus::ok();
         }
 
+        ScopedAStatus getEncryptedStorageSize(int64_t *out) override {
+            const char* path_c = AVmPayload_getEncryptedStoragePath();
+            if (path_c == nullptr) {
+                *out = 0;
+                return ScopedAStatus::ok();
+            }
+            struct statvfs buffer;
+            if (statvfs(path_c, &buffer) != 0) {
+                std::string msg = "statvfs " + std::string(path_c) + " failed :  " +
+                    std::strerror(errno);
+                return ScopedAStatus::fromExceptionCodeWithMessage(EX_SERVICE_SPECIFIC,
+                                                                   msg.c_str());
+            }
+            *out= buffer.f_blocks * buffer.f_frsize;
+            return ScopedAStatus::ok();
+        }
+
         ScopedAStatus getEffectiveCapabilities(std::vector<std::string>* out) override {
             if (out == nullptr) {
                 return ScopedAStatus::ok();
diff --git a/tests/testapk/src/native/testbinary.rs b/tests/testapk/src/native/testbinary.rs
index 3900cad..6a7d96e 100644
--- a/tests/testapk/src/native/testbinary.rs
+++ b/tests/testapk/src/native/testbinary.rs
@@ -87,6 +87,10 @@
 
     // Everything below here is unimplemented. Implementations may be added as needed.
 
+    fn getEncryptedStorageSize(&self) -> BinderResult<i64> {
+        unimplemented()
+    }
+
     fn readProperty(&self, _: &str) -> BinderResult<String> {
         unimplemented()
     }