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;
+}