blob: 76c199b88a5a3b7a45126d161f960018dacb715f [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;
Victor Hsieh0c8f2e02017-10-06 14:44:52 -070036import android.annotation.Nullable;
David Brazdil6b4736d2016-02-04 11:54:17 +000037import android.app.AppGlobals;
38import android.content.Intent;
Todd Kennedy7c4c55d2017-11-02 10:01:39 -070039import android.content.pm.PackageManager;
David Brazdil6b4736d2016-02-04 11:54:17 +000040import android.content.pm.PackageParser;
Victor Hsieh0c8f2e02017-10-06 14:44:52 -070041import android.content.pm.PackageParser.PackageParserException;
David Brazdil6b4736d2016-02-04 11:54:17 +000042import android.content.pm.ResolveInfo;
Todd Kennedy7c4c55d2017-11-02 10:01:39 -070043import android.content.pm.Signature;
Calin Juravle03181622016-12-01 17:53:07 +000044import android.os.Build;
Todd Kennedy0eb97382017-10-03 16:57:22 -070045import android.os.Debug;
Todd Kennedy7c4c55d2017-11-02 10:01:39 -070046import android.os.Environment;
47import android.os.FileUtils;
Todd Kennedy0eb97382017-10-03 16:57:22 -070048import android.os.Process;
David Brazdil6b4736d2016-02-04 11:54:17 +000049import android.os.RemoteException;
Victor Hsieh0c8f2e02017-10-06 14:44:52 -070050import android.os.SystemProperties;
Jeff Sharkeyd5896632016-03-04 16:16:00 -070051import android.os.UserHandle;
Todd Kennedy7c4c55d2017-11-02 10:01:39 -070052import android.service.pm.PackageServiceDumpProto;
Narayan Kamath6d99f792016-05-16 17:34:48 +010053import android.system.ErrnoException;
Tobias Thierer96aac9b32017-10-17 20:26:20 +010054import android.system.Os;
David Brazdil6b4736d2016-02-04 11:54:17 +000055import android.util.ArraySet;
56import android.util.Log;
Daniel Cashman5cdda342018-01-19 07:22:52 -080057import android.util.PackageUtils;
Nicolas Geoffray20a894e2017-09-08 13:01:40 +010058import android.util.Slog;
59import android.util.jar.StrictJarFile;
Todd Kennedy7c4c55d2017-11-02 10:01:39 -070060import android.util.proto.ProtoOutputStream;
David Brazdil6b4736d2016-02-04 11:54:17 +000061
Todd Kennedy7c4c55d2017-11-02 10:01:39 -070062import dalvik.system.VMRuntime;
63
64import libcore.io.IoUtils;
65import libcore.io.Libcore;
66import libcore.io.Streams;
67
68import java.io.BufferedReader;
Narayan Kamath6d99f792016-05-16 17:34:48 +010069import java.io.File;
Todd Kennedy7c4c55d2017-11-02 10:01:39 -070070import java.io.FileInputStream;
71import java.io.FileOutputStream;
72import java.io.FileReader;
73import java.io.FilenameFilter;
Narayan Kamath6d99f792016-05-16 17:34:48 +010074import java.io.IOException;
Todd Kennedy7c4c55d2017-11-02 10:01:39 -070075import java.io.InputStream;
76import java.io.OutputStream;
77import java.io.PrintWriter;
Daniel Cashman5cdda342018-01-19 07:22:52 -080078import java.security.MessageDigest;
79import java.security.NoSuchAlgorithmException;
Todd Kennedy7c4c55d2017-11-02 10:01:39 -070080import java.security.cert.CertificateEncodingException;
81import java.security.cert.CertificateException;
82import java.text.SimpleDateFormat;
David Brazdil6b4736d2016-02-04 11:54:17 +000083import java.util.ArrayList;
Todd Kennedy7c4c55d2017-11-02 10:01:39 -070084import java.util.Arrays;
David Brazdil6b4736d2016-02-04 11:54:17 +000085import java.util.Collection;
Nicolas Geoffrayfa78b212016-05-26 14:20:49 +010086import java.util.Collections;
Todd Kennedy7c4c55d2017-11-02 10:01:39 -070087import java.util.Date;
Nicolas Geoffray20a894e2017-09-08 13:01:40 +010088import java.util.Iterator;
David Brazdil6b4736d2016-02-04 11:54:17 +000089import java.util.LinkedList;
90import java.util.List;
Andreas Gamped3e07d42016-09-06 18:22:19 -070091import java.util.function.Predicate;
Todd Kennedy7c4c55d2017-11-02 10:01:39 -070092import java.util.zip.GZIPInputStream;
Nicolas Geoffray20a894e2017-09-08 13:01:40 +010093import java.util.zip.ZipEntry;
David Brazdil6b4736d2016-02-04 11:54:17 +000094
David Brazdil6b4736d2016-02-04 11:54:17 +000095/**
96 * Class containing helper methods for the PackageManagerService.
97 *
98 * {@hide}
99 */
100public class PackageManagerServiceUtils {
David Brazdilb62d6902016-02-11 13:36:07 +0000101 private final static long SEVEN_DAYS_IN_MILLISECONDS = 7 * 24 * 60 * 60 * 1000;
David Brazdil6b4736d2016-02-04 11:54:17 +0000102
103 private static ArraySet<String> getPackageNamesForIntent(Intent intent, int userId) {
104 List<ResolveInfo> ris = null;
105 try {
Jeff Sharkeyd5896632016-03-04 16:16:00 -0700106 ris = AppGlobals.getPackageManager().queryIntentReceivers(intent, null, 0, userId)
107 .getList();
David Brazdil6b4736d2016-02-04 11:54:17 +0000108 } catch (RemoteException e) {
109 }
110 ArraySet<String> pkgNames = new ArraySet<String>();
111 if (ris != null) {
112 for (ResolveInfo ri : ris) {
113 pkgNames.add(ri.activityInfo.packageName);
114 }
115 }
116 return pkgNames;
117 }
118
Andreas Gamped3e07d42016-09-06 18:22:19 -0700119 // Sort a list of apps by their last usage, most recently used apps first. The order of
120 // packages without usage data is undefined (but they will be sorted after the packages
121 // that do have usage data).
122 public static void sortPackagesByUsageDate(List<PackageParser.Package> pkgs,
123 PackageManagerService packageManagerService) {
124 if (!packageManagerService.isHistoricalPackageUsageAvailable()) {
125 return;
126 }
127
128 Collections.sort(pkgs, (pkg1, pkg2) ->
129 Long.compare(pkg2.getLatestForegroundPackageUseTimeInMills(),
130 pkg1.getLatestForegroundPackageUseTimeInMills()));
131 }
132
133 // Apply the given {@code filter} to all packages in {@code packages}. If tested positive, the
134 // package will be removed from {@code packages} and added to {@code result} with its
135 // dependencies. If usage data is available, the positive packages will be sorted by usage
136 // data (with {@code sortTemp} as temporary storage).
137 private static void applyPackageFilter(Predicate<PackageParser.Package> filter,
138 Collection<PackageParser.Package> result,
139 Collection<PackageParser.Package> packages,
140 @NonNull List<PackageParser.Package> sortTemp,
141 PackageManagerService packageManagerService) {
142 for (PackageParser.Package pkg : packages) {
143 if (filter.test(pkg)) {
144 sortTemp.add(pkg);
David Brazdil6b4736d2016-02-04 11:54:17 +0000145 }
146 }
Andreas Gamped3e07d42016-09-06 18:22:19 -0700147
148 sortPackagesByUsageDate(sortTemp, packageManagerService);
149 packages.removeAll(sortTemp);
150
151 for (PackageParser.Package pkg : sortTemp) {
152 result.add(pkg);
153
154 Collection<PackageParser.Package> deps =
155 packageManagerService.findSharedNonSystemLibraries(pkg);
156 if (!deps.isEmpty()) {
157 deps.removeAll(result);
158 result.addAll(deps);
159 packages.removeAll(deps);
160 }
David Brazdil6b4736d2016-02-04 11:54:17 +0000161 }
Andreas Gamped3e07d42016-09-06 18:22:19 -0700162
163 sortTemp.clear();
David Brazdil6b4736d2016-02-04 11:54:17 +0000164 }
165
166 // Sort apps by importance for dexopt ordering. Important apps are given
167 // more priority in case the device runs out of space.
168 public static List<PackageParser.Package> getPackagesForDexopt(
169 Collection<PackageParser.Package> packages,
170 PackageManagerService packageManagerService) {
171 ArrayList<PackageParser.Package> remainingPkgs = new ArrayList<>(packages);
172 LinkedList<PackageParser.Package> result = new LinkedList<>();
Andreas Gamped3e07d42016-09-06 18:22:19 -0700173 ArrayList<PackageParser.Package> sortTemp = new ArrayList<>(remainingPkgs.size());
David Brazdil6b4736d2016-02-04 11:54:17 +0000174
175 // Give priority to core apps.
Andreas Gamped3e07d42016-09-06 18:22:19 -0700176 applyPackageFilter((pkg) -> pkg.coreApp, result, remainingPkgs, sortTemp,
177 packageManagerService);
David Brazdil6b4736d2016-02-04 11:54:17 +0000178
179 // Give priority to system apps that listen for pre boot complete.
180 Intent intent = new Intent(Intent.ACTION_PRE_BOOT_COMPLETED);
Andreas Gamped3e07d42016-09-06 18:22:19 -0700181 final ArraySet<String> pkgNames = getPackageNamesForIntent(intent, UserHandle.USER_SYSTEM);
182 applyPackageFilter((pkg) -> pkgNames.contains(pkg.packageName), result, remainingPkgs,
183 sortTemp, packageManagerService);
David Brazdil6b4736d2016-02-04 11:54:17 +0000184
David Brazdil90e26992016-04-18 14:08:52 +0100185 // Give priority to apps used by other apps.
Calin Juravle3b74c412017-08-03 19:48:37 -0700186 DexManager dexManager = packageManagerService.getDexManager();
Calin Juravle07b6eab2017-03-01 19:55:35 -0800187 applyPackageFilter((pkg) ->
Calin Juravle52a452c2017-08-04 01:42:17 -0700188 dexManager.getPackageUseInfoOrDefault(pkg.packageName)
189 .isAnyCodePathUsedByOtherApps(),
190 result, remainingPkgs, sortTemp, packageManagerService);
David Brazdil90e26992016-04-18 14:08:52 +0100191
David Brazdil6b4736d2016-02-04 11:54:17 +0000192 // Filter out packages that aren't recently used, add all remaining apps.
193 // TODO: add a property to control this?
Andreas Gamped3e07d42016-09-06 18:22:19 -0700194 Predicate<PackageParser.Package> remainingPredicate;
Nicolas Geoffrayfa78b212016-05-26 14:20:49 +0100195 if (!remainingPkgs.isEmpty() && packageManagerService.isHistoricalPackageUsageAvailable()) {
196 if (DEBUG_DEXOPT) {
197 Log.i(TAG, "Looking at historical package use");
198 }
199 // Get the package that was used last.
200 PackageParser.Package lastUsed = Collections.max(remainingPkgs, (pkg1, pkg2) ->
201 Long.compare(pkg1.getLatestForegroundPackageUseTimeInMills(),
202 pkg2.getLatestForegroundPackageUseTimeInMills()));
203 if (DEBUG_DEXOPT) {
204 Log.i(TAG, "Taking package " + lastUsed.packageName + " as reference in time use");
205 }
206 long estimatedPreviousSystemUseTime =
207 lastUsed.getLatestForegroundPackageUseTimeInMills();
208 // Be defensive if for some reason package usage has bogus data.
209 if (estimatedPreviousSystemUseTime != 0) {
Andreas Gamped3e07d42016-09-06 18:22:19 -0700210 final long cutoffTime = estimatedPreviousSystemUseTime - SEVEN_DAYS_IN_MILLISECONDS;
211 remainingPredicate =
212 (pkg) -> pkg.getLatestForegroundPackageUseTimeInMills() >= cutoffTime;
213 } else {
214 // No meaningful historical info. Take all.
215 remainingPredicate = (pkg) -> true;
Nicolas Geoffrayfa78b212016-05-26 14:20:49 +0100216 }
Andreas Gamped3e07d42016-09-06 18:22:19 -0700217 sortPackagesByUsageDate(remainingPkgs, packageManagerService);
218 } else {
219 // No historical info. Take all.
220 remainingPredicate = (pkg) -> true;
David Brazdil6b4736d2016-02-04 11:54:17 +0000221 }
Andreas Gamped3e07d42016-09-06 18:22:19 -0700222 applyPackageFilter(remainingPredicate, result, remainingPkgs, sortTemp,
223 packageManagerService);
David Brazdil6b4736d2016-02-04 11:54:17 +0000224
225 if (DEBUG_DEXOPT) {
Andreas Gampedab38e02016-09-09 17:50:20 -0700226 Log.i(TAG, "Packages to be dexopted: " + packagesToString(result));
227 Log.i(TAG, "Packages skipped from dexopt: " + packagesToString(remainingPkgs));
David Brazdil6b4736d2016-02-04 11:54:17 +0000228 }
229
230 return result;
231 }
Narayan Kamath6d99f792016-05-16 17:34:48 +0100232
233 /**
Shubham Ajmera246dccf2017-05-24 17:46:36 -0700234 * Checks if the package was inactive during since <code>thresholdTimeinMillis</code>.
235 * Package is considered active, if:
236 * 1) It was active in foreground.
237 * 2) It was active in background and also used by other apps.
238 *
239 * If it doesn't have sufficient information about the package, it return <code>false</code>.
240 */
Todd Kennedy7c4c55d2017-11-02 10:01:39 -0700241 public static boolean isUnusedSinceTimeInMillis(long firstInstallTime, long currentTimeInMillis,
Shubham Ajmera246dccf2017-05-24 17:46:36 -0700242 long thresholdTimeinMillis, PackageDexUsage.PackageUseInfo packageUseInfo,
243 long latestPackageUseTimeInMillis, long latestForegroundPackageUseTimeInMillis) {
244
245 if (currentTimeInMillis - firstInstallTime < thresholdTimeinMillis) {
246 return false;
247 }
248
249 // If the app was active in foreground during the threshold period.
250 boolean isActiveInForeground = (currentTimeInMillis
251 - latestForegroundPackageUseTimeInMillis)
252 < thresholdTimeinMillis;
253
254 if (isActiveInForeground) {
255 return false;
256 }
257
258 // If the app was active in background during the threshold period and was used
259 // by other packages.
260 boolean isActiveInBackgroundAndUsedByOtherPackages = ((currentTimeInMillis
261 - latestPackageUseTimeInMillis)
262 < thresholdTimeinMillis)
Calin Juravle52a452c2017-08-04 01:42:17 -0700263 && packageUseInfo.isAnyCodePathUsedByOtherApps();
Shubham Ajmera246dccf2017-05-24 17:46:36 -0700264
265 return !isActiveInBackgroundAndUsedByOtherPackages;
266 }
267
268 /**
Narayan Kamath6d99f792016-05-16 17:34:48 +0100269 * Returns the canonicalized path of {@code path} as per {@code realpath(3)}
270 * semantics.
271 */
272 public static String realpath(File path) throws IOException {
273 try {
Tobias Thierer96aac9b32017-10-17 20:26:20 +0100274 return Os.realpath(path.getAbsolutePath());
Narayan Kamath6d99f792016-05-16 17:34:48 +0100275 } catch (ErrnoException ee) {
276 throw ee.rethrowAsIOException();
277 }
278 }
Andreas Gampedab38e02016-09-09 17:50:20 -0700279
280 public static String packagesToString(Collection<PackageParser.Package> c) {
281 StringBuilder sb = new StringBuilder();
282 for (PackageParser.Package pkg : c) {
283 if (sb.length() > 0) {
284 sb.append(", ");
285 }
286 sb.append(pkg.packageName);
287 }
288 return sb.toString();
289 }
Calin Juravle03181622016-12-01 17:53:07 +0000290
291 /**
292 * Verifies that the given string {@code isa} is a valid supported isa on
293 * the running device.
294 */
295 public static boolean checkISA(String isa) {
296 for (String abi : Build.SUPPORTED_ABIS) {
297 if (VMRuntime.getInstructionSet(abi).equals(isa)) {
298 return true;
299 }
300 }
301 return false;
302 }
Nicolas Geoffray20a894e2017-09-08 13:01:40 +0100303
Todd Kennedyddaaf4c2017-11-07 10:01:25 -0800304 public static long getLastModifiedTime(PackageParser.Package pkg) {
305 final File srcFile = new File(pkg.codePath);
306 if (!srcFile.isDirectory()) {
307 return srcFile.lastModified();
Todd Kennedy7c4c55d2017-11-02 10:01:39 -0700308 }
Todd Kennedyddaaf4c2017-11-07 10:01:25 -0800309 final File baseFile = new File(pkg.baseCodePath);
310 long maxModifiedTime = baseFile.lastModified();
311 if (pkg.splitCodePaths != null) {
312 for (int i = pkg.splitCodePaths.length - 1; i >=0; --i) {
313 final File splitFile = new File(pkg.splitCodePaths[i]);
314 maxModifiedTime = Math.max(maxModifiedTime, splitFile.lastModified());
315 }
316 }
317 return maxModifiedTime;
Todd Kennedy7c4c55d2017-11-02 10:01:39 -0700318 }
319
Nicolas Geoffray20a894e2017-09-08 13:01:40 +0100320 /**
321 * Checks that the archive located at {@code fileName} has uncompressed dex file and so
322 * files that can be direclty mapped.
323 */
324 public static void logApkHasUncompressedCode(String fileName) {
325 StrictJarFile jarFile = null;
326 try {
327 jarFile = new StrictJarFile(fileName,
328 false /*verify*/, false /*signatureSchemeRollbackProtectionsEnforced*/);
329 Iterator<ZipEntry> it = jarFile.iterator();
330 while (it.hasNext()) {
331 ZipEntry entry = it.next();
332 if (entry.getName().endsWith(".dex")) {
333 if (entry.getMethod() != ZipEntry.STORED) {
334 Slog.wtf(TAG, "APK " + fileName + " has compressed dex code " +
335 entry.getName());
336 } else if ((entry.getDataOffset() & 0x3) != 0) {
337 Slog.wtf(TAG, "APK " + fileName + " has unaligned dex code " +
338 entry.getName());
339 }
340 } else if (entry.getName().endsWith(".so")) {
341 if (entry.getMethod() != ZipEntry.STORED) {
342 Slog.wtf(TAG, "APK " + fileName + " has compressed native code " +
343 entry.getName());
344 } else if ((entry.getDataOffset() & (0x1000 - 1)) != 0) {
345 Slog.wtf(TAG, "APK " + fileName + " has unaligned native code " +
346 entry.getName());
347 }
348 }
349 }
350 } catch (IOException ignore) {
351 Slog.wtf(TAG, "Error when parsing APK " + fileName);
352 } finally {
353 try {
354 if (jarFile != null) {
355 jarFile.close();
356 }
357 } catch (IOException ignore) {}
358 }
359 return;
360 }
361
362 /**
363 * Checks that the APKs in the given package have uncompressed dex file and so
364 * files that can be direclty mapped.
365 */
366 public static void logPackageHasUncompressedCode(PackageParser.Package pkg) {
367 logApkHasUncompressedCode(pkg.baseCodePath);
368 if (!ArrayUtils.isEmpty(pkg.splitCodePaths)) {
369 for (int i = 0; i < pkg.splitCodePaths.length; i++) {
370 logApkHasUncompressedCode(pkg.splitCodePaths[i]);
371 }
372 }
373 }
Todd Kennedy0eb97382017-10-03 16:57:22 -0700374
Todd Kennedy7c4c55d2017-11-02 10:01:39 -0700375 private static File getSettingsProblemFile() {
376 File dataDir = Environment.getDataDirectory();
377 File systemDir = new File(dataDir, "system");
378 File fname = new File(systemDir, "uiderrors.txt");
379 return fname;
380 }
381
382 public static void dumpCriticalInfo(ProtoOutputStream proto) {
383 try (BufferedReader in = new BufferedReader(new FileReader(getSettingsProblemFile()))) {
384 String line = null;
385 while ((line = in.readLine()) != null) {
386 if (line.contains("ignored: updated version")) continue;
387 proto.write(PackageServiceDumpProto.MESSAGES, line);
388 }
389 } catch (IOException ignored) {
390 }
391 }
392
393 public static void dumpCriticalInfo(PrintWriter pw, String msg) {
394 try (BufferedReader in = new BufferedReader(new FileReader(getSettingsProblemFile()))) {
395 String line = null;
396 while ((line = in.readLine()) != null) {
397 if (line.contains("ignored: updated version")) continue;
398 if (msg != null) {
399 pw.print(msg);
400 }
401 pw.println(line);
402 }
403 } catch (IOException ignored) {
404 }
405 }
406
407 public static void logCriticalInfo(int priority, String msg) {
408 Slog.println(priority, TAG, msg);
409 EventLogTags.writePmCriticalInfo(msg);
410 try {
411 File fname = getSettingsProblemFile();
412 FileOutputStream out = new FileOutputStream(fname, true);
413 PrintWriter pw = new FastPrintWriter(out);
414 SimpleDateFormat formatter = new SimpleDateFormat();
415 String dateString = formatter.format(new Date(System.currentTimeMillis()));
416 pw.println(dateString + ": " + msg);
417 pw.close();
418 FileUtils.setPermissions(
419 fname.toString(),
420 FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IROTH,
421 -1, -1);
422 } catch (java.io.IOException e) {
423 }
424 }
425
Todd Kennedy0eb97382017-10-03 16:57:22 -0700426 public static void enforceShellRestriction(String restriction, int callingUid, int userHandle) {
427 if (callingUid == Process.SHELL_UID) {
428 if (userHandle >= 0
429 && PackageManagerService.sUserManager.hasUserRestriction(
430 restriction, userHandle)) {
431 throw new SecurityException("Shell does not have permission to access user "
432 + userHandle);
433 } else if (userHandle < 0) {
434 Slog.e(PackageManagerService.TAG, "Unable to check shell permission for user "
435 + userHandle + "\n\t" + Debug.getCallers(3));
436 }
437 }
438 }
Todd Kennedy7c4c55d2017-11-02 10:01:39 -0700439
440 /**
441 * Derive the value of the {@code cpuAbiOverride} based on the provided
442 * value and an optional stored value from the package settings.
443 */
444 public static String deriveAbiOverride(String abiOverride, PackageSetting settings) {
445 String cpuAbiOverride = null;
446 if (NativeLibraryHelper.CLEAR_ABI_OVERRIDE.equals(abiOverride)) {
447 cpuAbiOverride = null;
448 } else if (abiOverride != null) {
449 cpuAbiOverride = abiOverride;
450 } else if (settings != null) {
451 cpuAbiOverride = settings.cpuAbiOverrideString;
452 }
453 return cpuAbiOverride;
454 }
455
456 /**
457 * Compares two sets of signatures. Returns:
458 * <br />
459 * {@link PackageManager#SIGNATURE_NEITHER_SIGNED}: if both signature sets are null,
460 * <br />
461 * {@link PackageManager#SIGNATURE_FIRST_NOT_SIGNED}: if the first signature set is null,
462 * <br />
463 * {@link PackageManager#SIGNATURE_SECOND_NOT_SIGNED}: if the second signature set is null,
464 * <br />
465 * {@link PackageManager#SIGNATURE_MATCH}: if the two signature sets are identical,
466 * <br />
467 * {@link PackageManager#SIGNATURE_NO_MATCH}: if the two signature sets differ.
468 */
469 public static int compareSignatures(Signature[] s1, Signature[] s2) {
470 if (s1 == null) {
471 return s2 == null
472 ? PackageManager.SIGNATURE_NEITHER_SIGNED
473 : PackageManager.SIGNATURE_FIRST_NOT_SIGNED;
474 }
475
476 if (s2 == null) {
477 return PackageManager.SIGNATURE_SECOND_NOT_SIGNED;
478 }
479
480 if (s1.length != s2.length) {
481 return PackageManager.SIGNATURE_NO_MATCH;
482 }
483
484 // Since both signature sets are of size 1, we can compare without HashSets.
485 if (s1.length == 1) {
486 return s1[0].equals(s2[0]) ?
487 PackageManager.SIGNATURE_MATCH :
488 PackageManager.SIGNATURE_NO_MATCH;
489 }
490
491 ArraySet<Signature> set1 = new ArraySet<Signature>();
492 for (Signature sig : s1) {
493 set1.add(sig);
494 }
495 ArraySet<Signature> set2 = new ArraySet<Signature>();
496 for (Signature sig : s2) {
497 set2.add(sig);
498 }
499 // Make sure s2 contains all signatures in s1.
500 if (set1.equals(set2)) {
501 return PackageManager.SIGNATURE_MATCH;
502 }
503 return PackageManager.SIGNATURE_NO_MATCH;
504 }
505
506 /**
507 * Used for backward compatibility to make sure any packages with
508 * certificate chains get upgraded to the new style. {@code existingSigs}
509 * will be in the old format (since they were stored on disk from before the
510 * system upgrade) and {@code scannedSigs} will be in the newer format.
511 */
512 private static boolean matchSignaturesCompat(String packageName,
Patrick Baumann420d58a2017-12-19 10:17:21 -0800513 PackageSignatures packageSignatures, PackageParser.SigningDetails parsedSignatures) {
Todd Kennedy7c4c55d2017-11-02 10:01:39 -0700514 ArraySet<Signature> existingSet = new ArraySet<Signature>();
Daniel Cashman77029c52018-01-18 16:19:29 -0800515 for (Signature sig : packageSignatures.mSigningDetails.signatures) {
Todd Kennedy7c4c55d2017-11-02 10:01:39 -0700516 existingSet.add(sig);
517 }
518 ArraySet<Signature> scannedCompatSet = new ArraySet<Signature>();
Patrick Baumann420d58a2017-12-19 10:17:21 -0800519 for (Signature sig : parsedSignatures.signatures) {
Todd Kennedy7c4c55d2017-11-02 10:01:39 -0700520 try {
521 Signature[] chainSignatures = sig.getChainSignatures();
522 for (Signature chainSig : chainSignatures) {
523 scannedCompatSet.add(chainSig);
524 }
525 } catch (CertificateEncodingException e) {
526 scannedCompatSet.add(sig);
527 }
528 }
529 // make sure the expanded scanned set contains all signatures in the existing one
530 if (scannedCompatSet.equals(existingSet)) {
531 // migrate the old signatures to the new scheme
Daniel Cashman77029c52018-01-18 16:19:29 -0800532 packageSignatures.mSigningDetails = parsedSignatures;
Todd Kennedy7c4c55d2017-11-02 10:01:39 -0700533 return true;
534 }
535 return false;
536 }
537
538 private static boolean matchSignaturesRecover(String packageName,
539 Signature[] existingSignatures, Signature[] parsedSignatures) {
540 String msg = null;
541 try {
542 if (Signature.areEffectiveMatch(existingSignatures, parsedSignatures)) {
543 logCriticalInfo(Log.INFO,
544 "Recovered effectively matching certificates for " + packageName);
545 return true;
546 }
547 } catch (CertificateException e) {
548 msg = e.getMessage();
549 }
550 logCriticalInfo(Log.INFO,
551 "Failed to recover certificates for " + packageName + ": " + msg);
552 return false;
553 }
554
555 /**
Victor Hsieh0c8f2e02017-10-06 14:44:52 -0700556 * Make sure the updated priv app is signed with the same key as the original APK file on the
557 * /system partition.
558 *
559 * <p>The rationale is that {@code disabledPkg} is a PackageSetting backed by xml files in /data
560 * and is not tamperproof.
561 */
562 private static boolean matchSignatureInSystem(PackageSetting pkgSetting,
563 PackageSetting disabledPkgSetting) {
564 try {
Victor Hsieh5f761242018-01-20 10:30:12 -0800565 PackageParser.collectCertificates(disabledPkgSetting.pkg, true /* skipVerify */);
Daniel Cashman77029c52018-01-18 16:19:29 -0800566 if (compareSignatures(pkgSetting.signatures.mSigningDetails.signatures,
567 disabledPkgSetting.signatures.mSigningDetails.signatures)
Victor Hsieh0c8f2e02017-10-06 14:44:52 -0700568 != PackageManager.SIGNATURE_MATCH) {
569 logCriticalInfo(Log.ERROR, "Updated system app mismatches cert on /system: " +
570 pkgSetting.name);
571 return false;
572 }
573 } catch (PackageParserException e) {
574 logCriticalInfo(Log.ERROR, "Failed to collect cert for " + pkgSetting.name + ": " +
575 e.getMessage());
576 return false;
577 }
578 return true;
579 }
580
Daniel Cashman5cdda342018-01-19 07:22:52 -0800581 /**
582 * Checks the signing certificates to see if the provided certificate is a member. Invalid for
583 * {@code SigningDetails} with multiple signing certificates.
584 * @param certificate certificate to check for membership
585 * @param signingDetails signing certificates record whose members are to be searched
586 * @return true if {@code certificate} is in {@code signingDetails}
587 */
588 public static boolean signingDetailsHasCertificate(
589 byte[] certificate, PackageParser.SigningDetails signingDetails) {
590 if (signingDetails == PackageParser.SigningDetails.UNKNOWN) {
591 return false;
592 }
593 Signature signature = new Signature(certificate);
594 if (signingDetails.hasPastSigningCertificates()) {
595 for (int i = 0; i < signingDetails.pastSigningCertificates.length; i++) {
596 if (signingDetails.pastSigningCertificates[i].equals(signature)) {
597 return true;
598 }
599 }
600 } else {
601 // no signing history, just check the current signer
602 if (signingDetails.signatures.length == 1
603 && signingDetails.signatures[0].equals(signature)) {
604 return true;
605 }
606 }
607 return false;
608 }
609
610 /**
611 * Checks the signing certificates to see if the provided certificate is a member. Invalid for
612 * {@code SigningDetails} with multiple signing certificaes.
613 * @param sha256Certificate certificate to check for membership
614 * @param signingDetails signing certificates record whose members are to be searched
615 * @return true if {@code certificate} is in {@code signingDetails}
616 */
617 public static boolean signingDetailsHasSha256Certificate(
618 byte[] sha256Certificate, PackageParser.SigningDetails signingDetails ) {
619 if (signingDetails == PackageParser.SigningDetails.UNKNOWN) {
620 return false;
621 }
622 if (signingDetails.hasPastSigningCertificates()) {
623 for (int i = 0; i < signingDetails.pastSigningCertificates.length; i++) {
624 byte[] digest = PackageUtils.computeSha256DigestBytes(
625 signingDetails.pastSigningCertificates[i].toByteArray());
626 if (Arrays.equals(sha256Certificate, digest)) {
627 return true;
628 }
629 }
630 } else {
631 // no signing history, just check the current signer
632 if (signingDetails.signatures.length == 1) {
633 byte[] digest = PackageUtils.computeSha256DigestBytes(
634 signingDetails.signatures[0].toByteArray());
635 if (Arrays.equals(sha256Certificate, digest)) {
636 return true;
637 }
638 }
639 }
640 return false;
641 }
642
Victor Hsieh55f14992018-01-13 14:12:59 -0800643 /** Returns true if APK Verity is enabled. */
644 static boolean isApkVerityEnabled() {
645 return SystemProperties.getInt("ro.apk_verity.mode", 0) != 0;
646 }
647
Victor Hsieh0c8f2e02017-10-06 14:44:52 -0700648 /** Returns true to force apk verification if the updated package (in /data) is a priv app. */
649 static boolean isApkVerificationForced(@Nullable PackageSetting disabledPs) {
Victor Hsieh55f14992018-01-13 14:12:59 -0800650 return disabledPs != null && disabledPs.isPrivileged() && isApkVerityEnabled();
Victor Hsieh0c8f2e02017-10-06 14:44:52 -0700651 }
652
653 /**
Todd Kennedy7c4c55d2017-11-02 10:01:39 -0700654 * Verifies that signatures match.
655 * @returns {@code true} if the compat signatures were matched; otherwise, {@code false}.
656 * @throws PackageManagerException if the signatures did not match.
657 */
658 public static boolean verifySignatures(PackageSetting pkgSetting,
Victor Hsieh0c8f2e02017-10-06 14:44:52 -0700659 PackageSetting disabledPkgSetting, PackageParser.SigningDetails parsedSignatures,
660 boolean compareCompat, boolean compareRecover)
Todd Kennedy7c4c55d2017-11-02 10:01:39 -0700661 throws PackageManagerException {
662 final String packageName = pkgSetting.name;
663 boolean compatMatch = false;
Daniel Cashman77029c52018-01-18 16:19:29 -0800664 if (pkgSetting.signatures.mSigningDetails.signatures != null) {
Todd Kennedy7c4c55d2017-11-02 10:01:39 -0700665 // Already existing package. Make sure signatures match
Daniel Cashman77029c52018-01-18 16:19:29 -0800666 boolean match = compareSignatures(pkgSetting.signatures.mSigningDetails.signatures,
Patrick Baumann420d58a2017-12-19 10:17:21 -0800667 parsedSignatures.signatures)
Todd Kennedy7c4c55d2017-11-02 10:01:39 -0700668 == PackageManager.SIGNATURE_MATCH;
669 if (!match && compareCompat) {
Patrick Baumann420d58a2017-12-19 10:17:21 -0800670 match = matchSignaturesCompat(packageName, pkgSetting.signatures,
671 parsedSignatures);
Todd Kennedy7c4c55d2017-11-02 10:01:39 -0700672 compatMatch = match;
673 }
674 if (!match && compareRecover) {
675 match = matchSignaturesRecover(
Daniel Cashman77029c52018-01-18 16:19:29 -0800676 packageName, pkgSetting.signatures.mSigningDetails.signatures,
Patrick Baumann420d58a2017-12-19 10:17:21 -0800677 parsedSignatures.signatures);
Todd Kennedy7c4c55d2017-11-02 10:01:39 -0700678 }
Victor Hsieh0c8f2e02017-10-06 14:44:52 -0700679
680 if (!match && isApkVerificationForced(disabledPkgSetting)) {
681 match = matchSignatureInSystem(pkgSetting, disabledPkgSetting);
682 }
683
Todd Kennedy7c4c55d2017-11-02 10:01:39 -0700684 if (!match) {
685 throw new PackageManagerException(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
686 "Package " + packageName +
Todd Kennedy96cb94b2017-11-29 13:17:12 -0800687 " signatures do not match previously installed version; ignoring!");
Todd Kennedy7c4c55d2017-11-02 10:01:39 -0700688 }
689 }
690 // Check for shared user signatures
Daniel Cashman77029c52018-01-18 16:19:29 -0800691 if (pkgSetting.sharedUser != null
692 && pkgSetting.sharedUser.signatures.mSigningDetails.signatures != null) {
Todd Kennedy7c4c55d2017-11-02 10:01:39 -0700693 // Already existing package. Make sure signatures match
Daniel Cashman77029c52018-01-18 16:19:29 -0800694 boolean match =
695 compareSignatures(
696 pkgSetting.sharedUser.signatures.mSigningDetails.signatures,
697 parsedSignatures.signatures) == PackageManager.SIGNATURE_MATCH;
Todd Kennedy3e654842017-11-29 13:58:53 -0800698 if (!match && compareCompat) {
Todd Kennedy7c4c55d2017-11-02 10:01:39 -0700699 match = matchSignaturesCompat(
700 packageName, pkgSetting.sharedUser.signatures, parsedSignatures);
701 }
Todd Kennedy3e654842017-11-29 13:58:53 -0800702 if (!match && compareRecover) {
Patrick Baumann420d58a2017-12-19 10:17:21 -0800703 match = matchSignaturesRecover(packageName,
Daniel Cashman77029c52018-01-18 16:19:29 -0800704 pkgSetting.sharedUser.signatures.mSigningDetails.signatures,
705 parsedSignatures.signatures);
Todd Kennedy7c4c55d2017-11-02 10:01:39 -0700706 compatMatch |= match;
707 }
Todd Kennedy3e654842017-11-29 13:58:53 -0800708 if (!match) {
Todd Kennedy7c4c55d2017-11-02 10:01:39 -0700709 throw new PackageManagerException(INSTALL_FAILED_SHARED_USER_INCOMPATIBLE,
710 "Package " + packageName
711 + " has no signatures that match those in shared user "
712 + pkgSetting.sharedUser.name + "; ignoring!");
713 }
714 }
715 return compatMatch;
716 }
717
718 public static int decompressFile(File srcFile, File dstFile) throws ErrnoException {
719 if (DEBUG_COMPRESSION) {
720 Slog.i(TAG, "Decompress file"
721 + "; src: " + srcFile.getAbsolutePath()
722 + ", dst: " + dstFile.getAbsolutePath());
723 }
724 try (
725 InputStream fileIn = new GZIPInputStream(new FileInputStream(srcFile));
726 OutputStream fileOut = new FileOutputStream(dstFile, false /*append*/);
727 ) {
728 Streams.copy(fileIn, fileOut);
729 Os.chmod(dstFile.getAbsolutePath(), 0644);
730 return PackageManager.INSTALL_SUCCEEDED;
731 } catch (IOException e) {
732 logCriticalInfo(Log.ERROR, "Failed to decompress file"
733 + "; src: " + srcFile.getAbsolutePath()
734 + ", dst: " + dstFile.getAbsolutePath());
735 }
736 return PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
737 }
738
739 public static File[] getCompressedFiles(String codePath) {
740 final File stubCodePath = new File(codePath);
741 final String stubName = stubCodePath.getName();
742
743 // The layout of a compressed package on a given partition is as follows :
744 //
745 // Compressed artifacts:
746 //
747 // /partition/ModuleName/foo.gz
748 // /partation/ModuleName/bar.gz
749 //
750 // Stub artifact:
751 //
752 // /partition/ModuleName-Stub/ModuleName-Stub.apk
753 //
754 // In other words, stub is on the same partition as the compressed artifacts
755 // and in a directory that's suffixed with "-Stub".
756 int idx = stubName.lastIndexOf(STUB_SUFFIX);
757 if (idx < 0 || (stubName.length() != (idx + STUB_SUFFIX.length()))) {
758 return null;
759 }
760
761 final File stubParentDir = stubCodePath.getParentFile();
762 if (stubParentDir == null) {
763 Slog.e(TAG, "Unable to determine stub parent dir for codePath: " + codePath);
764 return null;
765 }
766
767 final File compressedPath = new File(stubParentDir, stubName.substring(0, idx));
768 final File[] files = compressedPath.listFiles(new FilenameFilter() {
769 @Override
770 public boolean accept(File dir, String name) {
771 return name.toLowerCase().endsWith(COMPRESSED_EXTENSION);
772 }
773 });
774
775 if (DEBUG_COMPRESSION && files != null && files.length > 0) {
776 Slog.i(TAG, "getCompressedFiles[" + codePath + "]: " + Arrays.toString(files));
777 }
778
779 return files;
780 }
781
782 public static boolean compressedFileExists(String codePath) {
783 final File[] compressedFiles = getCompressedFiles(codePath);
784 return compressedFiles != null && compressedFiles.length > 0;
785 }
Narayan Kamath6d99f792016-05-16 17:34:48 +0100786}