blob: f424bd8f7fdfae8f955f11878b746af45c5ab60d [file] [log] [blame]
Alan Stokes068f6d42023-10-09 10:13:03 +01001/*
2 * Copyright 2023 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include "apkmanifest.hpp"
18
19#include <android-base/logging.h>
20#include <android-base/result.h>
21#include <androidfw/AssetsProvider.h>
22#include <androidfw/ResourceTypes.h>
23#include <androidfw/StringPiece.h>
24#include <androidfw/Util.h>
25#include <stddef.h>
26#include <stdint.h>
27#include <utils/Errors.h>
28
29#include <cstdlib>
30#include <limits>
Nikita Ioffea73451a2025-01-28 17:09:22 +000031#include <optional>
Alan Stokes068f6d42023-10-09 10:13:03 +010032#include <string>
33#include <string_view>
34
35using android::Asset;
36using android::AssetsProvider;
37using android::OK;
38using android::Res_value;
39using android::ResXMLParser;
40using android::ResXMLTree;
41using android::statusToString;
42using android::StringPiece16;
43using android::base::Error;
44using android::base::Result;
45using android::util::Utf16ToUtf8;
46using std::u16string_view;
47using std::unique_ptr;
48
49struct ApkManifestInfo {
50 std::string package;
51 uint32_t version_code;
52 uint32_t version_code_major;
Nikita Ioffea73451a2025-01-28 17:09:22 +000053 std::optional<uint32_t> rollback_index;
54 bool has_relaxed_rollback_protection_permission;
Alan Stokes068f6d42023-10-09 10:13:03 +010055};
56
57namespace {
58// See https://developer.android.com/guide/topics/manifest/manifest-element
59constexpr u16string_view MANIFEST_TAG_NAME{u"manifest"};
60constexpr u16string_view ANDROID_NAMESPACE_URL{u"http://schemas.android.com/apk/res/android"};
61constexpr u16string_view PACKAGE_ATTRIBUTE_NAME{u"package"};
62constexpr u16string_view VERSION_CODE_ATTRIBUTE_NAME{u"versionCode"};
63constexpr u16string_view VERSION_CODE_MAJOR_ATTRIBUTE_NAME{u"versionCodeMajor"};
Nikita Ioffea73451a2025-01-28 17:09:22 +000064constexpr u16string_view USES_PERMISSION_TAG_NAME{u"uses-permission"};
65// This name is awkward, but i don't have a better idea ¯\_(ツ)_/¯.
66constexpr u16string_view NAME_ATTRIBUTE_NAME{u"name"};
67constexpr u16string_view VALUE_ATTRIBUTE_NAME{u"value"};
68constexpr u16string_view PROPERTY_TAG_NAME{u"property"};
69constexpr u16string_view ROLLBACK_INDEX_PROPERTY_NAME{
70 u"android.system.virtualmachine.ROLLBACK_INDEX"};
71constexpr u16string_view USE_RELAXED_ROLLBACK_PROTECTION_PERMISSION_NAME{
72 u"android.permission.USE_RELAXED_MICRODROID_ROLLBACK_PROTECTION"};
Alan Stokes068f6d42023-10-09 10:13:03 +010073
74// Read through the XML parse tree up to the <manifest> element.
75Result<void> findManifestElement(ResXMLTree& tree) {
76 for (;;) {
77 ResXMLParser::event_code_t event = tree.next();
78 switch (event) {
79 case ResXMLParser::END_DOCUMENT:
80 case ResXMLParser::END_TAG:
81 case ResXMLParser::TEXT:
82 default:
83 return Error() << "Unexpected XML parsing event: " << event;
84 case ResXMLParser::BAD_DOCUMENT:
85 return Error() << "Failed to parse XML: " << statusToString(tree.getError());
86 case ResXMLParser::START_NAMESPACE:
87 case ResXMLParser::END_NAMESPACE:
88 // Not of interest, keep going.
89 break;
90 case ResXMLParser::START_TAG:
91 // The first tag in an AndroidManifest.xml should be <manifest> (no namespace).
92 // And that's actually the only tag we care about.
93 if (tree.getElementNamespaceID() >= 0) {
94 return Error() << "Root element has unexpected namespace.";
95 }
96 size_t nameLength = 0;
97 const char16_t* nameChars = tree.getElementName(&nameLength);
98 if (!nameChars) {
99 return Error() << "Missing tag name";
100 }
101 if (u16string_view(nameChars, nameLength) != MANIFEST_TAG_NAME) {
102 return Error() << "Expected <manifest> as root element";
103 }
104 return {};
105 }
106 }
107}
108
109// Return an attribute encoded as a string, converted to UTF-8. Note that all
110// attributes are strings in the original XML, but the binary format encodes
111// some as binary numbers etc. This function does not handle converting those
112// encodings back to strings, so should only be used when it is known that a
113// numeric value is not allowed.
114Result<std::string> getStringOnlyAttribute(const ResXMLTree& tree, size_t index) {
115 size_t len;
116 const char16_t* value = tree.getAttributeStringValue(index, &len);
117 if (!value) {
118 return Error() << "Expected attribute to have string value";
119 }
120 return Utf16ToUtf8(StringPiece16(value, len));
121}
122
123// Return the u32 value of an attribute.
124Result<uint32_t> getU32Attribute(const ResXMLTree& tree, size_t index) {
125 auto type = tree.getAttributeDataType(index);
126 switch (type) {
127 case Res_value::TYPE_INT_DEC:
128 case Res_value::TYPE_INT_HEX:
129 // This is how we'd expect the version to be encoded - and we don't
130 // care what base it was originally in.
131 return tree.getAttributeData(index);
132 case Res_value::TYPE_STRING: {
133 // If the original string is encoded, then we need to convert it.
134 auto str = OR_RETURN(getStringOnlyAttribute(tree, index));
135 char* str_end = nullptr;
136 // Note that by specifying base 0 we allow for octal, hex, or
137 // decimal representations here.
138 unsigned long value = std::strtoul(str.c_str(), &str_end, 0);
139 if (str_end != str.c_str() + str.size() ||
140 value > std::numeric_limits<uint32_t>::max()) {
141 return Error() << "Invalid numeric value";
142 }
143 return static_cast<uint32_t>(value);
144 }
145 default:
146 return Error() << "Expected numeric value, got type " << type;
147 }
148}
149
Nikita Ioffea73451a2025-01-28 17:09:22 +0000150// Returns true if the given perm_tag contains the
151// `USE_RELAXED_MICRODROID_ROLLBACK_PROTECTION` permission.
152bool isRelaxedRollbackProtectionPermission(const ResXMLTree& perm_tag) {
153 size_t count = perm_tag.getAttributeCount();
154
155 for (size_t i = 0; i < count; i++) {
156 size_t len = 0;
157 const char16_t* chars = perm_tag.getAttributeNamespace(i, &len);
158 auto namespaceUrl = chars ? u16string_view(chars, len) : u16string_view();
159
160 chars = perm_tag.getAttributeName(i, &len);
161 auto attributeName = chars ? u16string_view(chars, len) : u16string_view();
162
163 if (namespaceUrl != ANDROID_NAMESPACE_URL) {
164 continue;
165 }
166
167 if (attributeName != NAME_ATTRIBUTE_NAME) {
168 continue;
169 }
170
171 chars = perm_tag.getAttributeStringValue(i, &len);
172 if (!chars) {
173 LOG(WARNING) << "expected name attribute to be non-empty";
174 continue;
175 }
176
177 // What a name!
178 auto nameName = u16string_view(chars, len);
179 if (nameName == USE_RELAXED_ROLLBACK_PROTECTION_PERMISSION_NAME) {
180 return true;
181 }
182 }
183
184 return false;
185}
186
187// Returns value of the `android.system.virtualmachine.ROLLBACK_INDEX` property or std::nullopt if
188// given prop_tag doesn't represent the rollback_index property.
189std::optional<uint32_t> getRollbackIndexValue(const ResXMLTree& prop_tag) {
190 size_t count = prop_tag.getAttributeCount();
191 bool is_rollback_index_prop = false;
192
193 // Note: in theory the `android:value` attribute can come before `android:name` one, so we need
194 // to iterate over all attributes twice.
195 for (size_t it = 0; it < 2 * count; it++) {
196 size_t i = it >= count ? it - count : it;
197
198 size_t len = 0;
199 const char16_t* chars = prop_tag.getAttributeNamespace(i, &len);
200 auto namespaceUrl = chars ? u16string_view(chars, len) : u16string_view();
201
202 chars = prop_tag.getAttributeName(i, &len);
203 auto attributeName = chars ? u16string_view(chars, len) : u16string_view();
204
205 if (namespaceUrl != ANDROID_NAMESPACE_URL) {
206 continue;
207 }
208
209 if (attributeName == NAME_ATTRIBUTE_NAME) {
210 chars = prop_tag.getAttributeStringValue(i, &len);
211 if (!chars) {
212 LOG(WARNING) << "expected name attribute to be non-empty";
213 continue;
214 }
215
216 // What a name!
217 auto nameName = u16string_view(chars, len);
218 if (nameName != ROLLBACK_INDEX_PROPERTY_NAME) {
219 return std::nullopt;
220 }
221 is_rollback_index_prop = true;
222 } else if (attributeName == VALUE_ATTRIBUTE_NAME) {
223 if (!is_rollback_index_prop) {
224 // We don't know yet if this is the right property. Skip for now.
225 continue;
226 }
227 auto value = getU32Attribute(prop_tag, i);
228 if (!value.ok()) {
229 LOG(ERROR) << "Failed to parse value of the rollback index : " << value.error();
230 return std::nullopt;
231 }
232 return std::make_optional(std::move(*value));
233 }
234 }
235
236 return std::nullopt;
237}
238
239// Parse the binary manifest and extract the information we care about. Everything we're interested
240// in should be an attribute on the <manifest> tag. We don't care what order they come in, absent
241// attributes will be treated as the default value, and any unknown attributes (including ones not
242// in the expected namespace) will be ignored.
Alan Stokes068f6d42023-10-09 10:13:03 +0100243Result<unique_ptr<ApkManifestInfo>> parseManifest(const void* manifest, size_t size) {
244 ResXMLTree tree;
245 auto status = tree.setTo(manifest, size);
246 if (status != OK) {
247 return Error() << "Failed to create XML Tree: " << statusToString(status);
248 }
249
250 OR_RETURN(findManifestElement(tree));
251
252 unique_ptr<ApkManifestInfo> info{new ApkManifestInfo{}};
253
254 size_t count = tree.getAttributeCount();
255 for (size_t i = 0; i < count; ++i) {
256 size_t len;
257 const char16_t* chars;
258
259 chars = tree.getAttributeNamespace(i, &len);
260 auto namespaceUrl = chars ? u16string_view(chars, len) : u16string_view();
261
262 chars = tree.getAttributeName(i, &len);
263 auto attributeName = chars ? u16string_view(chars, len) : u16string_view();
264
265 if (namespaceUrl.empty()) {
266 if (attributeName == PACKAGE_ATTRIBUTE_NAME) {
267 auto result = getStringOnlyAttribute(tree, i);
268 if (!result.ok()) return Error() << "Package name: " << result.error();
269 info->package = *result;
270 }
271 } else if (namespaceUrl == ANDROID_NAMESPACE_URL) {
272 if (attributeName == VERSION_CODE_ATTRIBUTE_NAME) {
273 auto result = getU32Attribute(tree, i);
274 if (!result.ok()) return Error() << "Version code: " << result.error();
275 info->version_code = *result;
276 } else if (attributeName == VERSION_CODE_MAJOR_ATTRIBUTE_NAME) {
277 auto result = getU32Attribute(tree, i);
278 if (!result.ok()) return Error() << "Version code major: " << result.error();
279 info->version_code_major = *result;
280 }
281 }
282 }
283
Nikita Ioffea73451a2025-01-28 17:09:22 +0000284 info->has_relaxed_rollback_protection_permission = false;
285
286 // Now we need to parse the rest of the manifest to check if it contains the
287 // `USE_RELAXED_MICRODROID_ROLLBACK_PROTECTION` permission and the
288 // `android.system.virtualmachine.ROLLBACK_INDEX` property.
289 for (;;) {
290 ResXMLParser::event_code_t event = tree.next();
291 switch (event) {
292 case ResXMLParser::END_DOCUMENT:
293 return info;
294 case ResXMLParser::BAD_DOCUMENT:
295 return Error() << "Failed to parse XML: " << statusToString(tree.getError());
296 case ResXMLParser::START_TAG: {
297 size_t len = 0;
298 const char16_t* chars = tree.getElementName(&len);
299 if (!chars) {
300 return Error() << "Missing tag name";
301 }
302 auto tag_name = u16string_view(chars, len);
303 if (tag_name != USES_PERMISSION_TAG_NAME && tag_name != PROPERTY_TAG_NAME) {
304 // We are only interested in <uses-permission> and <property> tags.
305 break;
306 }
307
308 if (tag_name == USES_PERMISSION_TAG_NAME) {
309 if (isRelaxedRollbackProtectionPermission(tree)) {
310 info->has_relaxed_rollback_protection_permission = true;
311 }
312 } else if (tag_name == PROPERTY_TAG_NAME) {
313 auto rollback_index = getRollbackIndexValue(tree);
314 if (rollback_index.has_value()) {
315 LOG(INFO) << "found rollback_index : " << *rollback_index;
316 if (info->rollback_index.has_value()) {
317 LOG(WARNING)
318 << "found duplicate rollback index, overriding previous value";
319 }
320 info->rollback_index.emplace(*rollback_index);
321 }
322 } else {
323 break;
324 }
325
326 break;
327 }
328 case ResXMLParser::START_NAMESPACE:
329 break;
330 case ResXMLParser::END_NAMESPACE:
331 break;
332 case ResXMLParser::END_TAG:
333 break;
334 default: {
335 LOG(ERROR) << "found unexpected event : " << event;
336 continue;
337 }
338 }
339 }
340
Alan Stokes068f6d42023-10-09 10:13:03 +0100341 return info;
342}
Nikita Ioffea73451a2025-01-28 17:09:22 +0000343
Alan Stokes068f6d42023-10-09 10:13:03 +0100344} // namespace
345
346const ApkManifestInfo* extractManifestInfo(const void* manifest, size_t size) {
347 auto result = parseManifest(manifest, size);
348 if (!result.ok()) {
349 LOG(ERROR) << "Failed to parse APK manifest:" << result.error().message();
350 return nullptr;
351 }
352 return result->release();
353}
354
355void freeManifestInfo(const ApkManifestInfo* info) {
356 delete info;
357}
358
359const char* getPackageName(const ApkManifestInfo* info) {
360 return info->package.c_str();
361}
362
363uint64_t getVersionCode(const ApkManifestInfo* info) {
364 return info->version_code | (static_cast<uint64_t>(info->version_code_major) << 32);
365}
Nikita Ioffea73451a2025-01-28 17:09:22 +0000366
367const uint32_t* getRollbackIndex(const ApkManifestInfo* info) {
368 return info->rollback_index.has_value() ? &info->rollback_index.value() : nullptr;
369}
370
371bool hasRelaxedRollbackProtectionPermission(const ApkManifestInfo* info) {
372 return info->has_relaxed_rollback_protection_permission;
373}