• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 
17 package android.content.pm.parsing;
18 
19 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME;
20 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
21 import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
22 
23 import android.annotation.NonNull;
24 import android.app.admin.DeviceAdminReceiver;
25 import android.content.pm.ApplicationInfo;
26 import android.content.pm.PackageInfo;
27 import android.content.pm.PackageManager;
28 import android.content.pm.SharedLibraryInfo;
29 import android.content.pm.SigningDetails;
30 import android.content.pm.VerifierInfo;
31 import android.content.pm.parsing.result.ParseInput;
32 import android.content.pm.parsing.result.ParseResult;
33 import android.content.res.ApkAssets;
34 import android.content.res.XmlResourceParser;
35 import android.os.Build;
36 import android.os.SystemProperties;
37 import android.os.Trace;
38 import android.text.TextUtils;
39 import android.util.ArrayMap;
40 import android.util.ArraySet;
41 import android.util.AttributeSet;
42 import android.util.EmptyArray;
43 import android.util.Pair;
44 import android.util.Slog;
45 
46 import com.android.internal.pm.pkg.component.flags.Flags;
47 import com.android.internal.util.ArrayUtils;
48 import com.android.internal.util.XmlUtils;
49 
50 import libcore.io.IoUtils;
51 import libcore.util.HexEncoding;
52 
53 import org.xmlpull.v1.XmlPullParser;
54 import org.xmlpull.v1.XmlPullParserException;
55 
56 import java.io.File;
57 import java.io.FileDescriptor;
58 import java.io.IOException;
59 import java.security.PublicKey;
60 import java.util.ArrayList;
61 import java.util.Arrays;
62 import java.util.Comparator;
63 import java.util.List;
64 import java.util.Objects;
65 import java.util.Set;
66 
67 /** @hide */
68 public class ApkLiteParseUtils {
69 
70     private static final String TAG = "ApkLiteParseUtils";
71 
72     private static final int PARSE_DEFAULT_INSTALL_LOCATION =
73             PackageInfo.INSTALL_LOCATION_UNSPECIFIED;
74 
75     private static final Comparator<String> sSplitNameComparator = new SplitNameComparator();
76 
77     public static final String APK_FILE_EXTENSION = ".apk";
78 
79 
80     // Constants copied from services.jar side since they're not accessible
81     private static final String ANDROID_RES_NAMESPACE =
82             "http://schemas.android.com/apk/res/android";
83     public static final int DEFAULT_MIN_SDK_VERSION = 1;
84     private static final int DEFAULT_TARGET_SDK_VERSION = 0;
85     public static final String ANDROID_MANIFEST_FILENAME = "AndroidManifest.xml";
86     private static final int PARSE_IS_SYSTEM_DIR = 1 << 4;
87     private static final int PARSE_COLLECT_CERTIFICATES = 1 << 5;
88     private static final String TAG_APPLICATION = "application";
89     private static final String TAG_PACKAGE_VERIFIER = "package-verifier";
90     private static final String TAG_PROFILEABLE = "profileable";
91     private static final String TAG_RECEIVER = "receiver";
92     private static final String TAG_OVERLAY = "overlay";
93     private static final String TAG_USES_SDK = "uses-sdk";
94     private static final String TAG_USES_SPLIT = "uses-split";
95     private static final String TAG_MANIFEST = "manifest";
96     private static final String TAG_USES_SDK_LIBRARY = "uses-sdk-library";
97     private static final String TAG_USES_STATIC_LIBRARY = "uses-static-library";
98     private static final String TAG_SDK_LIBRARY = "sdk-library";
99     private static final int SDK_VERSION = Build.VERSION.SDK_INT;
100     private static final String[] SDK_CODENAMES = Build.VERSION.ACTIVE_CODENAMES;
101     private static final String TAG_PROCESSES = "processes";
102     private static final String TAG_PROCESS = "process";
103     private static final String TAG_STATIC_LIBRARY = "static-library";
104     private static final String TAG_LIBRARY = "library";
105 
106     /**
107      * Parse only lightweight details about the package at the given location.
108      * Automatically detects if the package is a monolithic style (single APK
109      * file) or cluster style (directory of APKs).
110      * <p>
111      * This performs validity checking on cluster style packages, such as
112      * requiring identical package name and version codes, a single base APK,
113      * and unique split names.
114      */
parsePackageLite(ParseInput input, File packageFile, int flags)115     public static ParseResult<PackageLite> parsePackageLite(ParseInput input,
116             File packageFile, int flags) {
117         if (packageFile.isDirectory()) {
118             return parseClusterPackageLite(input, packageFile, flags);
119         } else {
120             return parseMonolithicPackageLite(input, packageFile, flags);
121         }
122     }
123 
124     /**
125      * Parse lightweight details about a single APK file.
126      */
parseMonolithicPackageLite(ParseInput input, File packageFile, int flags)127     public static ParseResult<PackageLite> parseMonolithicPackageLite(ParseInput input,
128             File packageFile, int flags) {
129         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parseApkLite");
130         try {
131             final ParseResult<ApkLite> result = parseApkLite(input, packageFile, flags);
132             if (result.isError()) {
133                 return input.error(result);
134             }
135 
136             final ApkLite baseApk = result.getResult();
137             final String packagePath = packageFile.getAbsolutePath();
138             return input.success(
139                     new PackageLite(packagePath, baseApk.getPath(), baseApk, null /* splitNames */,
140                             null /* isFeatureSplits */, null /* usesSplitNames */,
141                             null /* configForSplit */, null /* splitApkPaths */,
142                             null /* splitRevisionCodes */, baseApk.getTargetSdkVersion(),
143                             null /* requiredSplitTypes */, null /* splitTypes */));
144         } finally {
145             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
146         }
147     }
148 
149     /**
150      * Parse lightweight details about a single APK file passed as an FD.
151      */
parseMonolithicPackageLite(ParseInput input, FileDescriptor packageFd, String debugPathName, int flags)152     public static ParseResult<PackageLite> parseMonolithicPackageLite(ParseInput input,
153             FileDescriptor packageFd, String debugPathName, int flags) {
154         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parseApkLite");
155         try {
156             final ParseResult<ApkLite> result = parseApkLite(input, packageFd, debugPathName,
157                     flags);
158             if (result.isError()) {
159                 return input.error(result);
160             }
161 
162             final ApkLite baseApk = result.getResult();
163             final String packagePath = debugPathName;
164             return input.success(
165                     new PackageLite(packagePath, baseApk.getPath(), baseApk, null /* splitNames */,
166                             null /* isFeatureSplits */, null /* usesSplitNames */,
167                             null /* configForSplit */, null /* splitApkPaths */,
168                             null /* splitRevisionCodes */, baseApk.getTargetSdkVersion(),
169                             null /* requiredSplitTypes */, null /* splitTypes */));
170         } finally {
171             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
172         }
173     }
174 
175     /**
176      * Parse lightweight details about a directory of APKs.
177      *
178      * @param packageDir is the folder that contains split apks for a regular app
179      */
parseClusterPackageLite(ParseInput input, File packageDir, int flags)180     public static ParseResult<PackageLite> parseClusterPackageLite(ParseInput input,
181             File packageDir, int flags) {
182         final File[] files;
183         files = packageDir.listFiles();
184         if (ArrayUtils.isEmpty(files)) {
185             return input.error(PackageManager.INSTALL_PARSE_FAILED_NOT_APK,
186                     "No packages found in split");
187         }
188         // Apk directory is directly nested under the current directory
189         if (files.length == 1 && files[0].isDirectory()) {
190             return parseClusterPackageLite(input, files[0], flags);
191         }
192 
193         String packageName = null;
194         int versionCode = 0;
195         ApkLite baseApk = null;
196 
197         final ArrayMap<String, ApkLite> apks = new ArrayMap<>();
198         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parseApkLite");
199         try {
200             for (File file : files) {
201                 if (!isApkFile(file)) {
202                     continue;
203                 }
204 
205                 final ParseResult<ApkLite> result = parseApkLite(input, file, flags);
206                 if (result.isError()) {
207                     return input.error(result);
208                 }
209 
210                 final ApkLite lite = result.getResult();
211                 // Assert that all package names and version codes are
212                 // consistent with the first one we encounter.
213                 if (packageName == null) {
214                     packageName = lite.getPackageName();
215                     versionCode = lite.getVersionCode();
216                 } else {
217                     if (!packageName.equals(lite.getPackageName())) {
218                         return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST,
219                                 "Inconsistent package " + lite.getPackageName() + " in " + file
220                                         + "; expected " + packageName);
221                     }
222                     if (versionCode != lite.getVersionCode()) {
223                         return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST,
224                                 "Inconsistent version " + lite.getVersionCode() + " in " + file
225                                         + "; expected " + versionCode);
226                     }
227                 }
228 
229                 // Assert that each split is defined only once
230                 ApkLite prev = apks.put(lite.getSplitName(), lite);
231                 if (prev != null) {
232                     return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST,
233                             "Split name " + lite.getSplitName()
234                                     + " defined more than once; most recent was " + file
235                                     + ", previous was " + prev.getPath());
236                 }
237             }
238             baseApk = apks.remove(null);
239         } finally {
240             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
241         }
242         return composePackageLiteFromApks(input, packageDir, baseApk, apks);
243     }
244 
245     /**
246      * Utility method that retrieves lightweight details about the package by given location,
247      * base APK, and split APKs.
248      *
249      * @param packageDir Path to the package
250      * @param baseApk Parsed base APK
251      * @param splitApks Parsed split APKs
252      * @return PackageLite
253      */
composePackageLiteFromApks(ParseInput input, File packageDir, ApkLite baseApk, ArrayMap<String, ApkLite> splitApks)254     public static ParseResult<PackageLite> composePackageLiteFromApks(ParseInput input,
255             File packageDir, ApkLite baseApk, ArrayMap<String, ApkLite> splitApks) {
256         return composePackageLiteFromApks(input, packageDir, baseApk, splitApks, false);
257     }
258 
259     /**
260      * Utility method that retrieves lightweight details about the package by given location,
261      * base APK, and split APKs.
262      *
263      * @param packageDir Path to the package
264      * @param baseApk Parsed base APK
265      * @param splitApks Parsed split APKs
266      * @param apkRenamed Indicate whether the APKs are renamed after parsed.
267      * @return PackageLite
268      */
composePackageLiteFromApks( ParseInput input, File packageDir, ApkLite baseApk, ArrayMap<String, ApkLite> splitApks, boolean apkRenamed)269     public static ParseResult<PackageLite> composePackageLiteFromApks(
270             ParseInput input, File packageDir, ApkLite baseApk,
271             ArrayMap<String, ApkLite> splitApks, boolean apkRenamed) {
272         if (baseApk == null) {
273             return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST,
274                     "Missing base APK in " + packageDir);
275         }
276         // Always apply deterministic ordering based on splitName
277         final int size = ArrayUtils.size(splitApks);
278 
279         String[] splitNames = null;
280         Set<String>[] requiredSplitTypes = null;
281         Set<String>[] splitTypes = null;
282         boolean[] isFeatureSplits = null;
283         String[] usesSplitNames = null;
284         String[] configForSplits = null;
285         String[] splitCodePaths = null;
286         int[] splitRevisionCodes = null;
287         if (size > 0) {
288             splitNames = new String[size];
289             requiredSplitTypes = new Set[size];
290             splitTypes = new Set[size];
291             isFeatureSplits = new boolean[size];
292             usesSplitNames = new String[size];
293             configForSplits = new String[size];
294             splitCodePaths = new String[size];
295             splitRevisionCodes = new int[size];
296 
297             splitNames = splitApks.keySet().toArray(splitNames);
298             Arrays.sort(splitNames, sSplitNameComparator);
299 
300             for (int i = 0; i < size; i++) {
301                 final ApkLite apk = splitApks.get(splitNames[i]);
302                 requiredSplitTypes[i] = apk.getRequiredSplitTypes();
303                 splitTypes[i] = apk.getSplitTypes();
304                 usesSplitNames[i] = apk.getUsesSplitName();
305                 isFeatureSplits[i] = apk.isFeatureSplit();
306                 configForSplits[i] = apk.getConfigForSplit();
307                 splitCodePaths[i] = apkRenamed ? new File(packageDir,
308                         splitNameToFileName(apk)).getAbsolutePath() : apk.getPath();
309                 splitRevisionCodes[i] = apk.getRevisionCode();
310             }
311         }
312 
313         final String codePath = packageDir.getAbsolutePath();
314         final String baseCodePath = apkRenamed ? new File(packageDir,
315                 splitNameToFileName(baseApk)).getAbsolutePath() : baseApk.getPath();
316         return input.success(
317                 new PackageLite(codePath, baseCodePath, baseApk, splitNames, isFeatureSplits,
318                         usesSplitNames, configForSplits, splitCodePaths, splitRevisionCodes,
319                         baseApk.getTargetSdkVersion(), requiredSplitTypes, splitTypes));
320     }
321 
322     /**
323      * Utility method that retrieves canonical file name by given split name from parsed APK.
324      *
325      * @param apk Parsed APK
326      * @return The canonical file name
327      */
splitNameToFileName(@onNull ApkLite apk)328     public static String splitNameToFileName(@NonNull ApkLite apk) {
329         Objects.requireNonNull(apk);
330         final String fileName = apk.getSplitName() == null ? "base" : "split_" + apk.getSplitName();
331         return fileName + APK_FILE_EXTENSION;
332     }
333 
334     /**
335      * Utility method that retrieves lightweight details about a single APK
336      * file, including package name, split name, and install location.
337      *
338      * @param apkFile path to a single APK
339      * @param flags optional parse flags, such as
340      *            {@link ParsingPackageUtils#PARSE_COLLECT_CERTIFICATES}
341      */
parseApkLite(ParseInput input, File apkFile, int flags)342     public static ParseResult<ApkLite> parseApkLite(ParseInput input, File apkFile, int flags) {
343         return parseApkLiteInner(input, apkFile, null, null, flags);
344     }
345 
346     /**
347      * Utility method that retrieves lightweight details about a single APK
348      * file, including package name, split name, and install location.
349      *
350      * @param fd already open file descriptor of an apk file
351      * @param debugPathName arbitrary text name for this file, for debug output
352      * @param flags optional parse flags, such as
353      *            {@link ParsingPackageUtils#PARSE_COLLECT_CERTIFICATES}
354      */
parseApkLite(ParseInput input, FileDescriptor fd, String debugPathName, int flags)355     public static ParseResult<ApkLite> parseApkLite(ParseInput input,
356             FileDescriptor fd, String debugPathName, int flags) {
357         return parseApkLiteInner(input, null, fd, debugPathName, flags);
358     }
359 
parseApkLiteInner(ParseInput input, File apkFile, FileDescriptor fd, String debugPathName, int flags)360     private static ParseResult<ApkLite> parseApkLiteInner(ParseInput input,
361             File apkFile, FileDescriptor fd, String debugPathName, int flags) {
362         final String apkPath = fd != null ? debugPathName : apkFile.getAbsolutePath();
363 
364         XmlResourceParser parser = null;
365         ApkAssets apkAssets = null;
366         try {
367             try {
368                 apkAssets = fd != null
369                         ? ApkAssets.loadFromFd(fd, debugPathName, 0 /* flags */, null /* assets */)
370                         : ApkAssets.loadFromPath(apkPath);
371             } catch (IOException e) {
372                 return input.error(PackageManager.INSTALL_PARSE_FAILED_NOT_APK,
373                         "Failed to parse " + apkPath, e);
374             }
375 
376             parser = apkAssets.openXml(ANDROID_MANIFEST_FILENAME);
377 
378             final SigningDetails signingDetails;
379             if ((flags & PARSE_COLLECT_CERTIFICATES) != 0) {
380                 final boolean skipVerify = (flags & PARSE_IS_SYSTEM_DIR) != 0;
381                 Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "collectCertificates");
382                 try {
383                     final ParseResult<SigningDetails> result =
384                             FrameworkParsingPackageUtils.getSigningDetails(input,
385                                     apkFile.getAbsolutePath(),
386                                     skipVerify, /* isStaticSharedLibrary */ false,
387                                     SigningDetails.UNKNOWN, DEFAULT_TARGET_SDK_VERSION);
388                     if (result.isError()) {
389                         return input.error(result);
390                     }
391                     signingDetails = result.getResult();
392                 } finally {
393                     Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
394                 }
395             } else {
396                 signingDetails = SigningDetails.UNKNOWN;
397             }
398 
399             return parseApkLite(input, apkPath, parser, signingDetails, flags);
400         } catch (XmlPullParserException | IOException | RuntimeException e) {
401             Slog.w(TAG, "Failed to parse " + apkPath, e);
402             return input.error(PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
403                     "Failed to parse " + apkPath, e);
404         } finally {
405             IoUtils.closeQuietly(parser);
406             if (apkAssets != null) {
407                 try {
408                     apkAssets.close();
409                 } catch (Throwable ignored) {
410                 }
411             }
412             // TODO(b/72056911): Implement AutoCloseable on ApkAssets.
413         }
414     }
415 
parseApkLite(ParseInput input, String codePath, XmlResourceParser parser, SigningDetails signingDetails, int flags)416     private static ParseResult<ApkLite> parseApkLite(ParseInput input, String codePath,
417             XmlResourceParser parser, SigningDetails signingDetails, int flags)
418             throws IOException, XmlPullParserException {
419         ParseResult<Pair<String, String>> result = parsePackageSplitNames(input, parser);
420         if (result.isError()) {
421             return input.error(result);
422         }
423         Pair<String, String> packageSplit = result.getResult();
424 
425         final ParseResult<Pair<Set<String>, Set<String>>> requiredSplitTypesResult =
426                 parseRequiredSplitTypes(input, parser);
427         if (requiredSplitTypesResult.isError()) {
428             return input.error(result);
429         }
430         Pair<Set<String>, Set<String>> requiredSplitTypes = requiredSplitTypesResult.getResult();
431 
432         int installLocation = parser.getAttributeIntValue(ANDROID_RES_NAMESPACE,
433                 "installLocation", PARSE_DEFAULT_INSTALL_LOCATION);
434         int versionCode = parser.getAttributeIntValue(ANDROID_RES_NAMESPACE, "versionCode", 0);
435         int versionCodeMajor = parser.getAttributeIntValue(ANDROID_RES_NAMESPACE,
436                 "versionCodeMajor",
437                 0);
438         int revisionCode = parser.getAttributeIntValue(ANDROID_RES_NAMESPACE, "revisionCode", 0);
439         boolean coreApp = parser.getAttributeBooleanValue(null, "coreApp", false);
440         boolean updatableSystem = parser.getAttributeBooleanValue(null, "updatableSystem", true);
441         boolean isolatedSplits = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE,
442                 "isolatedSplits", false);
443         boolean isFeatureSplit = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE,
444                 "isFeatureSplit", false);
445         boolean isSplitRequired = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE,
446                 "isSplitRequired", false);
447         String configForSplit = parser.getAttributeValue(null, "configForSplit");
448         String emergencyInstaller = parser.getAttributeValue(null, "emergencyInstaller");
449 
450         int targetSdkVersion = DEFAULT_TARGET_SDK_VERSION;
451         int minSdkVersion = DEFAULT_MIN_SDK_VERSION;
452         boolean debuggable = false;
453         boolean profilableByShell = false;
454         boolean multiArch = false;
455         boolean use32bitAbi = false;
456         boolean extractNativeLibs = true;
457         boolean useEmbeddedDex = false;
458         String usesSplitName = null;
459         String targetPackage = null;
460         boolean overlayIsStatic = false;
461         int overlayPriority = 0;
462         int rollbackDataPolicy = 0;
463         int pageSizeCompat = ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_UNDEFINED;
464 
465         String requiredSystemPropertyName = null;
466         String requiredSystemPropertyValue = null;
467 
468         boolean hasDeviceAdminReceiver = false;
469 
470         boolean isSdkLibrary = false;
471         boolean isStaticLibrary = false;
472         List<String> usesSdkLibraries = new ArrayList<>();
473         long[] usesSdkLibrariesVersionsMajor = new long[0];
474         String[][] usesSdkLibrariesCertDigests = new String[0][0];
475 
476         List<String> usesStaticLibraries = new ArrayList<>();
477         long[] usesStaticLibrariesVersions = new long[0];
478         String[][] usesStaticLibrariesCertDigests = new String[0][0];
479 
480         List<SharedLibraryInfo> declaredLibraries = new ArrayList<>();
481 
482         // Only search the tree when the tag is the direct child of <manifest> tag
483         int type;
484         final int searchDepth = parser.getDepth() + 1;
485 
486         final List<VerifierInfo> verifiers = new ArrayList<>();
487         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
488                 && (type != XmlPullParser.END_TAG || parser.getDepth() >= searchDepth)) {
489             if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
490                 continue;
491             }
492 
493             if (parser.getDepth() != searchDepth) {
494                 continue;
495             }
496 
497             if (TAG_PACKAGE_VERIFIER.equals(parser.getName())) {
498                 final VerifierInfo verifier = parseVerifier(parser);
499                 if (verifier != null) {
500                     verifiers.add(verifier);
501                 }
502             } else if (TAG_APPLICATION.equals(parser.getName())) {
503                 debuggable = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE, "debuggable",
504                         false);
505                 multiArch = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE, "multiArch",
506                         false);
507                 use32bitAbi = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE, "use32bitAbi",
508                         false);
509                 extractNativeLibs = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE,
510                         "extractNativeLibs", true);
511                 useEmbeddedDex = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE,
512                         "useEmbeddedDex", false);
513 
514                 rollbackDataPolicy = parser.getAttributeIntValue(ANDROID_RES_NAMESPACE,
515                         "rollbackDataPolicy", 0);
516                 String permission = parser.getAttributeValue(ANDROID_RES_NAMESPACE,
517                         "permission");
518                 boolean hasBindDeviceAdminPermission =
519                         android.Manifest.permission.BIND_DEVICE_ADMIN.equals(permission);
520 
521                 pageSizeCompat = parser.getAttributeIntValue(ANDROID_RES_NAMESPACE,
522                         "pageSizeCompat",
523                         ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_UNDEFINED);
524 
525                 final int innerDepth = parser.getDepth();
526                 int innerType;
527                 while ((innerType = parser.next()) != XmlPullParser.END_DOCUMENT
528                         && (innerType != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
529                     if (innerType == XmlPullParser.END_TAG || innerType == XmlPullParser.TEXT) {
530                         continue;
531                     }
532 
533                     if (parser.getDepth() != innerDepth + 1) {
534                         // Search only under <application>.
535                         continue;
536                     }
537 
538                     switch (parser.getName()) {
539                         case TAG_PROFILEABLE:
540                             profilableByShell = parser.getAttributeBooleanValue(
541                                     ANDROID_RES_NAMESPACE, "shell", profilableByShell);
542                             break;
543                         case TAG_RECEIVER:
544                             hasDeviceAdminReceiver |= isDeviceAdminReceiver(parser,
545                                     hasBindDeviceAdminPermission);
546                             break;
547                         case TAG_USES_SDK_LIBRARY:
548                             String usesSdkLibName = parser.getAttributeValue(
549                                     ANDROID_RES_NAMESPACE, "name");
550                             // TODO(b/391604666): Due to a bug in bundletool, old apps could be
551                             //  using versionMajor as string. Do not remove this workaround until
552                             //  b/391604666 is resolved.
553                             String usesSdkLibVersionMajorString = parser.getAttributeValue(
554                                     ANDROID_RES_NAMESPACE, "versionMajor");
555                             long usesSdkLibVersionMajor = XmlUtils.convertValueToInt(
556                                     usesSdkLibVersionMajorString, -1);
557                             String usesSdkCertDigest = parser.getAttributeValue(
558                                      ANDROID_RES_NAMESPACE, "certDigest");
559 
560                             if (usesSdkLibName == null || usesSdkLibName.isBlank()
561                                     || usesSdkLibVersionMajor < 0) {
562                                 return input.error(
563                                         PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
564                                         "Bad uses-sdk-library declaration name: "
565                                                 + usesSdkLibName
566                                                 + " version: " + usesSdkLibVersionMajor);
567                             }
568 
569                             if (usesSdkLibraries.contains(usesSdkLibName)) {
570                                 return input.error(
571                                         PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
572                                         "Bad uses-sdk-library declaration. Depending on"
573                                                 + " multiple versions of SDK library: "
574                                                 + usesSdkLibName);
575                             }
576 
577                             usesSdkLibraries.add(usesSdkLibName);
578                             usesSdkLibrariesVersionsMajor = ArrayUtils.appendLong(
579                                     usesSdkLibrariesVersionsMajor, usesSdkLibVersionMajor,
580                                     /*allowDuplicates=*/ true);
581 
582                             usesSdkCertDigest = normalizeCertDigest(usesSdkCertDigest);
583 
584                             if ("".equals(usesSdkCertDigest)) {
585                                 // Test-only uses-sdk-library empty certificate digest override.
586                                 usesSdkCertDigest = SystemProperties.get(
587                                         "debug.pm.uses_sdk_library_default_cert_digest", "");
588                                 // Validate the overridden digest.
589                                 try {
590                                     HexEncoding.decode(usesSdkCertDigest, false);
591                                 } catch (IllegalArgumentException e) {
592                                     usesSdkCertDigest = "";
593                                 }
594                             }
595                             // TODO(372862145): Add support for multiple signer
596                             usesSdkLibrariesCertDigests = ArrayUtils.appendElement(String[].class,
597                                     usesSdkLibrariesCertDigests, new String[]{usesSdkCertDigest},
598                                     /*allowDuplicates=*/ true);
599                             break;
600                         case TAG_USES_STATIC_LIBRARY:
601                             String usesStaticLibName = parser.getAttributeValue(
602                                     ANDROID_RES_NAMESPACE, "name");
603                             long usesStaticLibVersion = parser.getAttributeIntValue(
604                                     ANDROID_RES_NAMESPACE, "version", -1);
605                             String usesStaticLibCertDigest = parser.getAttributeValue(
606                                     ANDROID_RES_NAMESPACE, "certDigest");
607 
608                             if (usesStaticLibName == null || usesStaticLibName.isBlank()
609                                     || usesStaticLibVersion < 0
610                                     || usesStaticLibCertDigest == null) {
611                                 return input.error(
612                                         PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
613                                         "Bad uses-static-library declaration name: "
614                                                 + usesStaticLibName
615                                                 + " version: " + usesStaticLibVersion
616                                                 + " certDigest: " + usesStaticLibCertDigest);
617                             }
618 
619                             if (usesStaticLibraries.contains(usesStaticLibName)) {
620                                 return input.error(
621                                         PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
622                                         "Bad uses-sdk-library declaration. Depending on"
623                                                 + " multiple versions of static library: "
624                                                 + usesStaticLibName);
625                             }
626 
627                             usesStaticLibraries.add(usesStaticLibName);
628                             usesStaticLibrariesVersions = ArrayUtils.appendLong(
629                                     usesStaticLibrariesVersions, usesStaticLibVersion,
630                                     /*allowDuplicates=*/ true);
631 
632                             usesStaticLibCertDigest = normalizeCertDigest(usesStaticLibCertDigest);
633 
634                             ParseResult<String[]> certResult =
635                                     parseAdditionalCertificates(input, parser);
636                             if (certResult.isError()) {
637                                 return input.error(certResult);
638                             }
639                             String[] additionalCertSha256Digests = certResult.getResult();
640                             String[] certSha256Digests =
641                                     new String[additionalCertSha256Digests.length + 1];
642                             certSha256Digests[0] = usesStaticLibCertDigest;
643                             System.arraycopy(additionalCertSha256Digests, 0, certSha256Digests,
644                                     1, additionalCertSha256Digests.length);
645 
646                             usesStaticLibrariesCertDigests = ArrayUtils.appendElement(
647                                     String[].class, usesStaticLibrariesCertDigests,
648                                     certSha256Digests, /*allowDuplicates=*/ true);
649                             break;
650                         case TAG_SDK_LIBRARY:
651                             isSdkLibrary = true;
652                             // Mirrors ParsingPackageUtils#parseSdkLibrary until lite and full
653                             // parsing are combined
654                             String sdkLibName = parser.getAttributeValue(
655                                     ANDROID_RES_NAMESPACE, "name");
656                             int sdkLibVersionMajor = parser.getAttributeIntValue(
657                                         ANDROID_RES_NAMESPACE, "versionMajor", -1);
658                             if (sdkLibName == null || sdkLibVersionMajor < 0) {
659                                 return input.error(
660                                         PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
661                                         "Bad sdk-library declaration name: " + sdkLibName
662                                                 + " version: " + sdkLibVersionMajor);
663                             }
664                             declaredLibraries.add(new SharedLibraryInfo(
665                                     sdkLibName, sdkLibVersionMajor,
666                                     SharedLibraryInfo.TYPE_SDK_PACKAGE));
667                             break;
668                         case TAG_STATIC_LIBRARY:
669                             isStaticLibrary = true;
670                             // Mirrors ParsingPackageUtils#parseStaticLibrary until lite and full
671                             // parsing are combined
672                             String staticLibName = parser.getAttributeValue(
673                                     ANDROID_RES_NAMESPACE, "name");
674                             int staticLibVersion = parser.getAttributeIntValue(
675                                     ANDROID_RES_NAMESPACE, "version", -1);
676                             int staticLibVersionMajor = parser.getAttributeIntValue(
677                                     ANDROID_RES_NAMESPACE, "versionMajor", 0);
678                             if (staticLibName == null || staticLibVersion < 0) {
679                                 return input.error("Bad static-library declaration name: "
680                                         + staticLibName + " version: " + staticLibVersion);
681                             }
682                             declaredLibraries.add(new SharedLibraryInfo(staticLibName,
683                                     PackageInfo.composeLongVersionCode(staticLibVersionMajor,
684                                             staticLibVersion), SharedLibraryInfo.TYPE_STATIC));
685                             break;
686                         case TAG_LIBRARY:
687                             // Mirrors ParsingPackageUtils#parseLibrary until lite and full parsing
688                             // are combined
689                             String libName = parser.getAttributeValue(
690                                     ANDROID_RES_NAMESPACE, "name");
691                             if (libName == null) {
692                                 return input.error("Bad library declaration name: null");
693                             }
694                             libName = libName.intern();
695                             declaredLibraries.add(new SharedLibraryInfo(libName,
696                                     SharedLibraryInfo.VERSION_UNDEFINED,
697                                     SharedLibraryInfo.TYPE_DYNAMIC));
698                             break;
699                         case TAG_PROCESSES:
700                             final int processesDepth = parser.getDepth();
701                             int processesType;
702                             while ((processesType = parser.next()) != XmlPullParser.END_DOCUMENT
703                                     && (processesType != XmlPullParser.END_TAG
704                                     || parser.getDepth() > processesDepth)) {
705                                 if (processesType == XmlPullParser.END_TAG
706                                         || processesType == XmlPullParser.TEXT) {
707                                     continue;
708                                 }
709 
710                                 if (parser.getDepth() != processesDepth + 1) {
711                                     // Search only under <processes>.
712                                     continue;
713                                 }
714 
715                                 if (parser.getName().equals(TAG_PROCESS)
716                                         && Flags.enablePerProcessUseEmbeddedDexAttr()) {
717                                     useEmbeddedDex |= parser.getAttributeBooleanValue(
718                                             ANDROID_RES_NAMESPACE, "useEmbeddedDex", false);
719                                 }
720                             }
721                     }
722                 }
723             } else if (TAG_OVERLAY.equals(parser.getName())) {
724                 requiredSystemPropertyName = parser.getAttributeValue(ANDROID_RES_NAMESPACE,
725                         "requiredSystemPropertyName");
726                 requiredSystemPropertyValue = parser.getAttributeValue(ANDROID_RES_NAMESPACE,
727                         "requiredSystemPropertyValue");
728                 targetPackage = parser.getAttributeValue(ANDROID_RES_NAMESPACE, "targetPackage");
729                 overlayIsStatic = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE, "isStatic",
730                         false);
731                 overlayPriority = parser.getAttributeIntValue(ANDROID_RES_NAMESPACE, "priority", 0);
732             } else if (TAG_USES_SPLIT.equals(parser.getName())) {
733                 if (usesSplitName != null) {
734                     Slog.w(TAG, "Only one <uses-split> permitted. Ignoring others.");
735                     continue;
736                 }
737 
738                 usesSplitName = parser.getAttributeValue(ANDROID_RES_NAMESPACE, "name");
739                 if (usesSplitName == null) {
740                     return input.error(PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
741                             "<uses-split> tag requires 'android:name' attribute");
742                 }
743             } else if (TAG_USES_SDK.equals(parser.getName())) {
744                 // Mirrors FrameworkParsingPackageUtils#parseUsesSdk until lite and full parsing is combined
745                 String minSdkVersionString = parser.getAttributeValue(ANDROID_RES_NAMESPACE,
746                         "minSdkVersion");
747                 String targetSdkVersionString = parser.getAttributeValue(ANDROID_RES_NAMESPACE,
748                         "targetSdkVersion");
749 
750                 int minVer = DEFAULT_MIN_SDK_VERSION;
751                 String minCode = null;
752                 boolean minAssigned = false;
753                 int targetVer = DEFAULT_TARGET_SDK_VERSION;
754                 String targetCode = null;
755 
756                 if (!TextUtils.isEmpty(minSdkVersionString)) {
757                     try {
758                         minVer = Integer.parseInt(minSdkVersionString);
759                         minAssigned = true;
760                     } catch (NumberFormatException ignored) {
761                         minCode = minSdkVersionString;
762                         minAssigned = !TextUtils.isEmpty(minCode);
763                     }
764                 }
765 
766                 if (!TextUtils.isEmpty(targetSdkVersionString)) {
767                     try {
768                         targetVer = Integer.parseInt(targetSdkVersionString);
769                     } catch (NumberFormatException ignored) {
770                         targetCode = targetSdkVersionString;
771                         if (!minAssigned) {
772                             minCode = targetCode;
773                         }
774                     }
775                 } else {
776                     targetVer = minVer;
777                     targetCode = minCode;
778                 }
779 
780                 boolean allowUnknownCodenames = false;
781                 if ((flags & FrameworkParsingPackageUtils.PARSE_APK_IN_APEX) != 0) {
782                     allowUnknownCodenames = true;
783                 }
784 
785                 ParseResult<Integer> targetResult = FrameworkParsingPackageUtils.computeTargetSdkVersion(
786                         targetVer, targetCode, SDK_CODENAMES, input,
787                         allowUnknownCodenames);
788                 if (targetResult.isError()) {
789                     return input.error(targetResult);
790                 }
791                 targetSdkVersion = targetResult.getResult();
792 
793                 ParseResult<Integer> minResult = FrameworkParsingPackageUtils.computeMinSdkVersion(
794                         minVer, minCode, SDK_VERSION, SDK_CODENAMES, input);
795                 if (minResult.isError()) {
796                     return input.error(minResult);
797                 }
798                 minSdkVersion = minResult.getResult();
799             }
800         }
801 
802         // Check to see if overlay should be excluded based on system property condition
803         if ((flags & FrameworkParsingPackageUtils.PARSE_IGNORE_OVERLAY_REQUIRED_SYSTEM_PROPERTY)
804                 == 0 && !FrameworkParsingPackageUtils.checkRequiredSystemProperties(
805                 requiredSystemPropertyName, requiredSystemPropertyValue)) {
806             String message = "Skipping target and overlay pair " + targetPackage + " and "
807                     + codePath + ": overlay ignored due to required system property: "
808                     + requiredSystemPropertyName + " with value: " + requiredSystemPropertyValue;
809             Slog.i(TAG, message);
810             return input.skip(message);
811         }
812 
813         return input.success(
814                 new ApkLite(codePath, packageSplit.first, packageSplit.second, isFeatureSplit,
815                         configForSplit, usesSplitName, isSplitRequired, versionCode,
816                         versionCodeMajor, revisionCode, installLocation, verifiers, signingDetails,
817                         coreApp, debuggable, profilableByShell, multiArch, use32bitAbi,
818                         useEmbeddedDex, extractNativeLibs, isolatedSplits, targetPackage,
819                         overlayIsStatic, overlayPriority, requiredSystemPropertyName,
820                         requiredSystemPropertyValue, minSdkVersion, targetSdkVersion,
821                         rollbackDataPolicy, requiredSplitTypes.first, requiredSplitTypes.second,
822                         hasDeviceAdminReceiver, isSdkLibrary, usesSdkLibraries,
823                         usesSdkLibrariesVersionsMajor, usesSdkLibrariesCertDigests, isStaticLibrary,
824                         usesStaticLibraries, usesStaticLibrariesVersions,
825                         usesStaticLibrariesCertDigests, updatableSystem, emergencyInstaller,
826                         declaredLibraries, pageSizeCompat));
827     }
828 
parseAdditionalCertificates(ParseInput input, XmlResourceParser parser)829     private static ParseResult<String[]> parseAdditionalCertificates(ParseInput input,
830             XmlResourceParser parser) throws XmlPullParserException, IOException {
831         String[] certSha256Digests = EmptyArray.STRING;
832         final int depth = parser.getDepth();
833         int type;
834         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
835                 && (type != XmlPullParser.END_TAG
836                 || parser.getDepth() > depth)) {
837             if (type != XmlPullParser.START_TAG) {
838                 continue;
839             }
840             final String nodeName = parser.getName();
841             if (nodeName.equals("additional-certificate")) {
842                 String certSha256Digest = parser.getAttributeValue(
843                         ANDROID_RES_NAMESPACE, "certDigest");
844                 if (TextUtils.isEmpty(certSha256Digest)) {
845                     return input.error("Bad additional-certificate declaration with empty"
846                             + " certDigest:" + certSha256Digest);
847                 }
848 
849                 certSha256Digest = normalizeCertDigest(certSha256Digest);
850                 certSha256Digests = ArrayUtils.appendElement(String.class,
851                         certSha256Digests, certSha256Digest);
852             }
853         }
854 
855         return input.success(certSha256Digests);
856     }
857 
858     /**
859      * We allow ":" delimiters in the SHA declaration as this is the format emitted by the
860      * certtool making it easy for developers to copy/paste.
861      */
normalizeCertDigest(String certDigest)862     private static String normalizeCertDigest(String certDigest) {
863         return certDigest.replace(":", "").toLowerCase();
864     }
865 
isDeviceAdminReceiver( XmlResourceParser parser, boolean applicationHasBindDeviceAdminPermission)866     private static boolean isDeviceAdminReceiver(
867             XmlResourceParser parser, boolean applicationHasBindDeviceAdminPermission)
868             throws XmlPullParserException, IOException {
869         String permission = parser.getAttributeValue(ANDROID_RES_NAMESPACE,
870                 "permission");
871         if (!applicationHasBindDeviceAdminPermission
872                 && !android.Manifest.permission.BIND_DEVICE_ADMIN.equals(permission)) {
873             return false;
874         }
875 
876         boolean hasDeviceAdminReceiver = false;
877         final int depth = parser.getDepth();
878         int type;
879         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
880                 && (type != XmlPullParser.END_TAG || parser.getDepth() > depth)) {
881             if (type == XmlPullParser.END_TAG
882                     || type == XmlPullParser.TEXT) {
883                 continue;
884             }
885             if (parser.getDepth() != depth + 1) {
886                 // Search only under <receiver>.
887                 continue;
888             }
889             if (!hasDeviceAdminReceiver && "meta-data".equals(parser.getName())) {
890                 String name = parser.getAttributeValue(ANDROID_RES_NAMESPACE,
891                         "name");
892                 if (DeviceAdminReceiver.DEVICE_ADMIN_META_DATA.equals(name)) {
893                     hasDeviceAdminReceiver = true;
894                 }
895             }
896         }
897         return hasDeviceAdminReceiver;
898     }
899 
parsePackageSplitNames(ParseInput input, XmlResourceParser parser)900     public static ParseResult<Pair<String, String>> parsePackageSplitNames(ParseInput input,
901             XmlResourceParser parser) throws IOException, XmlPullParserException {
902         int type;
903         while ((type = parser.next()) != XmlPullParser.START_TAG
904                 && type != XmlPullParser.END_DOCUMENT) {
905         }
906 
907         if (type != XmlPullParser.START_TAG) {
908             return input.error(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
909                     "No start tag found");
910         }
911         if (!parser.getName().equals(TAG_MANIFEST)) {
912             return input.error(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
913                     "No <manifest> tag");
914         }
915 
916         final String packageName = parser.getAttributeValue(null, "package");
917         if (!"android".equals(packageName)) {
918             final ParseResult<?> nameResult = FrameworkParsingPackageUtils.validateName(input,
919                     packageName, true, true);
920             if (nameResult.isError()) {
921                 return input.error(INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME,
922                         "Invalid manifest package: " + nameResult.getErrorMessage());
923             }
924         }
925 
926         String splitName = parser.getAttributeValue(null, "split");
927         if (splitName != null) {
928             if (splitName.length() == 0) {
929                 splitName = null;
930             } else {
931                 final ParseResult<?> nameResult = FrameworkParsingPackageUtils.validateName(input,
932                         splitName, false, false);
933                 if (nameResult.isError()) {
934                     return input.error(INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME,
935                             "Invalid manifest split: " + nameResult.getErrorMessage());
936                 }
937             }
938         }
939 
940         return input.success(Pair.create(packageName.intern(),
941                 (splitName != null) ? splitName.intern() : splitName));
942     }
943 
944     /**
945      * Utility method that parses attributes android:requiredSplitTypes and android:splitTypes.
946      */
parseRequiredSplitTypes( ParseInput input, XmlResourceParser parser)947     public static ParseResult<Pair<Set<String>, Set<String>>> parseRequiredSplitTypes(
948             ParseInput input, XmlResourceParser parser) {
949         Set<String> requiredSplitTypes = null;
950         Set<String> splitTypes = null;
951         String value = parser.getAttributeValue(ANDROID_RES_NAMESPACE, "requiredSplitTypes");
952         if (!TextUtils.isEmpty(value)) {
953             final ParseResult<Set<String>> result = separateAndValidateSplitTypes(input, value);
954             if (result.isError()) {
955                 return input.error(result);
956             }
957             requiredSplitTypes = result.getResult();
958         }
959 
960         value = parser.getAttributeValue(ANDROID_RES_NAMESPACE, "splitTypes");
961         if (!TextUtils.isEmpty(value)) {
962             final ParseResult<Set<String>> result = separateAndValidateSplitTypes(input, value);
963             if (result.isError()) {
964                 return input.error(result);
965             }
966             splitTypes = result.getResult();
967         }
968 
969         return input.success(Pair.create(requiredSplitTypes, splitTypes));
970     }
971 
separateAndValidateSplitTypes(ParseInput input, String values)972     private static ParseResult<Set<String>> separateAndValidateSplitTypes(ParseInput input,
973             String values) {
974         final Set<String> ret = new ArraySet<>();
975         for (String value : values.trim().split(",")) {
976             final String type = value.trim();
977             // Using requireFilename as true because it limits length of the name to the
978             // {@link #MAX_FILE_NAME_SIZE}.
979             final ParseResult<?> nameResult = FrameworkParsingPackageUtils.validateName(input, type,
980                     false /* requireSeparator */, true /* requireFilename */);
981             if (nameResult.isError()) {
982                 return input.error(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
983                         "Invalid manifest split types: " + nameResult.getErrorMessage());
984             }
985             if (!ret.add(type)) {
986                 Slog.w(TAG, type + " was defined multiple times");
987             }
988         }
989         return input.success(ret);
990     }
991 
parseVerifier(AttributeSet attrs)992     public static VerifierInfo parseVerifier(AttributeSet attrs) {
993         String packageName = attrs.getAttributeValue(ANDROID_RES_NAMESPACE, "name");
994         String encodedPublicKey = attrs.getAttributeValue(ANDROID_RES_NAMESPACE, "publicKey");
995 
996         if (packageName == null || packageName.length() == 0) {
997             Slog.i(TAG, "verifier package name was null; skipping");
998             return null;
999         }
1000 
1001         final PublicKey publicKey = FrameworkParsingPackageUtils.parsePublicKey(encodedPublicKey);
1002         if (publicKey == null) {
1003             Slog.i(TAG, "Unable to parse verifier public key for " + packageName);
1004             return null;
1005         }
1006 
1007         return new VerifierInfo(packageName, publicKey);
1008     }
1009 
1010     /**
1011      * Used to sort a set of APKs based on their split names, always placing the
1012      * base APK (with {@code null} split name) first.
1013      */
1014     private static class SplitNameComparator implements Comparator<String> {
1015         @Override
compare(String lhs, String rhs)1016         public int compare(String lhs, String rhs) {
1017             if (lhs == null) {
1018                 return -1;
1019             } else if (rhs == null) {
1020                 return 1;
1021             } else {
1022                 return lhs.compareTo(rhs);
1023             }
1024         }
1025     }
1026 
1027     /**
1028      * Check if the given file is an APK file.
1029      *
1030      * @param file the file to check.
1031      * @return {@code true} if the given file is an APK file.
1032      */
isApkFile(File file)1033     public static boolean isApkFile(File file) {
1034         return isApkPath(file.getName());
1035     }
1036 
1037     /**
1038      * Check if the given path ends with APK file extension.
1039      *
1040      * @param path the path to check.
1041      * @return {@code true} if the given path ends with APK file extension.
1042      */
isApkPath(String path)1043     public static boolean isApkPath(String path) {
1044         return path.endsWith(APK_FILE_EXTENSION);
1045     }
1046 }
1047