apkmanifest: parse fields related to relaxed rollback protection scheme

In case a Microdroid pVM wants to opt in a relaxed rollback protection
scheme it needs to have the following things defined in its manifest:
* <uses-permission USE_RELAXED_MICRODROID_ROLLBACK_PROTECTION>
* set android.system.virtualmachine.ROLLBACK_INDEX <property>

In case only one of the two things is defined, the VM won't boot. This
is enforced by microdroid_manager (see changes to the verify.rs).

In the follow-up patch these new fields will be used to create a new
more relaxed sealing policy.

Bug: 378681279
Test: atest MicrodroidTests
Change-Id: Iabd12fd47f0eb271f021d5ad466de4f6c0669f2b
diff --git a/libs/apkmanifest/native/apkmanifest.cpp b/libs/apkmanifest/native/apkmanifest.cpp
index ab0ba72..f424bd8 100644
--- a/libs/apkmanifest/native/apkmanifest.cpp
+++ b/libs/apkmanifest/native/apkmanifest.cpp
@@ -28,6 +28,7 @@
 
 #include <cstdlib>
 #include <limits>
+#include <optional>
 #include <string>
 #include <string_view>
 
@@ -49,6 +50,8 @@
     std::string package;
     uint32_t version_code;
     uint32_t version_code_major;
+    std::optional<uint32_t> rollback_index;
+    bool has_relaxed_rollback_protection_permission;
 };
 
 namespace {
@@ -58,6 +61,15 @@
 constexpr u16string_view PACKAGE_ATTRIBUTE_NAME{u"package"};
 constexpr u16string_view VERSION_CODE_ATTRIBUTE_NAME{u"versionCode"};
 constexpr u16string_view VERSION_CODE_MAJOR_ATTRIBUTE_NAME{u"versionCodeMajor"};
+constexpr u16string_view USES_PERMISSION_TAG_NAME{u"uses-permission"};
+// This name is awkward, but i don't have a better idea ¯\_(ツ)_/¯.
+constexpr u16string_view NAME_ATTRIBUTE_NAME{u"name"};
+constexpr u16string_view VALUE_ATTRIBUTE_NAME{u"value"};
+constexpr u16string_view PROPERTY_TAG_NAME{u"property"};
+constexpr u16string_view ROLLBACK_INDEX_PROPERTY_NAME{
+        u"android.system.virtualmachine.ROLLBACK_INDEX"};
+constexpr u16string_view USE_RELAXED_ROLLBACK_PROTECTION_PERMISSION_NAME{
+        u"android.permission.USE_RELAXED_MICRODROID_ROLLBACK_PROTECTION"};
 
 // Read through the XML parse tree up to the <manifest> element.
 Result<void> findManifestElement(ResXMLTree& tree) {
@@ -135,11 +147,99 @@
     }
 }
 
-// Parse the binary manifest and extract the information we care about.
-// Everything we're interested in should be an attribute on the <manifest> tag.
-// We don't care what order they come in, absent attributes will be treated as
-// the default value, and any unknown attributes (including ones not in the
-// expected namespace) will be ignored.
+// Returns true if the given perm_tag contains the
+// `USE_RELAXED_MICRODROID_ROLLBACK_PROTECTION` permission.
+bool isRelaxedRollbackProtectionPermission(const ResXMLTree& perm_tag) {
+    size_t count = perm_tag.getAttributeCount();
+
+    for (size_t i = 0; i < count; i++) {
+        size_t len = 0;
+        const char16_t* chars = perm_tag.getAttributeNamespace(i, &len);
+        auto namespaceUrl = chars ? u16string_view(chars, len) : u16string_view();
+
+        chars = perm_tag.getAttributeName(i, &len);
+        auto attributeName = chars ? u16string_view(chars, len) : u16string_view();
+
+        if (namespaceUrl != ANDROID_NAMESPACE_URL) {
+            continue;
+        }
+
+        if (attributeName != NAME_ATTRIBUTE_NAME) {
+            continue;
+        }
+
+        chars = perm_tag.getAttributeStringValue(i, &len);
+        if (!chars) {
+            LOG(WARNING) << "expected name attribute to be non-empty";
+            continue;
+        }
+
+        // What a name!
+        auto nameName = u16string_view(chars, len);
+        if (nameName == USE_RELAXED_ROLLBACK_PROTECTION_PERMISSION_NAME) {
+            return true;
+        }
+    }
+
+    return false;
+}
+
+// Returns value of the `android.system.virtualmachine.ROLLBACK_INDEX` property or std::nullopt if
+// given prop_tag doesn't represent the rollback_index property.
+std::optional<uint32_t> getRollbackIndexValue(const ResXMLTree& prop_tag) {
+    size_t count = prop_tag.getAttributeCount();
+    bool is_rollback_index_prop = false;
+
+    // Note: in theory the `android:value` attribute can come before `android:name` one, so we need
+    // to iterate over all attributes twice.
+    for (size_t it = 0; it < 2 * count; it++) {
+        size_t i = it >= count ? it - count : it;
+
+        size_t len = 0;
+        const char16_t* chars = prop_tag.getAttributeNamespace(i, &len);
+        auto namespaceUrl = chars ? u16string_view(chars, len) : u16string_view();
+
+        chars = prop_tag.getAttributeName(i, &len);
+        auto attributeName = chars ? u16string_view(chars, len) : u16string_view();
+
+        if (namespaceUrl != ANDROID_NAMESPACE_URL) {
+            continue;
+        }
+
+        if (attributeName == NAME_ATTRIBUTE_NAME) {
+            chars = prop_tag.getAttributeStringValue(i, &len);
+            if (!chars) {
+                LOG(WARNING) << "expected name attribute to be non-empty";
+                continue;
+            }
+
+            // What a name!
+            auto nameName = u16string_view(chars, len);
+            if (nameName != ROLLBACK_INDEX_PROPERTY_NAME) {
+                return std::nullopt;
+            }
+            is_rollback_index_prop = true;
+        } else if (attributeName == VALUE_ATTRIBUTE_NAME) {
+            if (!is_rollback_index_prop) {
+                // We don't know yet if this is the right property. Skip for now.
+                continue;
+            }
+            auto value = getU32Attribute(prop_tag, i);
+            if (!value.ok()) {
+                LOG(ERROR) << "Failed to parse value of the rollback index : " << value.error();
+                return std::nullopt;
+            }
+            return std::make_optional(std::move(*value));
+        }
+    }
+
+    return std::nullopt;
+}
+
+// Parse the binary manifest and extract the information we care about. Everything we're interested
+// in should be an attribute on the <manifest> tag. We don't care what order they come in, absent
+// attributes will be treated as the default value, and any unknown attributes (including ones not
+// in the expected namespace) will be ignored.
 Result<unique_ptr<ApkManifestInfo>> parseManifest(const void* manifest, size_t size) {
     ResXMLTree tree;
     auto status = tree.setTo(manifest, size);
@@ -181,8 +281,66 @@
         }
     }
 
+    info->has_relaxed_rollback_protection_permission = false;
+
+    // Now we need to parse the rest of the manifest to check if it contains the
+    // `USE_RELAXED_MICRODROID_ROLLBACK_PROTECTION` permission and the
+    // `android.system.virtualmachine.ROLLBACK_INDEX` property.
+    for (;;) {
+        ResXMLParser::event_code_t event = tree.next();
+        switch (event) {
+            case ResXMLParser::END_DOCUMENT:
+                return info;
+            case ResXMLParser::BAD_DOCUMENT:
+                return Error() << "Failed to parse XML: " << statusToString(tree.getError());
+            case ResXMLParser::START_TAG: {
+                size_t len = 0;
+                const char16_t* chars = tree.getElementName(&len);
+                if (!chars) {
+                    return Error() << "Missing tag name";
+                }
+                auto tag_name = u16string_view(chars, len);
+                if (tag_name != USES_PERMISSION_TAG_NAME && tag_name != PROPERTY_TAG_NAME) {
+                    // We are only interested in <uses-permission> and <property> tags.
+                    break;
+                }
+
+                if (tag_name == USES_PERMISSION_TAG_NAME) {
+                    if (isRelaxedRollbackProtectionPermission(tree)) {
+                        info->has_relaxed_rollback_protection_permission = true;
+                    }
+                } else if (tag_name == PROPERTY_TAG_NAME) {
+                    auto rollback_index = getRollbackIndexValue(tree);
+                    if (rollback_index.has_value()) {
+                        LOG(INFO) << "found rollback_index : " << *rollback_index;
+                        if (info->rollback_index.has_value()) {
+                            LOG(WARNING)
+                                    << "found duplicate rollback index, overriding previous value";
+                        }
+                        info->rollback_index.emplace(*rollback_index);
+                    }
+                } else {
+                    break;
+                }
+
+                break;
+            }
+            case ResXMLParser::START_NAMESPACE:
+                break;
+            case ResXMLParser::END_NAMESPACE:
+                break;
+            case ResXMLParser::END_TAG:
+                break;
+            default: {
+                LOG(ERROR) << "found unexpected event : " << event;
+                continue;
+            }
+        }
+    }
+
     return info;
 }
+
 } // namespace
 
 const ApkManifestInfo* extractManifestInfo(const void* manifest, size_t size) {
@@ -205,3 +363,11 @@
 uint64_t getVersionCode(const ApkManifestInfo* info) {
     return info->version_code | (static_cast<uint64_t>(info->version_code_major) << 32);
 }
+
+const uint32_t* getRollbackIndex(const ApkManifestInfo* info) {
+    return info->rollback_index.has_value() ? &info->rollback_index.value() : nullptr;
+}
+
+bool hasRelaxedRollbackProtectionPermission(const ApkManifestInfo* info) {
+    return info->has_relaxed_rollback_protection_permission;
+}
diff --git a/libs/apkmanifest/native/apkmanifest.hpp b/libs/apkmanifest/native/apkmanifest.hpp
index 352912e..f86d5d9 100644
--- a/libs/apkmanifest/native/apkmanifest.hpp
+++ b/libs/apkmanifest/native/apkmanifest.hpp
@@ -41,4 +41,13 @@
 
 // Given a valid ApkManifestInfo pointer, return the version code of the APK.
 uint64_t getVersionCode(const ApkManifestInfo* info);
+
+// Given a valid ApkManifestInfo pointer, return the value of the rollback
+// index. If rollback index is not defined in the AndroidManifest.xml returns
+// nullptr.
+const uint32_t* getRollbackIndex(const ApkManifestInfo* info);
+
+// Give a valid ApkManifestInfo pointer, return the value of the
+// has_relaxed_rollback_protection_permission.
+bool hasRelaxedRollbackProtectionPermission(const ApkManifestInfo* info);
 }
diff --git a/libs/apkmanifest/src/apkmanifest.rs b/libs/apkmanifest/src/apkmanifest.rs
index b92aa74..9ba899d 100644
--- a/libs/apkmanifest/src/apkmanifest.rs
+++ b/libs/apkmanifest/src/apkmanifest.rs
@@ -22,7 +22,10 @@
 //! things).
 
 use anyhow::{bail, Context, Result};
-use apkmanifest_bindgen::{extractManifestInfo, freeManifestInfo, getPackageName, getVersionCode};
+use apkmanifest_bindgen::{
+    extractManifestInfo, freeManifestInfo, getPackageName, getRollbackIndex, getVersionCode,
+    hasRelaxedRollbackProtectionPermission,
+};
 use std::ffi::CStr;
 use std::fs::File;
 use std::path::Path;
@@ -34,6 +37,12 @@
     pub package: String,
     /// The version code of the app.
     pub version_code: u64,
+    /// Rollback index of the app used in the sealing dice policy.
+    /// This is only set if the apk manifest has USE_RELAXED_MICRODROID_ROLLBACK_PROTECTION
+    /// permission.
+    pub rollback_index: Option<u32>,
+    /// Whether manifest has USE_RELAXED_MICRODROID_ROLLBACK_PROTECTION permission.
+    pub has_relaxed_rollback_protection_permission: bool,
 }
 
 const ANDROID_MANIFEST: &str = "AndroidManifest.xml";
@@ -66,5 +75,24 @@
     // Safety: It is always safe to call this with a valid native_info, which we have.
     let version_code = unsafe { getVersionCode(native_info) };
 
-    Ok(ApkManifestInfo { package, version_code })
+    // Safety: It is always safe to call this with a valid native_info, which we have.
+    let rollback_index = unsafe {
+        let rollback_index = getRollbackIndex(native_info);
+        if rollback_index.is_null() {
+            None
+        } else {
+            Some(*rollback_index)
+        }
+    };
+
+    // Safety: It is always safe to call this with a valid native_info, which we have.
+    let has_relaxed_rollback_protection_permission =
+        unsafe { hasRelaxedRollbackProtectionPermission(native_info) };
+
+    Ok(ApkManifestInfo {
+        package,
+        version_code,
+        rollback_index,
+        has_relaxed_rollback_protection_permission,
+    })
 }