blob: 2f416a2538baee780e4964fd524889d0c55f8a3d [file] [log] [blame]
Alan Viverette3da604b2020-06-10 18:34:39 +00001/*
2 * Copyright (C) 2019 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
17package android.content.pm.parsing;
18
19import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
20
21import android.compat.annotation.UnsupportedAppUsage;
22import android.content.pm.PackageInfo;
23import android.content.pm.PackageManager;
24import android.content.pm.PackageParser;
25import android.content.pm.VerifierInfo;
26import android.content.res.ApkAssets;
27import android.content.res.XmlResourceParser;
28import android.os.Trace;
29import android.util.ArrayMap;
30import android.util.AttributeSet;
31import android.util.Pair;
32import android.util.Slog;
33
34import com.android.internal.R;
35import com.android.internal.util.ArrayUtils;
36
37import libcore.io.IoUtils;
38
39import org.xmlpull.v1.XmlPullParser;
40import org.xmlpull.v1.XmlPullParserException;
41
42import java.io.File;
43import java.io.FileDescriptor;
44import java.io.IOException;
45import java.security.PublicKey;
46import java.util.ArrayList;
47import java.util.Arrays;
48import java.util.List;
49
50/** @hide */
51public class ApkLiteParseUtils {
52
53 private static final String TAG = ParsingPackageUtils.TAG;
54
55 // TODO(b/135203078): Consolidate constants
56 private static final int DEFAULT_MIN_SDK_VERSION = 1;
57 private static final int DEFAULT_TARGET_SDK_VERSION = 0;
58
59 private static final int PARSE_DEFAULT_INSTALL_LOCATION =
60 PackageInfo.INSTALL_LOCATION_UNSPECIFIED;
61
62 /**
63 * Parse only lightweight details about the package at the given location.
64 * Automatically detects if the package is a monolithic style (single APK
65 * file) or cluster style (directory of APKs).
66 * <p>
67 * This performs sanity checking on cluster style packages, such as
68 * requiring identical package name and version codes, a single base APK,
69 * and unique split names.
70 *
71 * @see PackageParser#parsePackage(File, int)
72 */
73 @UnsupportedAppUsage
74 public static PackageParser.PackageLite parsePackageLite(File packageFile, int flags)
75 throws PackageParser.PackageParserException {
76 if (packageFile.isDirectory()) {
77 return parseClusterPackageLite(packageFile, flags);
78 } else {
79 return parseMonolithicPackageLite(packageFile, flags);
80 }
81 }
82
83 public static PackageParser.PackageLite parseMonolithicPackageLite(File packageFile, int flags)
84 throws PackageParser.PackageParserException {
85 Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parseApkLite");
86 final PackageParser.ApkLite baseApk = parseApkLite(packageFile, flags);
87 final String packagePath = packageFile.getAbsolutePath();
88 Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
89 return new PackageParser.PackageLite(packagePath, baseApk, null, null, null, null,
90 null, null);
91 }
92
93 public static PackageParser.PackageLite parseClusterPackageLite(File packageDir, int flags)
94 throws PackageParser.PackageParserException {
95 final File[] files = packageDir.listFiles();
96 if (ArrayUtils.isEmpty(files)) {
97 throw new PackageParser.PackageParserException(
98 PackageManager.INSTALL_PARSE_FAILED_NOT_APK, "No packages found in split");
99 }
100 // Apk directory is directly nested under the current directory
101 if (files.length == 1 && files[0].isDirectory()) {
102 return parseClusterPackageLite(files[0], flags);
103 }
104
105 String packageName = null;
106 int versionCode = 0;
107
108 Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parseApkLite");
109 final ArrayMap<String, PackageParser.ApkLite> apks = new ArrayMap<>();
110 for (File file : files) {
111 if (PackageParser.isApkFile(file)) {
112 final PackageParser.ApkLite lite = parseApkLite(file, flags);
113
114 // Assert that all package names and version codes are
115 // consistent with the first one we encounter.
116 if (packageName == null) {
117 packageName = lite.packageName;
118 versionCode = lite.versionCode;
119 } else {
120 if (!packageName.equals(lite.packageName)) {
121 throw new PackageParser.PackageParserException(
122 PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST,
123 "Inconsistent package " + lite.packageName + " in " + file
124 + "; expected " + packageName);
125 }
126 if (versionCode != lite.versionCode) {
127 throw new PackageParser.PackageParserException(
128 PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST,
129 "Inconsistent version " + lite.versionCode + " in " + file
130 + "; expected " + versionCode);
131 }
132 }
133
134 // Assert that each split is defined only oncuses-static-libe
135 if (apks.put(lite.splitName, lite) != null) {
136 throw new PackageParser.PackageParserException(
137 PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST,
138 "Split name " + lite.splitName
139 + " defined more than once; most recent was " + file);
140 }
141 }
142 }
143 Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
144
145 final PackageParser.ApkLite baseApk = apks.remove(null);
146 if (baseApk == null) {
147 throw new PackageParser.PackageParserException(
148 PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST,
149 "Missing base APK in " + packageDir);
150 }
151
152 // Always apply deterministic ordering based on splitName
153 final int size = apks.size();
154
155 String[] splitNames = null;
156 boolean[] isFeatureSplits = null;
157 String[] usesSplitNames = null;
158 String[] configForSplits = null;
159 String[] splitCodePaths = null;
160 int[] splitRevisionCodes = null;
161 if (size > 0) {
162 splitNames = new String[size];
163 isFeatureSplits = new boolean[size];
164 usesSplitNames = new String[size];
165 configForSplits = new String[size];
166 splitCodePaths = new String[size];
167 splitRevisionCodes = new int[size];
168
169 splitNames = apks.keySet().toArray(splitNames);
170 Arrays.sort(splitNames, PackageParser.sSplitNameComparator);
171
172 for (int i = 0; i < size; i++) {
173 final PackageParser.ApkLite apk = apks.get(splitNames[i]);
174 usesSplitNames[i] = apk.usesSplitName;
175 isFeatureSplits[i] = apk.isFeatureSplit;
176 configForSplits[i] = apk.configForSplit;
177 splitCodePaths[i] = apk.codePath;
178 splitRevisionCodes[i] = apk.revisionCode;
179 }
180 }
181
182 final String codePath = packageDir.getAbsolutePath();
183 return new PackageParser.PackageLite(codePath, baseApk, splitNames, isFeatureSplits,
184 usesSplitNames, configForSplits, splitCodePaths, splitRevisionCodes);
185 }
186
187 /**
188 * Utility method that retrieves lightweight details about a single APK
189 * file, including package name, split name, and install location.
190 *
191 * @param apkFile path to a single APK
192 * @param flags optional parse flags, such as
193 * {@link PackageParser#PARSE_COLLECT_CERTIFICATES}
194 */
195 public static PackageParser.ApkLite parseApkLite(File apkFile, int flags)
196 throws PackageParser.PackageParserException {
197 return parseApkLiteInner(apkFile, null, null, flags);
198 }
199
200 /**
201 * Utility method that retrieves lightweight details about a single APK
202 * file, including package name, split name, and install location.
203 *
204 * @param fd already open file descriptor of an apk file
205 * @param debugPathName arbitrary text name for this file, for debug output
206 * @param flags optional parse flags, such as
207 * {@link PackageParser#PARSE_COLLECT_CERTIFICATES}
208 */
209 public static PackageParser.ApkLite parseApkLite(FileDescriptor fd, String debugPathName,
210 int flags) throws PackageParser.PackageParserException {
211 return parseApkLiteInner(null, fd, debugPathName, flags);
212 }
213
214 private static PackageParser.ApkLite parseApkLiteInner(File apkFile, FileDescriptor fd,
215 String debugPathName, int flags) throws PackageParser.PackageParserException {
216 final String apkPath = fd != null ? debugPathName : apkFile.getAbsolutePath();
217
218 XmlResourceParser parser = null;
219 ApkAssets apkAssets = null;
220 try {
221 try {
222 apkAssets = fd != null
223 ? ApkAssets.loadFromFd(fd, debugPathName, 0 /* flags */, null /* assets */)
224 : ApkAssets.loadFromPath(apkPath);
225 } catch (IOException e) {
226 throw new PackageParser.PackageParserException(
227 PackageManager.INSTALL_PARSE_FAILED_NOT_APK,
228 "Failed to parse " + apkPath, e);
229 }
230
231 parser = apkAssets.openXml(PackageParser.ANDROID_MANIFEST_FILENAME);
232
233 final PackageParser.SigningDetails signingDetails;
234 if ((flags & PackageParser.PARSE_COLLECT_CERTIFICATES) != 0) {
235 final boolean skipVerify = (flags & PackageParser.PARSE_IS_SYSTEM_DIR) != 0;
236 Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "collectCertificates");
237 try {
238 signingDetails = ParsingPackageUtils.collectCertificates(apkFile.getAbsolutePath(),
239 skipVerify, false, PackageParser.SigningDetails.UNKNOWN,
240 DEFAULT_TARGET_SDK_VERSION);
241 } finally {
242 Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
243 }
244 } else {
245 signingDetails = PackageParser.SigningDetails.UNKNOWN;
246 }
247
248 final AttributeSet attrs = parser;
249 return parseApkLite(apkPath, parser, attrs, signingDetails);
250
251 } catch (XmlPullParserException | IOException | RuntimeException e) {
252 Slog.w(TAG, "Failed to parse " + apkPath, e);
253 throw new PackageParser.PackageParserException(
254 PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
255 "Failed to parse " + apkPath, e);
256 } finally {
257 IoUtils.closeQuietly(parser);
258 if (apkAssets != null) {
259 try {
260 apkAssets.close();
261 } catch (Throwable ignored) {
262 }
263 }
264 // TODO(b/72056911): Implement AutoCloseable on ApkAssets.
265 }
266 }
267
268 private static PackageParser.ApkLite parseApkLite(
269 String codePath, XmlPullParser parser, AttributeSet attrs,
270 PackageParser.SigningDetails signingDetails)
271 throws IOException, XmlPullParserException, PackageParser.PackageParserException {
272 final Pair<String, String> packageSplit = PackageParser.parsePackageSplitNames(
273 parser, attrs);
274
275 int installLocation = PARSE_DEFAULT_INSTALL_LOCATION;
276 int versionCode = 0;
277 int versionCodeMajor = 0;
278 int targetSdkVersion = DEFAULT_TARGET_SDK_VERSION;
279 int minSdkVersion = DEFAULT_MIN_SDK_VERSION;
280 int revisionCode = 0;
281 boolean coreApp = false;
282 boolean debuggable = false;
283 boolean multiArch = false;
284 boolean use32bitAbi = false;
285 boolean extractNativeLibs = true;
286 boolean isolatedSplits = false;
287 boolean isFeatureSplit = false;
288 boolean isSplitRequired = false;
289 boolean useEmbeddedDex = false;
290 String configForSplit = null;
291 String usesSplitName = null;
292 String targetPackage = null;
293 boolean overlayIsStatic = false;
294 int overlayPriority = 0;
295
296 String requiredSystemPropertyName = null;
297 String requiredSystemPropertyValue = null;
298
299 for (int i = 0; i < attrs.getAttributeCount(); i++) {
300 final String attr = attrs.getAttributeName(i);
301 switch (attr) {
302 case "installLocation":
303 installLocation = attrs.getAttributeIntValue(i,
304 PARSE_DEFAULT_INSTALL_LOCATION);
305 break;
306 case "versionCode":
307 versionCode = attrs.getAttributeIntValue(i, 0);
308 break;
309 case "versionCodeMajor":
310 versionCodeMajor = attrs.getAttributeIntValue(i, 0);
311 break;
312 case "revisionCode":
313 revisionCode = attrs.getAttributeIntValue(i, 0);
314 break;
315 case "coreApp":
316 coreApp = attrs.getAttributeBooleanValue(i, false);
317 break;
318 case "isolatedSplits":
319 isolatedSplits = attrs.getAttributeBooleanValue(i, false);
320 break;
321 case "configForSplit":
322 configForSplit = attrs.getAttributeValue(i);
323 break;
324 case "isFeatureSplit":
325 isFeatureSplit = attrs.getAttributeBooleanValue(i, false);
326 break;
327 case "isSplitRequired":
328 isSplitRequired = attrs.getAttributeBooleanValue(i, false);
329 break;
330 }
331 }
332
333 // Only search the tree when the tag is the direct child of <manifest> tag
334 int type;
335 final int searchDepth = parser.getDepth() + 1;
336
337 final List<VerifierInfo> verifiers = new ArrayList<>();
338 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
339 && (type != XmlPullParser.END_TAG || parser.getDepth() >= searchDepth)) {
340 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
341 continue;
342 }
343
344 if (parser.getDepth() != searchDepth) {
345 continue;
346 }
347
348 if (PackageParser.TAG_PACKAGE_VERIFIER.equals(parser.getName())) {
349 final VerifierInfo verifier = parseVerifier(attrs);
350 if (verifier != null) {
351 verifiers.add(verifier);
352 }
353 } else if (PackageParser.TAG_APPLICATION.equals(parser.getName())) {
354 for (int i = 0; i < attrs.getAttributeCount(); ++i) {
355 final String attr = attrs.getAttributeName(i);
356 switch (attr) {
357 case "debuggable":
358 debuggable = attrs.getAttributeBooleanValue(i, false);
359 break;
360 case "multiArch":
361 multiArch = attrs.getAttributeBooleanValue(i, false);
362 break;
363 case "use32bitAbi":
364 use32bitAbi = attrs.getAttributeBooleanValue(i, false);
365 break;
366 case "extractNativeLibs":
367 extractNativeLibs = attrs.getAttributeBooleanValue(i, true);
368 break;
369 case "useEmbeddedDex":
370 useEmbeddedDex = attrs.getAttributeBooleanValue(i, false);
371 break;
372 }
373 }
374 } else if (PackageParser.TAG_OVERLAY.equals(parser.getName())) {
375 for (int i = 0; i < attrs.getAttributeCount(); ++i) {
376 final String attr = attrs.getAttributeName(i);
377 if ("requiredSystemPropertyName".equals(attr)) {
378 requiredSystemPropertyName = attrs.getAttributeValue(i);
379 } else if ("requiredSystemPropertyValue".equals(attr)) {
380 requiredSystemPropertyValue = attrs.getAttributeValue(i);
381 } else if ("targetPackage".equals(attr)) {
382 targetPackage = attrs.getAttributeValue(i);;
383 } else if ("isStatic".equals(attr)) {
384 overlayIsStatic = attrs.getAttributeBooleanValue(i, false);
385 } else if ("priority".equals(attr)) {
386 overlayPriority = attrs.getAttributeIntValue(i, 0);
387 }
388 }
389 } else if (PackageParser.TAG_USES_SPLIT.equals(parser.getName())) {
390 if (usesSplitName != null) {
391 Slog.w(TAG, "Only one <uses-split> permitted. Ignoring others.");
392 continue;
393 }
394
395 usesSplitName = attrs.getAttributeValue(PackageParser.ANDROID_RESOURCES, "name");
396 if (usesSplitName == null) {
397 throw new PackageParser.PackageParserException(
398 PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
399 "<uses-split> tag requires 'android:name' attribute");
400 }
401 } else if (PackageParser.TAG_USES_SDK.equals(parser.getName())) {
402 for (int i = 0; i < attrs.getAttributeCount(); ++i) {
403 final String attr = attrs.getAttributeName(i);
404 if ("targetSdkVersion".equals(attr)) {
405 targetSdkVersion = attrs.getAttributeIntValue(i,
406 DEFAULT_TARGET_SDK_VERSION);
407 }
408 if ("minSdkVersion".equals(attr)) {
409 minSdkVersion = attrs.getAttributeIntValue(i, DEFAULT_MIN_SDK_VERSION);
410 }
411 }
412 }
413 }
414
415 // Check to see if overlay should be excluded based on system property condition
416 if (!PackageParser.checkRequiredSystemProperties(requiredSystemPropertyName,
417 requiredSystemPropertyValue)) {
418 Slog.i(TAG, "Skipping target and overlay pair " + targetPackage + " and "
419 + codePath + ": overlay ignored due to required system property: "
420 + requiredSystemPropertyName + " with value: " + requiredSystemPropertyValue);
421 targetPackage = null;
422 overlayIsStatic = false;
423 overlayPriority = 0;
424 }
425
426 return new PackageParser.ApkLite(codePath, packageSplit.first, packageSplit.second,
427 isFeatureSplit, configForSplit, usesSplitName, isSplitRequired, versionCode,
428 versionCodeMajor, revisionCode, installLocation, verifiers, signingDetails,
429 coreApp, debuggable, multiArch, use32bitAbi, useEmbeddedDex, extractNativeLibs,
430 isolatedSplits, targetPackage, overlayIsStatic, overlayPriority, minSdkVersion,
431 targetSdkVersion);
432 }
433
434 public static VerifierInfo parseVerifier(AttributeSet attrs) {
435 String packageName = null;
436 String encodedPublicKey = null;
437
438 final int attrCount = attrs.getAttributeCount();
439 for (int i = 0; i < attrCount; i++) {
440 final int attrResId = attrs.getAttributeNameResource(i);
441 switch (attrResId) {
442 case R.attr.name:
443 packageName = attrs.getAttributeValue(i);
444 break;
445
446 case R.attr.publicKey:
447 encodedPublicKey = attrs.getAttributeValue(i);
448 break;
449 }
450 }
451
452 if (packageName == null || packageName.length() == 0) {
453 Slog.i(TAG, "verifier package name was null; skipping");
454 return null;
455 }
456
457 final PublicKey publicKey = PackageParser.parsePublicKey(encodedPublicKey);
458 if (publicKey == null) {
459 Slog.i(TAG, "Unable to parse verifier public key for " + packageName);
460 return null;
461 }
462
463 return new VerifierInfo(packageName, publicKey);
464 }
465}