VM: Introduce writePayloadRpData/readPayloadRpData

VM payload require an api to allow storing n bytes' data with
confidentialty & tamper evidence integrity guarantees.

Microdroid Manager implements this using the vm_secret module, which
uses the payload's DICE chain to store/get secret from Secretkeeper.

Additionally introduce a test that uses these api.
Test: #rollbackProtectedDataOfPayload
Bug: 378911776

Change-Id: Id39f5c6c626531029bf33ef5d28dc237881e40e6
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 97a5e78..35ee7dd 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -1826,6 +1826,79 @@
         assertThat(testResults.mFileContent).isEqualTo(EXAMPLE_STRING);
     }
 
+    private boolean deviceCapableOfProtectedVm() {
+        int capabilities = getVirtualMachineManager().getCapabilities();
+        if ((capabilities & CAPABILITY_PROTECTED_VM) != 0) {
+            return true;
+        }
+        return false;
+    }
+
+    private void ensureUpdatableVmSupported() throws Exception {
+        if (getVendorApiLevel() >= 202504 && deviceCapableOfProtectedVm()) {
+            assertTrue(
+                    "Missing Updatable VM support, have you declared Secretkeeper interface?",
+                    isUpdatableVmSupported());
+        } else {
+            assumeTrue("Device does not support Updatable VM", isUpdatableVmSupported());
+        }
+    }
+
+    @Test
+    public void rollbackProtectedDataOfPayload() throws Exception {
+        assumeSupportedDevice();
+        // Rollback protected data is only possible if Updatable VMs is supported -
+        // which implies Secretkeeper support.
+        ensureUpdatableVmSupported();
+        byte[] value1 = new byte[32];
+        Arrays.fill(value1, (byte) 0xcc);
+        byte[] value2 = new byte[32];
+        Arrays.fill(value2, (byte) 0xdd);
+
+        VirtualMachineConfig config =
+                newVmConfigBuilderWithPayloadBinary("MicrodroidTestNativeLib.so")
+                        .setMemoryBytes(minMemoryRequired())
+                        .setEncryptedStorageBytes(ENCRYPTED_STORAGE_BYTES)
+                        .setDebugLevel(DEBUG_LEVEL_FULL)
+                        .build();
+        VirtualMachine vm = forceCreateNewVirtualMachine("test_vm", config);
+        TestResults testResults =
+                runVmTestService(
+                        TAG,
+                        vm,
+                        (ts, tr) -> {
+                            tr.mPayloadRpData = ts.insecurelyReadPayloadRpData();
+                        });
+        // ainsecurelyReadPayloadRpData()` must've failed since no data was ever written!
+        assertWithMessage("The read (unexpectedly) succeeded!")
+                .that(testResults.mException)
+                .isNotNull();
+
+        // Re-run the same VM & write/read th RP data & verify it what we just wrote!
+        testResults =
+                runVmTestService(
+                        TAG,
+                        vm,
+                        (ts, tr) -> {
+                            ts.insecurelyWritePayloadRpData(value1);
+                            tr.mPayloadRpData = ts.insecurelyReadPayloadRpData();
+                            ts.insecurelyWritePayloadRpData(value2);
+                        });
+        testResults.assertNoException();
+        assertThat(testResults.mPayloadRpData).isEqualTo(value1);
+
+        // Re-run the same VM again
+        testResults =
+                runVmTestService(
+                        TAG,
+                        vm,
+                        (ts, tr) -> {
+                            tr.mPayloadRpData = ts.insecurelyReadPayloadRpData();
+                        });
+        testResults.assertNoException();
+        assertThat(testResults.mPayloadRpData).isEqualTo(value2);
+    }
+
     @Test
     @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-1"})
     public void canReadFileFromAssets_debugFull() throws Exception {
diff --git a/tests/testapk/src/native/testbinary.cpp b/tests/testapk/src/native/testbinary.cpp
index 632f648..a1739f9 100644
--- a/tests/testapk/src/native/testbinary.cpp
+++ b/tests/testapk/src/native/testbinary.cpp
@@ -344,6 +344,23 @@
             return ScopedAStatus::ok();
         }
 
+        ScopedAStatus insecurelyReadPayloadRpData(std::array<uint8_t, 32>* out) override {
+            int32_t ret = AVmPayload_readRollbackProtectedSecret(out->data(), 32);
+            if (ret != 32) {
+                return ScopedAStatus::fromServiceSpecificError(ret);
+            }
+            return ScopedAStatus::ok();
+        }
+
+        ScopedAStatus insecurelyWritePayloadRpData(
+                const std::array<uint8_t, 32>& inputData) override {
+            int32_t ret = AVmPayload_writeRollbackProtectedSecret(inputData.data(), 32);
+            if (ret != 32) {
+                return ScopedAStatus::fromServiceSpecificError(ret);
+            }
+            return ScopedAStatus::ok();
+        }
+
         ScopedAStatus quit() override { exit(0); }
     };
     auto testService = ndk::SharedRefBase::make<TestService>();
diff --git a/tests/testapk/src/native/testbinary.rs b/tests/testapk/src/native/testbinary.rs
index e479342..2b2fa28 100644
--- a/tests/testapk/src/native/testbinary.rs
+++ b/tests/testapk/src/native/testbinary.rs
@@ -132,6 +132,12 @@
     fn readLineFromConsole(&self) -> BinderResult<String> {
         unimplemented()
     }
+    fn insecurelyReadPayloadRpData(&self) -> BinderResult<[u8; 32]> {
+        unimplemented()
+    }
+    fn insecurelyWritePayloadRpData(&self, _: &[u8; 32]) -> BinderResult<()> {
+        unimplemented()
+    }
 }
 
 fn unimplemented<T>() -> BinderResult<T> {