blob: 20ec9b5efa987dc3bbbeb3a17bef9d04e7b4fa9a [file] [log] [blame]
David Brazdil6b4736d2016-02-04 11:54:17 +00001/*
2 * Copyright (C) 2016 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 com.android.server.pm;
18
Todd Kennedy7c4c55d2017-11-02 10:01:39 -070019import static android.content.pm.PackageManager.INSTALL_FAILED_SHARED_USER_INCOMPATIBLE;
20import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE;
21import static com.android.server.pm.PackageManagerService.COMPRESSED_EXTENSION;
22import static com.android.server.pm.PackageManagerService.DEBUG_COMPRESSION;
23import static com.android.server.pm.PackageManagerService.DEBUG_DEXOPT;
24import static com.android.server.pm.PackageManagerService.STUB_SUFFIX;
25import static com.android.server.pm.PackageManagerService.TAG;
26import static com.android.server.pm.PackageManagerServiceUtils.logCriticalInfo;
27
28import com.android.internal.content.NativeLibraryHelper;
29import com.android.internal.util.ArrayUtils;
30import com.android.internal.util.FastPrintWriter;
31import com.android.server.EventLogTags;
Shubham Ajmera246dccf2017-05-24 17:46:36 -070032import com.android.server.pm.dex.DexManager;
33import com.android.server.pm.dex.PackageDexUsage;
34
Andreas Gamped3e07d42016-09-06 18:22:19 -070035import android.annotation.NonNull;
David Brazdil6b4736d2016-02-04 11:54:17 +000036import android.app.AppGlobals;
37import android.content.Intent;
Todd Kennedy7c4c55d2017-11-02 10:01:39 -070038import android.content.pm.PackageManager;
David Brazdil6b4736d2016-02-04 11:54:17 +000039import android.content.pm.PackageParser;
David Brazdil6b4736d2016-02-04 11:54:17 +000040import android.content.pm.ResolveInfo;
Todd Kennedy7c4c55d2017-11-02 10:01:39 -070041import android.content.pm.Signature;
Calin Juravle03181622016-12-01 17:53:07 +000042import android.os.Build;
Todd Kennedy0eb97382017-10-03 16:57:22 -070043import android.os.Debug;
Todd Kennedy7c4c55d2017-11-02 10:01:39 -070044import android.os.Environment;
45import android.os.FileUtils;
Todd Kennedy0eb97382017-10-03 16:57:22 -070046import android.os.Process;
David Brazdil6b4736d2016-02-04 11:54:17 +000047import android.os.RemoteException;
Jeff Sharkeyd5896632016-03-04 16:16:00 -070048import android.os.UserHandle;
Todd Kennedy7c4c55d2017-11-02 10:01:39 -070049import android.service.pm.PackageServiceDumpProto;
Narayan Kamath6d99f792016-05-16 17:34:48 +010050import android.system.ErrnoException;
Tobias Thierer96aac9b32017-10-17 20:26:20 +010051import android.system.Os;
David Brazdil6b4736d2016-02-04 11:54:17 +000052import android.util.ArraySet;
53import android.util.Log;
Nicolas Geoffray20a894e2017-09-08 13:01:40 +010054import android.util.Slog;
55import android.util.jar.StrictJarFile;
Todd Kennedy7c4c55d2017-11-02 10:01:39 -070056import android.util.proto.ProtoOutputStream;
David Brazdil6b4736d2016-02-04 11:54:17 +000057
Todd Kennedy7c4c55d2017-11-02 10:01:39 -070058import dalvik.system.VMRuntime;
59
60import libcore.io.IoUtils;
61import libcore.io.Libcore;
62import libcore.io.Streams;
63
64import java.io.BufferedReader;
Narayan Kamath6d99f792016-05-16 17:34:48 +010065import java.io.File;
Todd Kennedy7c4c55d2017-11-02 10:01:39 -070066import java.io.FileInputStream;
67import java.io.FileOutputStream;
68import java.io.FileReader;
69import java.io.FilenameFilter;
Narayan Kamath6d99f792016-05-16 17:34:48 +010070import java.io.IOException;
Todd Kennedy7c4c55d2017-11-02 10:01:39 -070071import java.io.InputStream;
72import java.io.OutputStream;
73import java.io.PrintWriter;
74import java.security.cert.CertificateEncodingException;
75import java.security.cert.CertificateException;
76import java.text.SimpleDateFormat;
David Brazdil6b4736d2016-02-04 11:54:17 +000077import java.util.ArrayList;
Todd Kennedy7c4c55d2017-11-02 10:01:39 -070078import java.util.Arrays;
David Brazdil6b4736d2016-02-04 11:54:17 +000079import java.util.Collection;
Nicolas Geoffrayfa78b212016-05-26 14:20:49 +010080import java.util.Collections;
Todd Kennedy7c4c55d2017-11-02 10:01:39 -070081import java.util.Date;
Nicolas Geoffray20a894e2017-09-08 13:01:40 +010082import java.util.Iterator;
David Brazdil6b4736d2016-02-04 11:54:17 +000083import java.util.LinkedList;
84import java.util.List;
Andreas Gamped3e07d42016-09-06 18:22:19 -070085import java.util.function.Predicate;
Todd Kennedy7c4c55d2017-11-02 10:01:39 -070086import java.util.zip.GZIPInputStream;
Nicolas Geoffray20a894e2017-09-08 13:01:40 +010087import java.util.zip.ZipEntry;
David Brazdil6b4736d2016-02-04 11:54:17 +000088
David Brazdil6b4736d2016-02-04 11:54:17 +000089/**
90 * Class containing helper methods for the PackageManagerService.
91 *
92 * {@hide}
93 */
94public class PackageManagerServiceUtils {
David Brazdilb62d6902016-02-11 13:36:07 +000095 private final static long SEVEN_DAYS_IN_MILLISECONDS = 7 * 24 * 60 * 60 * 1000;
David Brazdil6b4736d2016-02-04 11:54:17 +000096
97 private static ArraySet<String> getPackageNamesForIntent(Intent intent, int userId) {
98 List<ResolveInfo> ris = null;
99 try {
Jeff Sharkeyd5896632016-03-04 16:16:00 -0700100 ris = AppGlobals.getPackageManager().queryIntentReceivers(intent, null, 0, userId)
101 .getList();
David Brazdil6b4736d2016-02-04 11:54:17 +0000102 } catch (RemoteException e) {
103 }
104 ArraySet<String> pkgNames = new ArraySet<String>();
105 if (ris != null) {
106 for (ResolveInfo ri : ris) {
107 pkgNames.add(ri.activityInfo.packageName);
108 }
109 }
110 return pkgNames;
111 }
112
Andreas Gamped3e07d42016-09-06 18:22:19 -0700113 // Sort a list of apps by their last usage, most recently used apps first. The order of
114 // packages without usage data is undefined (but they will be sorted after the packages
115 // that do have usage data).
116 public static void sortPackagesByUsageDate(List<PackageParser.Package> pkgs,
117 PackageManagerService packageManagerService) {
118 if (!packageManagerService.isHistoricalPackageUsageAvailable()) {
119 return;
120 }
121
122 Collections.sort(pkgs, (pkg1, pkg2) ->
123 Long.compare(pkg2.getLatestForegroundPackageUseTimeInMills(),
124 pkg1.getLatestForegroundPackageUseTimeInMills()));
125 }
126
127 // Apply the given {@code filter} to all packages in {@code packages}. If tested positive, the
128 // package will be removed from {@code packages} and added to {@code result} with its
129 // dependencies. If usage data is available, the positive packages will be sorted by usage
130 // data (with {@code sortTemp} as temporary storage).
131 private static void applyPackageFilter(Predicate<PackageParser.Package> filter,
132 Collection<PackageParser.Package> result,
133 Collection<PackageParser.Package> packages,
134 @NonNull List<PackageParser.Package> sortTemp,
135 PackageManagerService packageManagerService) {
136 for (PackageParser.Package pkg : packages) {
137 if (filter.test(pkg)) {
138 sortTemp.add(pkg);
David Brazdil6b4736d2016-02-04 11:54:17 +0000139 }
140 }
Andreas Gamped3e07d42016-09-06 18:22:19 -0700141
142 sortPackagesByUsageDate(sortTemp, packageManagerService);
143 packages.removeAll(sortTemp);
144
145 for (PackageParser.Package pkg : sortTemp) {
146 result.add(pkg);
147
148 Collection<PackageParser.Package> deps =
149 packageManagerService.findSharedNonSystemLibraries(pkg);
150 if (!deps.isEmpty()) {
151 deps.removeAll(result);
152 result.addAll(deps);
153 packages.removeAll(deps);
154 }
David Brazdil6b4736d2016-02-04 11:54:17 +0000155 }
Andreas Gamped3e07d42016-09-06 18:22:19 -0700156
157 sortTemp.clear();
David Brazdil6b4736d2016-02-04 11:54:17 +0000158 }
159
160 // Sort apps by importance for dexopt ordering. Important apps are given
161 // more priority in case the device runs out of space.
162 public static List<PackageParser.Package> getPackagesForDexopt(
163 Collection<PackageParser.Package> packages,
164 PackageManagerService packageManagerService) {
165 ArrayList<PackageParser.Package> remainingPkgs = new ArrayList<>(packages);
166 LinkedList<PackageParser.Package> result = new LinkedList<>();
Andreas Gamped3e07d42016-09-06 18:22:19 -0700167 ArrayList<PackageParser.Package> sortTemp = new ArrayList<>(remainingPkgs.size());
David Brazdil6b4736d2016-02-04 11:54:17 +0000168
169 // Give priority to core apps.
Andreas Gamped3e07d42016-09-06 18:22:19 -0700170 applyPackageFilter((pkg) -> pkg.coreApp, result, remainingPkgs, sortTemp,
171 packageManagerService);
David Brazdil6b4736d2016-02-04 11:54:17 +0000172
173 // Give priority to system apps that listen for pre boot complete.
174 Intent intent = new Intent(Intent.ACTION_PRE_BOOT_COMPLETED);
Andreas Gamped3e07d42016-09-06 18:22:19 -0700175 final ArraySet<String> pkgNames = getPackageNamesForIntent(intent, UserHandle.USER_SYSTEM);
176 applyPackageFilter((pkg) -> pkgNames.contains(pkg.packageName), result, remainingPkgs,
177 sortTemp, packageManagerService);
David Brazdil6b4736d2016-02-04 11:54:17 +0000178
David Brazdil90e26992016-04-18 14:08:52 +0100179 // Give priority to apps used by other apps.
Calin Juravle3b74c412017-08-03 19:48:37 -0700180 DexManager dexManager = packageManagerService.getDexManager();
Calin Juravle07b6eab2017-03-01 19:55:35 -0800181 applyPackageFilter((pkg) ->
Calin Juravle52a452c2017-08-04 01:42:17 -0700182 dexManager.getPackageUseInfoOrDefault(pkg.packageName)
183 .isAnyCodePathUsedByOtherApps(),
184 result, remainingPkgs, sortTemp, packageManagerService);
David Brazdil90e26992016-04-18 14:08:52 +0100185
David Brazdil6b4736d2016-02-04 11:54:17 +0000186 // Filter out packages that aren't recently used, add all remaining apps.
187 // TODO: add a property to control this?
Andreas Gamped3e07d42016-09-06 18:22:19 -0700188 Predicate<PackageParser.Package> remainingPredicate;
Nicolas Geoffrayfa78b212016-05-26 14:20:49 +0100189 if (!remainingPkgs.isEmpty() && packageManagerService.isHistoricalPackageUsageAvailable()) {
190 if (DEBUG_DEXOPT) {
191 Log.i(TAG, "Looking at historical package use");
192 }
193 // Get the package that was used last.
194 PackageParser.Package lastUsed = Collections.max(remainingPkgs, (pkg1, pkg2) ->
195 Long.compare(pkg1.getLatestForegroundPackageUseTimeInMills(),
196 pkg2.getLatestForegroundPackageUseTimeInMills()));
197 if (DEBUG_DEXOPT) {
198 Log.i(TAG, "Taking package " + lastUsed.packageName + " as reference in time use");
199 }
200 long estimatedPreviousSystemUseTime =
201 lastUsed.getLatestForegroundPackageUseTimeInMills();
202 // Be defensive if for some reason package usage has bogus data.
203 if (estimatedPreviousSystemUseTime != 0) {
Andreas Gamped3e07d42016-09-06 18:22:19 -0700204 final long cutoffTime = estimatedPreviousSystemUseTime - SEVEN_DAYS_IN_MILLISECONDS;
205 remainingPredicate =
206 (pkg) -> pkg.getLatestForegroundPackageUseTimeInMills() >= cutoffTime;
207 } else {
208 // No meaningful historical info. Take all.
209 remainingPredicate = (pkg) -> true;
Nicolas Geoffrayfa78b212016-05-26 14:20:49 +0100210 }
Andreas Gamped3e07d42016-09-06 18:22:19 -0700211 sortPackagesByUsageDate(remainingPkgs, packageManagerService);
212 } else {
213 // No historical info. Take all.
214 remainingPredicate = (pkg) -> true;
David Brazdil6b4736d2016-02-04 11:54:17 +0000215 }
Andreas Gamped3e07d42016-09-06 18:22:19 -0700216 applyPackageFilter(remainingPredicate, result, remainingPkgs, sortTemp,
217 packageManagerService);
David Brazdil6b4736d2016-02-04 11:54:17 +0000218
219 if (DEBUG_DEXOPT) {
Andreas Gampedab38e02016-09-09 17:50:20 -0700220 Log.i(TAG, "Packages to be dexopted: " + packagesToString(result));
221 Log.i(TAG, "Packages skipped from dexopt: " + packagesToString(remainingPkgs));
David Brazdil6b4736d2016-02-04 11:54:17 +0000222 }
223
224 return result;
225 }
Narayan Kamath6d99f792016-05-16 17:34:48 +0100226
227 /**
Shubham Ajmera246dccf2017-05-24 17:46:36 -0700228 * Checks if the package was inactive during since <code>thresholdTimeinMillis</code>.
229 * Package is considered active, if:
230 * 1) It was active in foreground.
231 * 2) It was active in background and also used by other apps.
232 *
233 * If it doesn't have sufficient information about the package, it return <code>false</code>.
234 */
Todd Kennedy7c4c55d2017-11-02 10:01:39 -0700235 public static boolean isUnusedSinceTimeInMillis(long firstInstallTime, long currentTimeInMillis,
Shubham Ajmera246dccf2017-05-24 17:46:36 -0700236 long thresholdTimeinMillis, PackageDexUsage.PackageUseInfo packageUseInfo,
237 long latestPackageUseTimeInMillis, long latestForegroundPackageUseTimeInMillis) {
238
239 if (currentTimeInMillis - firstInstallTime < thresholdTimeinMillis) {
240 return false;
241 }
242
243 // If the app was active in foreground during the threshold period.
244 boolean isActiveInForeground = (currentTimeInMillis
245 - latestForegroundPackageUseTimeInMillis)
246 < thresholdTimeinMillis;
247
248 if (isActiveInForeground) {
249 return false;
250 }
251
252 // If the app was active in background during the threshold period and was used
253 // by other packages.
254 boolean isActiveInBackgroundAndUsedByOtherPackages = ((currentTimeInMillis
255 - latestPackageUseTimeInMillis)
256 < thresholdTimeinMillis)
Calin Juravle52a452c2017-08-04 01:42:17 -0700257 && packageUseInfo.isAnyCodePathUsedByOtherApps();
Shubham Ajmera246dccf2017-05-24 17:46:36 -0700258
259 return !isActiveInBackgroundAndUsedByOtherPackages;
260 }
261
262 /**
Narayan Kamath6d99f792016-05-16 17:34:48 +0100263 * Returns the canonicalized path of {@code path} as per {@code realpath(3)}
264 * semantics.
265 */
266 public static String realpath(File path) throws IOException {
267 try {
Tobias Thierer96aac9b32017-10-17 20:26:20 +0100268 return Os.realpath(path.getAbsolutePath());
Narayan Kamath6d99f792016-05-16 17:34:48 +0100269 } catch (ErrnoException ee) {
270 throw ee.rethrowAsIOException();
271 }
272 }
Andreas Gampedab38e02016-09-09 17:50:20 -0700273
274 public static String packagesToString(Collection<PackageParser.Package> c) {
275 StringBuilder sb = new StringBuilder();
276 for (PackageParser.Package pkg : c) {
277 if (sb.length() > 0) {
278 sb.append(", ");
279 }
280 sb.append(pkg.packageName);
281 }
282 return sb.toString();
283 }
Calin Juravle03181622016-12-01 17:53:07 +0000284
285 /**
286 * Verifies that the given string {@code isa} is a valid supported isa on
287 * the running device.
288 */
289 public static boolean checkISA(String isa) {
290 for (String abi : Build.SUPPORTED_ABIS) {
291 if (VMRuntime.getInstructionSet(abi).equals(isa)) {
292 return true;
293 }
294 }
295 return false;
296 }
Nicolas Geoffray20a894e2017-09-08 13:01:40 +0100297
Todd Kennedyddaaf4c2017-11-07 10:01:25 -0800298 public static long getLastModifiedTime(PackageParser.Package pkg) {
299 final File srcFile = new File(pkg.codePath);
300 if (!srcFile.isDirectory()) {
301 return srcFile.lastModified();
Todd Kennedy7c4c55d2017-11-02 10:01:39 -0700302 }
Todd Kennedyddaaf4c2017-11-07 10:01:25 -0800303 final File baseFile = new File(pkg.baseCodePath);
304 long maxModifiedTime = baseFile.lastModified();
305 if (pkg.splitCodePaths != null) {
306 for (int i = pkg.splitCodePaths.length - 1; i >=0; --i) {
307 final File splitFile = new File(pkg.splitCodePaths[i]);
308 maxModifiedTime = Math.max(maxModifiedTime, splitFile.lastModified());
309 }
310 }
311 return maxModifiedTime;
Todd Kennedy7c4c55d2017-11-02 10:01:39 -0700312 }
313
Nicolas Geoffray20a894e2017-09-08 13:01:40 +0100314 /**
315 * Checks that the archive located at {@code fileName} has uncompressed dex file and so
316 * files that can be direclty mapped.
317 */
318 public static void logApkHasUncompressedCode(String fileName) {
319 StrictJarFile jarFile = null;
320 try {
321 jarFile = new StrictJarFile(fileName,
322 false /*verify*/, false /*signatureSchemeRollbackProtectionsEnforced*/);
323 Iterator<ZipEntry> it = jarFile.iterator();
324 while (it.hasNext()) {
325 ZipEntry entry = it.next();
326 if (entry.getName().endsWith(".dex")) {
327 if (entry.getMethod() != ZipEntry.STORED) {
328 Slog.wtf(TAG, "APK " + fileName + " has compressed dex code " +
329 entry.getName());
330 } else if ((entry.getDataOffset() & 0x3) != 0) {
331 Slog.wtf(TAG, "APK " + fileName + " has unaligned dex code " +
332 entry.getName());
333 }
334 } else if (entry.getName().endsWith(".so")) {
335 if (entry.getMethod() != ZipEntry.STORED) {
336 Slog.wtf(TAG, "APK " + fileName + " has compressed native code " +
337 entry.getName());
338 } else if ((entry.getDataOffset() & (0x1000 - 1)) != 0) {
339 Slog.wtf(TAG, "APK " + fileName + " has unaligned native code " +
340 entry.getName());
341 }
342 }
343 }
344 } catch (IOException ignore) {
345 Slog.wtf(TAG, "Error when parsing APK " + fileName);
346 } finally {
347 try {
348 if (jarFile != null) {
349 jarFile.close();
350 }
351 } catch (IOException ignore) {}
352 }
353 return;
354 }
355
356 /**
357 * Checks that the APKs in the given package have uncompressed dex file and so
358 * files that can be direclty mapped.
359 */
360 public static void logPackageHasUncompressedCode(PackageParser.Package pkg) {
361 logApkHasUncompressedCode(pkg.baseCodePath);
362 if (!ArrayUtils.isEmpty(pkg.splitCodePaths)) {
363 for (int i = 0; i < pkg.splitCodePaths.length; i++) {
364 logApkHasUncompressedCode(pkg.splitCodePaths[i]);
365 }
366 }
367 }
Todd Kennedy0eb97382017-10-03 16:57:22 -0700368
Todd Kennedy7c4c55d2017-11-02 10:01:39 -0700369 private static File getSettingsProblemFile() {
370 File dataDir = Environment.getDataDirectory();
371 File systemDir = new File(dataDir, "system");
372 File fname = new File(systemDir, "uiderrors.txt");
373 return fname;
374 }
375
376 public static void dumpCriticalInfo(ProtoOutputStream proto) {
377 try (BufferedReader in = new BufferedReader(new FileReader(getSettingsProblemFile()))) {
378 String line = null;
379 while ((line = in.readLine()) != null) {
380 if (line.contains("ignored: updated version")) continue;
381 proto.write(PackageServiceDumpProto.MESSAGES, line);
382 }
383 } catch (IOException ignored) {
384 }
385 }
386
387 public static void dumpCriticalInfo(PrintWriter pw, String msg) {
388 try (BufferedReader in = new BufferedReader(new FileReader(getSettingsProblemFile()))) {
389 String line = null;
390 while ((line = in.readLine()) != null) {
391 if (line.contains("ignored: updated version")) continue;
392 if (msg != null) {
393 pw.print(msg);
394 }
395 pw.println(line);
396 }
397 } catch (IOException ignored) {
398 }
399 }
400
401 public static void logCriticalInfo(int priority, String msg) {
402 Slog.println(priority, TAG, msg);
403 EventLogTags.writePmCriticalInfo(msg);
404 try {
405 File fname = getSettingsProblemFile();
406 FileOutputStream out = new FileOutputStream(fname, true);
407 PrintWriter pw = new FastPrintWriter(out);
408 SimpleDateFormat formatter = new SimpleDateFormat();
409 String dateString = formatter.format(new Date(System.currentTimeMillis()));
410 pw.println(dateString + ": " + msg);
411 pw.close();
412 FileUtils.setPermissions(
413 fname.toString(),
414 FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IROTH,
415 -1, -1);
416 } catch (java.io.IOException e) {
417 }
418 }
419
Todd Kennedy0eb97382017-10-03 16:57:22 -0700420 public static void enforceShellRestriction(String restriction, int callingUid, int userHandle) {
421 if (callingUid == Process.SHELL_UID) {
422 if (userHandle >= 0
423 && PackageManagerService.sUserManager.hasUserRestriction(
424 restriction, userHandle)) {
425 throw new SecurityException("Shell does not have permission to access user "
426 + userHandle);
427 } else if (userHandle < 0) {
428 Slog.e(PackageManagerService.TAG, "Unable to check shell permission for user "
429 + userHandle + "\n\t" + Debug.getCallers(3));
430 }
431 }
432 }
Todd Kennedy7c4c55d2017-11-02 10:01:39 -0700433
434 /**
435 * Derive the value of the {@code cpuAbiOverride} based on the provided
436 * value and an optional stored value from the package settings.
437 */
438 public static String deriveAbiOverride(String abiOverride, PackageSetting settings) {
439 String cpuAbiOverride = null;
440 if (NativeLibraryHelper.CLEAR_ABI_OVERRIDE.equals(abiOverride)) {
441 cpuAbiOverride = null;
442 } else if (abiOverride != null) {
443 cpuAbiOverride = abiOverride;
444 } else if (settings != null) {
445 cpuAbiOverride = settings.cpuAbiOverrideString;
446 }
447 return cpuAbiOverride;
448 }
449
450 /**
451 * Compares two sets of signatures. Returns:
452 * <br />
453 * {@link PackageManager#SIGNATURE_NEITHER_SIGNED}: if both signature sets are null,
454 * <br />
455 * {@link PackageManager#SIGNATURE_FIRST_NOT_SIGNED}: if the first signature set is null,
456 * <br />
457 * {@link PackageManager#SIGNATURE_SECOND_NOT_SIGNED}: if the second signature set is null,
458 * <br />
459 * {@link PackageManager#SIGNATURE_MATCH}: if the two signature sets are identical,
460 * <br />
461 * {@link PackageManager#SIGNATURE_NO_MATCH}: if the two signature sets differ.
462 */
463 public static int compareSignatures(Signature[] s1, Signature[] s2) {
464 if (s1 == null) {
465 return s2 == null
466 ? PackageManager.SIGNATURE_NEITHER_SIGNED
467 : PackageManager.SIGNATURE_FIRST_NOT_SIGNED;
468 }
469
470 if (s2 == null) {
471 return PackageManager.SIGNATURE_SECOND_NOT_SIGNED;
472 }
473
474 if (s1.length != s2.length) {
475 return PackageManager.SIGNATURE_NO_MATCH;
476 }
477
478 // Since both signature sets are of size 1, we can compare without HashSets.
479 if (s1.length == 1) {
480 return s1[0].equals(s2[0]) ?
481 PackageManager.SIGNATURE_MATCH :
482 PackageManager.SIGNATURE_NO_MATCH;
483 }
484
485 ArraySet<Signature> set1 = new ArraySet<Signature>();
486 for (Signature sig : s1) {
487 set1.add(sig);
488 }
489 ArraySet<Signature> set2 = new ArraySet<Signature>();
490 for (Signature sig : s2) {
491 set2.add(sig);
492 }
493 // Make sure s2 contains all signatures in s1.
494 if (set1.equals(set2)) {
495 return PackageManager.SIGNATURE_MATCH;
496 }
497 return PackageManager.SIGNATURE_NO_MATCH;
498 }
499
500 /**
501 * Used for backward compatibility to make sure any packages with
502 * certificate chains get upgraded to the new style. {@code existingSigs}
503 * will be in the old format (since they were stored on disk from before the
504 * system upgrade) and {@code scannedSigs} will be in the newer format.
505 */
506 private static boolean matchSignaturesCompat(String packageName,
507 PackageSignatures packageSignatures, Signature[] parsedSignatures) {
508 ArraySet<Signature> existingSet = new ArraySet<Signature>();
509 for (Signature sig : packageSignatures.mSignatures) {
510 existingSet.add(sig);
511 }
512 ArraySet<Signature> scannedCompatSet = new ArraySet<Signature>();
513 for (Signature sig : parsedSignatures) {
514 try {
515 Signature[] chainSignatures = sig.getChainSignatures();
516 for (Signature chainSig : chainSignatures) {
517 scannedCompatSet.add(chainSig);
518 }
519 } catch (CertificateEncodingException e) {
520 scannedCompatSet.add(sig);
521 }
522 }
523 // make sure the expanded scanned set contains all signatures in the existing one
524 if (scannedCompatSet.equals(existingSet)) {
525 // migrate the old signatures to the new scheme
526 packageSignatures.assignSignatures(parsedSignatures);
527 return true;
528 }
529 return false;
530 }
531
532 private static boolean matchSignaturesRecover(String packageName,
533 Signature[] existingSignatures, Signature[] parsedSignatures) {
534 String msg = null;
535 try {
536 if (Signature.areEffectiveMatch(existingSignatures, parsedSignatures)) {
537 logCriticalInfo(Log.INFO,
538 "Recovered effectively matching certificates for " + packageName);
539 return true;
540 }
541 } catch (CertificateException e) {
542 msg = e.getMessage();
543 }
544 logCriticalInfo(Log.INFO,
545 "Failed to recover certificates for " + packageName + ": " + msg);
546 return false;
547 }
548
549 /**
550 * Verifies that signatures match.
551 * @returns {@code true} if the compat signatures were matched; otherwise, {@code false}.
552 * @throws PackageManagerException if the signatures did not match.
553 */
554 public static boolean verifySignatures(PackageSetting pkgSetting,
555 Signature[] parsedSignatures, boolean compareCompat, boolean compareRecover)
556 throws PackageManagerException {
557 final String packageName = pkgSetting.name;
558 boolean compatMatch = false;
559 if (pkgSetting.signatures.mSignatures != null) {
560 // Already existing package. Make sure signatures match
561 boolean match = compareSignatures(pkgSetting.signatures.mSignatures, parsedSignatures)
562 == PackageManager.SIGNATURE_MATCH;
563 if (!match && compareCompat) {
564 match = matchSignaturesCompat(packageName, pkgSetting.signatures, parsedSignatures);
565 compatMatch = match;
566 }
567 if (!match && compareRecover) {
568 match = matchSignaturesRecover(
569 packageName, pkgSetting.signatures.mSignatures, parsedSignatures);
570 }
571 if (!match) {
572 throw new PackageManagerException(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
573 "Package " + packageName +
Todd Kennedy96cb94b2017-11-29 13:17:12 -0800574 " signatures do not match previously installed version; ignoring!");
Todd Kennedy7c4c55d2017-11-02 10:01:39 -0700575 }
576 }
577 // Check for shared user signatures
578 if (pkgSetting.sharedUser != null && pkgSetting.sharedUser.signatures.mSignatures != null) {
579 // Already existing package. Make sure signatures match
580 boolean match = compareSignatures(pkgSetting.sharedUser.signatures.mSignatures,
581 parsedSignatures) == PackageManager.SIGNATURE_MATCH;
Todd Kennedy3e654842017-11-29 13:58:53 -0800582 if (!match && compareCompat) {
Todd Kennedy7c4c55d2017-11-02 10:01:39 -0700583 match = matchSignaturesCompat(
584 packageName, pkgSetting.sharedUser.signatures, parsedSignatures);
585 }
Todd Kennedy3e654842017-11-29 13:58:53 -0800586 if (!match && compareRecover) {
Todd Kennedy7c4c55d2017-11-02 10:01:39 -0700587 match = matchSignaturesRecover(
588 packageName, pkgSetting.sharedUser.signatures.mSignatures, parsedSignatures);
589 compatMatch |= match;
590 }
Todd Kennedy3e654842017-11-29 13:58:53 -0800591 if (!match) {
Todd Kennedy7c4c55d2017-11-02 10:01:39 -0700592 throw new PackageManagerException(INSTALL_FAILED_SHARED_USER_INCOMPATIBLE,
593 "Package " + packageName
594 + " has no signatures that match those in shared user "
595 + pkgSetting.sharedUser.name + "; ignoring!");
596 }
597 }
598 return compatMatch;
599 }
600
601 public static int decompressFile(File srcFile, File dstFile) throws ErrnoException {
602 if (DEBUG_COMPRESSION) {
603 Slog.i(TAG, "Decompress file"
604 + "; src: " + srcFile.getAbsolutePath()
605 + ", dst: " + dstFile.getAbsolutePath());
606 }
607 try (
608 InputStream fileIn = new GZIPInputStream(new FileInputStream(srcFile));
609 OutputStream fileOut = new FileOutputStream(dstFile, false /*append*/);
610 ) {
611 Streams.copy(fileIn, fileOut);
612 Os.chmod(dstFile.getAbsolutePath(), 0644);
613 return PackageManager.INSTALL_SUCCEEDED;
614 } catch (IOException e) {
615 logCriticalInfo(Log.ERROR, "Failed to decompress file"
616 + "; src: " + srcFile.getAbsolutePath()
617 + ", dst: " + dstFile.getAbsolutePath());
618 }
619 return PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
620 }
621
622 public static File[] getCompressedFiles(String codePath) {
623 final File stubCodePath = new File(codePath);
624 final String stubName = stubCodePath.getName();
625
626 // The layout of a compressed package on a given partition is as follows :
627 //
628 // Compressed artifacts:
629 //
630 // /partition/ModuleName/foo.gz
631 // /partation/ModuleName/bar.gz
632 //
633 // Stub artifact:
634 //
635 // /partition/ModuleName-Stub/ModuleName-Stub.apk
636 //
637 // In other words, stub is on the same partition as the compressed artifacts
638 // and in a directory that's suffixed with "-Stub".
639 int idx = stubName.lastIndexOf(STUB_SUFFIX);
640 if (idx < 0 || (stubName.length() != (idx + STUB_SUFFIX.length()))) {
641 return null;
642 }
643
644 final File stubParentDir = stubCodePath.getParentFile();
645 if (stubParentDir == null) {
646 Slog.e(TAG, "Unable to determine stub parent dir for codePath: " + codePath);
647 return null;
648 }
649
650 final File compressedPath = new File(stubParentDir, stubName.substring(0, idx));
651 final File[] files = compressedPath.listFiles(new FilenameFilter() {
652 @Override
653 public boolean accept(File dir, String name) {
654 return name.toLowerCase().endsWith(COMPRESSED_EXTENSION);
655 }
656 });
657
658 if (DEBUG_COMPRESSION && files != null && files.length > 0) {
659 Slog.i(TAG, "getCompressedFiles[" + codePath + "]: " + Arrays.toString(files));
660 }
661
662 return files;
663 }
664
665 public static boolean compressedFileExists(String codePath) {
666 final File[] compressedFiles = getCompressedFiles(codePath);
667 return compressedFiles != null && compressedFiles.length > 0;
668 }
Narayan Kamath6d99f792016-05-16 17:34:48 +0100669}