1 /* 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 17 package com.android.server; 18 19 import static android.app.ActivityManager.UID_OBSERVER_ACTIVE; 20 import static android.app.ActivityManager.UID_OBSERVER_GONE; 21 22 import android.annotation.IntDef; 23 import android.annotation.Nullable; 24 25 import android.app.ActivityManager; 26 import android.app.ActivityManagerInternal; 27 import android.app.IActivityManager; 28 import android.app.IUidObserver; 29 import android.content.BroadcastReceiver; 30 import android.content.Context; 31 import android.content.Intent; 32 import android.content.IntentFilter; 33 import android.content.pm.ActivityInfo; 34 import android.content.pm.ApplicationInfo; 35 import android.content.pm.PackageManager; 36 import android.content.pm.ResolveInfo; 37 import android.database.ContentObserver; 38 import android.net.Uri; 39 import android.os.Binder; 40 import android.os.Build; 41 import android.os.Handler; 42 import android.os.Looper; 43 import android.os.Message; 44 import android.os.RemoteException; 45 import android.os.SystemProperties; 46 import android.os.UserHandle; 47 import android.os.UserManager; 48 import android.provider.MediaStore; 49 import android.provider.Settings; 50 import android.system.ErrnoException; 51 import android.system.Os; 52 import android.system.OsConstants; 53 import android.util.ArrayMap; 54 import android.util.ArraySet; 55 import android.util.Slog; 56 57 import com.android.internal.annotations.GuardedBy; 58 import com.android.internal.app.ResolverActivity; 59 import com.android.internal.os.BackgroundThread; 60 import com.android.internal.util.DumpUtils; 61 import com.android.internal.util.function.pooled.PooledLambda; 62 63 import com.android.server.wm.ActivityTaskManagerInternal; 64 import dalvik.system.DexFile; 65 import dalvik.system.VMRuntime; 66 67 import java.io.FileDescriptor; 68 import java.io.Closeable; 69 import java.io.InputStream; 70 import java.io.DataInputStream; 71 import java.io.IOException; 72 import java.io.PrintWriter; 73 import java.lang.annotation.Retention; 74 import java.lang.annotation.RetentionPolicy; 75 import java.util.List; 76 import java.util.ArrayList; 77 78 import java.util.zip.ZipFile; 79 import java.util.zip.ZipEntry; 80 81 /** 82 * <p>PinnerService pins important files for key processes in memory.</p> 83 * <p>Files to pin are specified in the config_defaultPinnerServiceFiles 84 * overlay.</p> 85 * <p>Pin the default camera application if specified in config_pinnerCameraApp.</p> 86 */ 87 public final class PinnerService extends SystemService { 88 private static final boolean DEBUG = false; 89 private static final String TAG = "PinnerService"; 90 91 private static final String PIN_META_FILENAME = "pinlist.meta"; 92 private static final int PAGE_SIZE = (int) Os.sysconf(OsConstants._SC_PAGESIZE); 93 private static final int MATCH_FLAGS = PackageManager.MATCH_DEFAULT_ONLY 94 | PackageManager.MATCH_DIRECT_BOOT_AWARE 95 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE; 96 97 private static final int KEY_CAMERA = 0; 98 private static final int KEY_HOME = 1; 99 100 private static final int MAX_CAMERA_PIN_SIZE = 80 * (1 << 20); // 80MB max for camera app. 101 private static final int MAX_HOME_PIN_SIZE = 6 * (1 << 20); // 6MB max for home app. 102 103 @IntDef({KEY_CAMERA, KEY_HOME}) 104 @Retention(RetentionPolicy.SOURCE) 105 public @interface AppKey {} 106 107 private final Context mContext; 108 private final ActivityTaskManagerInternal mAtmInternal; 109 private final ActivityManagerInternal mAmInternal; 110 private final IActivityManager mAm; 111 private final UserManager mUserManager; 112 113 /** The list of the statically pinned files. */ 114 @GuardedBy("this") 115 private final ArrayList<PinnedFile> mPinnedFiles = new ArrayList<>(); 116 117 /** The list of the pinned apps. This is a map from {@link AppKey} to a pinned app. */ 118 @GuardedBy("this") 119 private final ArrayMap<Integer, PinnedApp> mPinnedApps = new ArrayMap<>(); 120 121 /** 122 * The list of the pinned apps that need to be repinned as soon as the all processes of a given 123 * uid are no longer active. Note that with background dex opt, the new dex/vdex files are only 124 * loaded into the processes once it restarts. So in case background dex opt recompiled these 125 * files, we still need to keep the old ones pinned until the processes restart. 126 * <p> 127 * This is a map from uid to {@link AppKey} 128 */ 129 @GuardedBy("this") 130 private final ArrayMap<Integer, Integer> mPendingRepin = new ArrayMap<>(); 131 132 /** 133 * A set of {@link AppKey} that are configured to be pinned. 134 */ 135 private final ArraySet<Integer> mPinKeys = new ArraySet<>(); 136 137 private BinderService mBinderService; 138 private PinnerHandler mPinnerHandler = null; 139 140 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 141 @Override 142 public void onReceive(Context context, Intent intent) { 143 // If an app has updated, update pinned files accordingly. 144 if (Intent.ACTION_PACKAGE_REPLACED.equals(intent.getAction())) { 145 Uri packageUri = intent.getData(); 146 String packageName = packageUri.getSchemeSpecificPart(); 147 ArraySet<String> updatedPackages = new ArraySet<>(); 148 updatedPackages.add(packageName); 149 update(updatedPackages, true /* force */); 150 } 151 } 152 }; 153 PinnerService(Context context)154 public PinnerService(Context context) { 155 super(context); 156 157 mContext = context; 158 boolean shouldPinCamera = context.getResources().getBoolean( 159 com.android.internal.R.bool.config_pinnerCameraApp); 160 boolean shouldPinHome = context.getResources().getBoolean( 161 com.android.internal.R.bool.config_pinnerHomeApp); 162 if (shouldPinCamera) { 163 mPinKeys.add(KEY_CAMERA); 164 } 165 if (shouldPinHome) { 166 mPinKeys.add(KEY_HOME); 167 } 168 mPinnerHandler = new PinnerHandler(BackgroundThread.get().getLooper()); 169 170 mAtmInternal = LocalServices.getService(ActivityTaskManagerInternal.class); 171 mAmInternal = LocalServices.getService(ActivityManagerInternal.class); 172 mAm = ActivityManager.getService(); 173 174 mUserManager = mContext.getSystemService(UserManager.class); 175 176 IntentFilter filter = new IntentFilter(); 177 filter.addAction(Intent.ACTION_PACKAGE_REPLACED); 178 filter.addDataScheme("package"); 179 mContext.registerReceiver(mBroadcastReceiver, filter); 180 181 registerUidListener(); 182 registerUserSetupCompleteListener(); 183 } 184 185 @Override onStart()186 public void onStart() { 187 if (DEBUG) { 188 Slog.i(TAG, "Starting PinnerService"); 189 } 190 mBinderService = new BinderService(); 191 publishBinderService("pinner", mBinderService); 192 publishLocalService(PinnerService.class, this); 193 194 mPinnerHandler.obtainMessage(PinnerHandler.PIN_ONSTART_MSG).sendToTarget(); 195 sendPinAppsMessage(UserHandle.USER_SYSTEM); 196 } 197 198 /** 199 * Repin apps on user switch. 200 * <p> 201 * If more than one user is using the device each user may set a different preference for the 202 * individual apps. Make sure that user's preference is pinned into memory. 203 */ 204 @Override onSwitchUser(int userHandle)205 public void onSwitchUser(int userHandle) { 206 if (!mUserManager.isManagedProfile(userHandle)) { 207 sendPinAppsMessage(userHandle); 208 } 209 } 210 211 @Override onUnlockUser(int userHandle)212 public void onUnlockUser(int userHandle) { 213 if (!mUserManager.isManagedProfile(userHandle)) { 214 sendPinAppsMessage(userHandle); 215 } 216 } 217 218 /** 219 * Update the currently pinned files. 220 * Specifically, this only updates pinning for the apps that need to be pinned. 221 * The other files pinned in onStart will not need to be updated. 222 */ update(ArraySet<String> updatedPackages, boolean force)223 public void update(ArraySet<String> updatedPackages, boolean force) { 224 int currentUser = ActivityManager.getCurrentUser(); 225 for (int i = mPinKeys.size() - 1; i >= 0; i--) { 226 int key = mPinKeys.valueAt(i); 227 ApplicationInfo info = getInfoForKey(key, currentUser); 228 if (info != null && updatedPackages.contains(info.packageName)) { 229 Slog.i(TAG, "Updating pinned files for " + info.packageName + " force=" + force); 230 sendPinAppMessage(key, currentUser, force); 231 } 232 } 233 } 234 235 /** 236 * Handler for on start pinning message 237 */ handlePinOnStart()238 private void handlePinOnStart() { 239 final String bootImage = SystemProperties.get("dalvik.vm.boot-image", ""); 240 String[] filesToPin = null; 241 if (bootImage.endsWith("apex.art")) { 242 // Use the files listed for that specific boot image 243 filesToPin = mContext.getResources().getStringArray( 244 com.android.internal.R.array.config_apexBootImagePinnerServiceFiles); 245 } else { 246 // Files to pin come from the overlay and can be specified per-device config 247 filesToPin = mContext.getResources().getStringArray( 248 com.android.internal.R.array.config_defaultPinnerServiceFiles); 249 } 250 // Continue trying to pin each file even if we fail to pin some of them 251 for (String fileToPin : filesToPin) { 252 PinnedFile pf = pinFile(fileToPin, 253 Integer.MAX_VALUE, 254 /*attemptPinIntrospection=*/false); 255 if (pf == null) { 256 Slog.e(TAG, "Failed to pin file = " + fileToPin); 257 continue; 258 } 259 260 synchronized (this) { 261 mPinnedFiles.add(pf); 262 } 263 } 264 } 265 266 /** 267 * Registers a listener to repin the home app when user setup is complete, as the home intent 268 * initially resolves to setup wizard, but once setup is complete, it will resolve to the 269 * regular home app. 270 */ registerUserSetupCompleteListener()271 private void registerUserSetupCompleteListener() { 272 Uri userSetupCompleteUri = Settings.Secure.getUriFor( 273 Settings.Secure.USER_SETUP_COMPLETE); 274 mContext.getContentResolver().registerContentObserver(userSetupCompleteUri, 275 false, new ContentObserver(null) { 276 @Override 277 public void onChange(boolean selfChange, Uri uri) { 278 if (userSetupCompleteUri.equals(uri)) { 279 sendPinAppMessage(KEY_HOME, ActivityManager.getCurrentUser(), 280 true /* force */); 281 } 282 } 283 }, UserHandle.USER_ALL); 284 } 285 registerUidListener()286 private void registerUidListener() { 287 try { 288 mAm.registerUidObserver(new IUidObserver.Stub() { 289 @Override 290 public void onUidGone(int uid, boolean disabled) throws RemoteException { 291 mPinnerHandler.sendMessage(PooledLambda.obtainMessage( 292 PinnerService::handleUidGone, PinnerService.this, uid)); 293 } 294 295 @Override 296 public void onUidActive(int uid) throws RemoteException { 297 mPinnerHandler.sendMessage(PooledLambda.obtainMessage( 298 PinnerService::handleUidActive, PinnerService.this, uid)); 299 } 300 301 @Override 302 public void onUidIdle(int uid, boolean disabled) throws RemoteException { 303 } 304 305 @Override 306 public void onUidStateChanged(int uid, int procState, long procStateSeq) 307 throws RemoteException { 308 } 309 310 @Override 311 public void onUidCachedChanged(int uid, boolean cached) throws RemoteException { 312 } 313 }, UID_OBSERVER_GONE | UID_OBSERVER_ACTIVE, 0, "system"); 314 } catch (RemoteException e) { 315 Slog.e(TAG, "Failed to register uid observer", e); 316 } 317 } 318 handleUidGone(int uid)319 private void handleUidGone(int uid) { 320 updateActiveState(uid, false /* active */); 321 int key; 322 synchronized (this) { 323 324 // In case we have a pending repin, repin now. See mPendingRepin for more information. 325 key = mPendingRepin.getOrDefault(uid, -1); 326 if (key == -1) { 327 return; 328 } 329 mPendingRepin.remove(uid); 330 } 331 pinApp(key, ActivityManager.getCurrentUser(), false /* force */); 332 } 333 handleUidActive(int uid)334 private void handleUidActive(int uid) { 335 updateActiveState(uid, true /* active */); 336 } 337 updateActiveState(int uid, boolean active)338 private void updateActiveState(int uid, boolean active) { 339 synchronized (this) { 340 for (int i = mPinnedApps.size() - 1; i >= 0; i--) { 341 PinnedApp app = mPinnedApps.valueAt(i); 342 if (app.uid == uid) { 343 app.active = active; 344 } 345 } 346 } 347 } 348 unpinApp(@ppKey int key)349 private void unpinApp(@AppKey int key) { 350 ArrayList<PinnedFile> pinnedAppFiles; 351 synchronized (this) { 352 PinnedApp app = mPinnedApps.get(key); 353 if (app == null) { 354 return; 355 } 356 mPinnedApps.remove(key); 357 pinnedAppFiles = new ArrayList<>(app.mFiles); 358 } 359 for (PinnedFile pinnedFile : pinnedAppFiles) { 360 pinnedFile.close(); 361 } 362 } 363 isResolverActivity(ActivityInfo info)364 private boolean isResolverActivity(ActivityInfo info) { 365 return ResolverActivity.class.getName().equals(info.name); 366 } 367 getCameraInfo(int userHandle)368 private ApplicationInfo getCameraInfo(int userHandle) { 369 Intent cameraIntent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA); 370 ApplicationInfo info = getApplicationInfoForIntent(cameraIntent, userHandle, 371 false /* defaultToSystemApp */); 372 373 // If the STILL_IMAGE_CAMERA intent doesn't resolve, try the _SECURE intent. 374 // We don't use _SECURE first because it will never get set on a device 375 // without File-based Encryption. But if the user has only set the intent 376 // before unlocking their device, we may still be able to identify their 377 // preference using this intent. 378 if (info == null) { 379 cameraIntent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE); 380 info = getApplicationInfoForIntent(cameraIntent, userHandle, 381 false /* defaultToSystemApp */); 382 } 383 384 // If the _SECURE intent doesn't resolve, try the original intent but request 385 // the system app for camera if there was more than one result. 386 if (info == null) { 387 cameraIntent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA); 388 info = getApplicationInfoForIntent(cameraIntent, userHandle, 389 true /* defaultToSystemApp */); 390 } 391 return info; 392 } 393 getHomeInfo(int userHandle)394 private ApplicationInfo getHomeInfo(int userHandle) { 395 Intent intent = mAtmInternal.getHomeIntent(); 396 return getApplicationInfoForIntent(intent, userHandle, false); 397 } 398 getApplicationInfoForIntent(Intent intent, int userHandle, boolean defaultToSystemApp)399 private ApplicationInfo getApplicationInfoForIntent(Intent intent, int userHandle, 400 boolean defaultToSystemApp) { 401 if (intent == null) { 402 return null; 403 } 404 405 ResolveInfo resolveInfo = mContext.getPackageManager().resolveActivityAsUser(intent, 406 MATCH_FLAGS, userHandle); 407 408 // If this intent can resolve to only one app, choose that one. 409 // Otherwise, if we've requested to default to the system app, return it; 410 // if we have not requested that default, return null if there's more than one option. 411 // If there's more than one system app, return null since we don't know which to pick. 412 if (resolveInfo == null) { 413 return null; 414 } 415 416 if (!isResolverActivity(resolveInfo.activityInfo)) { 417 return resolveInfo.activityInfo.applicationInfo; 418 } 419 420 if (defaultToSystemApp) { 421 List<ResolveInfo> infoList = mContext.getPackageManager() 422 .queryIntentActivitiesAsUser(intent, MATCH_FLAGS, userHandle); 423 ApplicationInfo systemAppInfo = null; 424 for (ResolveInfo info : infoList) { 425 if ((info.activityInfo.applicationInfo.flags 426 & ApplicationInfo.FLAG_SYSTEM) != 0) { 427 if (systemAppInfo == null) { 428 systemAppInfo = info.activityInfo.applicationInfo; 429 } else { 430 // If there's more than one system app, return null due to ambiguity. 431 return null; 432 } 433 } 434 } 435 return systemAppInfo; 436 } 437 438 return null; 439 } 440 sendPinAppsMessage(int userHandle)441 private void sendPinAppsMessage(int userHandle) { 442 mPinnerHandler.sendMessage(PooledLambda.obtainMessage(PinnerService::pinApps, this, 443 userHandle)); 444 } 445 pinApps(int userHandle)446 private void pinApps(int userHandle) { 447 for (int i = mPinKeys.size() - 1; i >= 0; i--) { 448 int key = mPinKeys.valueAt(i); 449 pinApp(key, userHandle, true /* force */); 450 } 451 } 452 453 /** 454 * @see #pinApp(int, int, boolean) 455 */ sendPinAppMessage(int key, int userHandle, boolean force)456 private void sendPinAppMessage(int key, int userHandle, boolean force) { 457 mPinnerHandler.sendMessage(PooledLambda.obtainMessage(PinnerService::pinApp, this, 458 key, userHandle, force)); 459 } 460 461 /** 462 * Pins an app of a specific type {@code key}. 463 * 464 * @param force If false, this will not repin the app if it's currently active. See 465 * {@link #mPendingRepin}. 466 */ pinApp(int key, int userHandle, boolean force)467 private void pinApp(int key, int userHandle, boolean force) { 468 int uid = getUidForKey(key); 469 470 // In case the app is currently active, don't repin until next process restart. See 471 // mPendingRepin for more information. 472 if (!force && uid != -1) { 473 synchronized (this) { 474 mPendingRepin.put(uid, key); 475 } 476 return; 477 } 478 unpinApp(key); 479 ApplicationInfo info = getInfoForKey(key, userHandle); 480 if (info != null) { 481 pinApp(key, info); 482 } 483 } 484 485 /** 486 * Checks whether the pinned package with {@code key} is active or not. 487 488 * @return The uid of the pinned app, or {@code -1} otherwise. 489 */ getUidForKey(@ppKey int key)490 private int getUidForKey(@AppKey int key) { 491 synchronized (this) { 492 PinnedApp existing = mPinnedApps.get(key); 493 return existing != null && existing.active 494 ? existing.uid 495 : -1; 496 } 497 } 498 499 /** 500 * Retrieves the current application info for the given app type. 501 * 502 * @param key The app type to retrieve the info for. 503 * @param userHandle The user id of the current user. 504 */ getInfoForKey(@ppKey int key, int userHandle)505 private @Nullable ApplicationInfo getInfoForKey(@AppKey int key, int userHandle) { 506 switch (key) { 507 case KEY_CAMERA: 508 return getCameraInfo(userHandle); 509 case KEY_HOME: 510 return getHomeInfo(userHandle); 511 default: 512 return null; 513 } 514 } 515 516 /** 517 * @return The app type name for {@code key}. 518 */ getNameForKey(@ppKey int key)519 private String getNameForKey(@AppKey int key) { 520 switch (key) { 521 case KEY_CAMERA: 522 return "Camera"; 523 case KEY_HOME: 524 return "Home"; 525 default: 526 return null; 527 } 528 } 529 530 /** 531 * @return The maximum amount of bytes to be pinned for an app of type {@code key}. 532 */ getSizeLimitForKey(@ppKey int key)533 private int getSizeLimitForKey(@AppKey int key) { 534 switch (key) { 535 case KEY_CAMERA: 536 return MAX_CAMERA_PIN_SIZE; 537 case KEY_HOME: 538 return MAX_HOME_PIN_SIZE; 539 default: 540 return 0; 541 } 542 } 543 544 /** 545 * Pins an application. 546 * 547 * @param key The key of the app to pin. 548 * @param appInfo The corresponding app info. 549 */ pinApp(@ppKey int key, @Nullable ApplicationInfo appInfo)550 private void pinApp(@AppKey int key, @Nullable ApplicationInfo appInfo) { 551 if (appInfo == null) { 552 return; 553 } 554 555 PinnedApp pinnedApp = new PinnedApp(appInfo); 556 synchronized (this) { 557 mPinnedApps.put(key, pinnedApp); 558 } 559 560 // pin APK 561 int pinSizeLimit = getSizeLimitForKey(key); 562 String apk = appInfo.sourceDir; 563 PinnedFile pf = pinFile(apk, pinSizeLimit, /*attemptPinIntrospection=*/true); 564 if (pf == null) { 565 Slog.e(TAG, "Failed to pin " + apk); 566 return; 567 } 568 if (DEBUG) { 569 Slog.i(TAG, "Pinned " + pf.fileName); 570 } 571 synchronized (this) { 572 pinnedApp.mFiles.add(pf); 573 } 574 575 // determine the ABI from either ApplicationInfo or Build 576 String arch = "arm"; 577 if (appInfo.primaryCpuAbi != null) { 578 if (VMRuntime.is64BitAbi(appInfo.primaryCpuAbi)) { 579 arch = arch + "64"; 580 } 581 } else { 582 if (VMRuntime.is64BitAbi(Build.SUPPORTED_ABIS[0])) { 583 arch = arch + "64"; 584 } 585 } 586 587 // get the path to the odex or oat file 588 String baseCodePath = appInfo.getBaseCodePath(); 589 String[] files = null; 590 try { 591 files = DexFile.getDexFileOutputPaths(baseCodePath, arch); 592 } catch (IOException ioe) {} 593 if (files == null) { 594 return; 595 } 596 597 //not pinning the oat/odex is not a fatal error 598 for (String file : files) { 599 pf = pinFile(file, pinSizeLimit, /*attemptPinIntrospection=*/false); 600 if (pf != null) { 601 synchronized (this) { 602 pinnedApp.mFiles.add(pf); 603 } 604 if (DEBUG) { 605 Slog.i(TAG, "Pinned " + pf.fileName); 606 } 607 } 608 } 609 } 610 611 /** mlock length bytes of fileToPin in memory 612 * 613 * If attemptPinIntrospection is true, then treat the file to pin as a zip file and 614 * look for a "pinlist.meta" file in the archive root directory. The structure of this 615 * file is a PINLIST_META as described below: 616 * 617 * <pre> 618 * PINLIST_META: PIN_RANGE* 619 * PIN_RANGE: PIN_START PIN_LENGTH 620 * PIN_START: big endian i32: offset in bytes of pin region from file start 621 * PIN_LENGTH: big endian i32: length of pin region in bytes 622 * </pre> 623 * 624 * (We use big endian because that's what DataInputStream is hardcoded to use.) 625 * 626 * If attemptPinIntrospection is false, then we use a single implicit PIN_RANGE of (0, 627 * maxBytesToPin); that is, we attempt to pin the first maxBytesToPin bytes of the file. 628 * 629 * After we open a file, we march through the list of pin ranges and attempt to pin 630 * each one, stopping after we've pinned maxBytesToPin bytes. (We may truncate the last 631 * pinned range to fit.) In this way, by choosing to emit certain PIN_RANGE pairs 632 * before others, file generators can express pins in priority order, making most 633 * effective use of the pinned-page quota. 634 * 635 * N.B. Each PIN_RANGE is clamped to the actual bounds of the file; all inputs have a 636 * meaningful interpretation. Also, a range locking a single byte of a page locks the 637 * whole page. Any truncated PIN_RANGE at EOF is ignored. Overlapping pinned entries 638 * are legal, but each pin of a byte counts toward the pin quota regardless of whether 639 * that byte has already been pinned, so the generator of PINLIST_META ought to ensure 640 * that ranges are non-overlapping. 641 * 642 * @param fileToPin Path to file to pin 643 * @param maxBytesToPin Maximum number of bytes to pin 644 * @param attemptPinIntrospection If true, try to open file as a 645 * zip in order to extract the 646 * @return Pinned memory resource owner thing or null on error 647 */ pinFile(String fileToPin, int maxBytesToPin, boolean attemptPinIntrospection)648 private static PinnedFile pinFile(String fileToPin, 649 int maxBytesToPin, 650 boolean attemptPinIntrospection) { 651 ZipFile fileAsZip = null; 652 InputStream pinRangeStream = null; 653 try { 654 if (attemptPinIntrospection) { 655 fileAsZip = maybeOpenZip(fileToPin); 656 } 657 658 if (fileAsZip != null) { 659 pinRangeStream = maybeOpenPinMetaInZip(fileAsZip, fileToPin); 660 } 661 662 Slog.d(TAG, "pinRangeStream: " + pinRangeStream); 663 664 PinRangeSource pinRangeSource = (pinRangeStream != null) 665 ? new PinRangeSourceStream(pinRangeStream) 666 : new PinRangeSourceStatic(0, Integer.MAX_VALUE /* will be clipped */); 667 return pinFileRanges(fileToPin, maxBytesToPin, pinRangeSource); 668 } finally { 669 safeClose(pinRangeStream); 670 safeClose(fileAsZip); // Also closes any streams we've opened 671 } 672 } 673 674 /** 675 * Attempt to open a file as a zip file. On any sort of corruption, log, swallow the 676 * error, and return null. 677 */ maybeOpenZip(String fileName)678 private static ZipFile maybeOpenZip(String fileName) { 679 ZipFile zip = null; 680 try { 681 zip = new ZipFile(fileName); 682 } catch (IOException ex) { 683 Slog.w(TAG, 684 String.format( 685 "could not open \"%s\" as zip: pinning as blob", 686 fileName), 687 ex); 688 } 689 return zip; 690 } 691 692 /** 693 * Open a pin metadata file in the zip if one is present. 694 * 695 * @param zipFile Zip file to search 696 * @return Open input stream or null on any error 697 */ maybeOpenPinMetaInZip(ZipFile zipFile, String fileName)698 private static InputStream maybeOpenPinMetaInZip(ZipFile zipFile, String fileName) { 699 ZipEntry pinMetaEntry = zipFile.getEntry(PIN_META_FILENAME); 700 InputStream pinMetaStream = null; 701 if (pinMetaEntry != null) { 702 try { 703 pinMetaStream = zipFile.getInputStream(pinMetaEntry); 704 } catch (IOException ex) { 705 Slog.w(TAG, 706 String.format("error reading pin metadata \"%s\": pinning as blob", 707 fileName), 708 ex); 709 } 710 } 711 return pinMetaStream; 712 } 713 714 private static abstract class PinRangeSource { 715 /** Retrive a range to pin. 716 * 717 * @param outPinRange Receives the pin region 718 * @return True if we filled in outPinRange or false if we're out of pin entries 719 */ read(PinRange outPinRange)720 abstract boolean read(PinRange outPinRange); 721 } 722 723 private static final class PinRangeSourceStatic extends PinRangeSource { 724 private final int mPinStart; 725 private final int mPinLength; 726 private boolean mDone = false; 727 PinRangeSourceStatic(int pinStart, int pinLength)728 PinRangeSourceStatic(int pinStart, int pinLength) { 729 mPinStart = pinStart; 730 mPinLength = pinLength; 731 } 732 733 @Override read(PinRange outPinRange)734 boolean read(PinRange outPinRange) { 735 outPinRange.start = mPinStart; 736 outPinRange.length = mPinLength; 737 boolean done = mDone; 738 mDone = true; 739 return !done; 740 } 741 } 742 743 private static final class PinRangeSourceStream extends PinRangeSource { 744 private final DataInputStream mStream; 745 private boolean mDone = false; 746 PinRangeSourceStream(InputStream stream)747 PinRangeSourceStream(InputStream stream) { 748 mStream = new DataInputStream(stream); 749 } 750 751 @Override read(PinRange outPinRange)752 boolean read(PinRange outPinRange) { 753 if (!mDone) { 754 try { 755 outPinRange.start = mStream.readInt(); 756 outPinRange.length = mStream.readInt(); 757 } catch (IOException ex) { 758 mDone = true; 759 } 760 } 761 return !mDone; 762 } 763 } 764 765 /** 766 * Helper for pinFile. 767 * 768 * @param fileToPin Name of file to pin 769 * @param maxBytesToPin Maximum number of bytes to pin 770 * @param pinRangeSource Read PIN_RANGE entries from this stream to tell us what bytes 771 * to pin. 772 * @return PinnedFile or null on error 773 */ pinFileRanges( String fileToPin, int maxBytesToPin, PinRangeSource pinRangeSource)774 private static PinnedFile pinFileRanges( 775 String fileToPin, 776 int maxBytesToPin, 777 PinRangeSource pinRangeSource) 778 { 779 FileDescriptor fd = new FileDescriptor(); 780 long address = -1; 781 int mapSize = 0; 782 783 try { 784 int openFlags = (OsConstants.O_RDONLY | 785 OsConstants.O_CLOEXEC | 786 OsConstants.O_NOFOLLOW); 787 fd = Os.open(fileToPin, openFlags, 0); 788 mapSize = (int) Math.min(Os.fstat(fd).st_size, Integer.MAX_VALUE); 789 address = Os.mmap(0, mapSize, 790 OsConstants.PROT_READ, 791 OsConstants.MAP_SHARED, 792 fd, /*offset=*/0); 793 794 PinRange pinRange = new PinRange(); 795 int bytesPinned = 0; 796 797 // We pin at page granularity, so make sure the limit is page-aligned 798 if (maxBytesToPin % PAGE_SIZE != 0) { 799 maxBytesToPin -= maxBytesToPin % PAGE_SIZE; 800 } 801 802 while (bytesPinned < maxBytesToPin && pinRangeSource.read(pinRange)) { 803 int pinStart = pinRange.start; 804 int pinLength = pinRange.length; 805 pinStart = clamp(0, pinStart, mapSize); 806 pinLength = clamp(0, pinLength, mapSize - pinStart); 807 pinLength = Math.min(maxBytesToPin - bytesPinned, pinLength); 808 809 // mlock doesn't require the region to be page-aligned, but we snap the 810 // lock region to page boundaries anyway so that we don't under-count 811 // locking a single byte of a page as a charge of one byte even though the 812 // OS will retain the whole page. Thanks to this adjustment, we slightly 813 // over-count the pin charge of back-to-back pins touching the same page, 814 // but better that than undercounting. Besides: nothing stops pin metafile 815 // creators from making the actual regions page-aligned. 816 pinLength += pinStart % PAGE_SIZE; 817 pinStart -= pinStart % PAGE_SIZE; 818 if (pinLength % PAGE_SIZE != 0) { 819 pinLength += PAGE_SIZE - pinLength % PAGE_SIZE; 820 } 821 pinLength = clamp(0, pinLength, maxBytesToPin - bytesPinned); 822 823 if (pinLength > 0) { 824 if (DEBUG) { 825 Slog.d(TAG, 826 String.format( 827 "pinning at %s %s bytes of %s", 828 pinStart, pinLength, fileToPin)); 829 } 830 Os.mlock(address + pinStart, pinLength); 831 } 832 bytesPinned += pinLength; 833 } 834 835 PinnedFile pinnedFile = new PinnedFile(address, mapSize, fileToPin, bytesPinned); 836 address = -1; // Ownership transferred 837 return pinnedFile; 838 } catch (ErrnoException ex) { 839 Slog.e(TAG, "Could not pin file " + fileToPin, ex); 840 return null; 841 } finally { 842 safeClose(fd); 843 if (address >= 0) { 844 safeMunmap(address, mapSize); 845 } 846 } 847 } 848 clamp(int min, int value, int max)849 private static int clamp(int min, int value, int max) { 850 return Math.max(min, Math.min(value, max)); 851 } 852 safeMunmap(long address, long mapSize)853 private static void safeMunmap(long address, long mapSize) { 854 try { 855 Os.munmap(address, mapSize); 856 } catch (ErrnoException ex) { 857 Slog.w(TAG, "ignoring error in unmap", ex); 858 } 859 } 860 861 /** 862 * Close FD, swallowing irrelevant errors. 863 */ safeClose(@ullable FileDescriptor fd)864 private static void safeClose(@Nullable FileDescriptor fd) { 865 if (fd != null && fd.valid()) { 866 try { 867 Os.close(fd); 868 } catch (ErrnoException ex) { 869 // Swallow the exception: non-EBADF errors in close(2) 870 // indicate deferred paging write errors, which we 871 // don't care about here. The underlying file 872 // descriptor is always closed. 873 if (ex.errno == OsConstants.EBADF) { 874 throw new AssertionError(ex); 875 } 876 } 877 } 878 } 879 880 /** 881 * Close closeable thing, swallowing errors. 882 */ safeClose(@ullable Closeable thing)883 private static void safeClose(@Nullable Closeable thing) { 884 if (thing != null) { 885 try { 886 thing.close(); 887 } catch (IOException ex) { 888 Slog.w(TAG, "ignoring error closing resource: " + thing, ex); 889 } 890 } 891 } 892 893 private final class BinderService extends Binder { 894 @Override dump(FileDescriptor fd, PrintWriter pw, String[] args)895 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 896 if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; 897 synchronized (PinnerService.this) { 898 long totalSize = 0; 899 for (PinnedFile pinnedFile : mPinnedFiles) { 900 pw.format("%s %s\n", pinnedFile.fileName, pinnedFile.bytesPinned); 901 totalSize += pinnedFile.bytesPinned; 902 } 903 pw.println(); 904 for (int key : mPinnedApps.keySet()) { 905 PinnedApp app = mPinnedApps.get(key); 906 pw.print(getNameForKey(key)); 907 pw.print(" uid="); pw.print(app.uid); 908 pw.print(" active="); pw.print(app.active); 909 pw.println(); 910 for (PinnedFile pf : mPinnedApps.get(key).mFiles) { 911 pw.print(" "); pw.format("%s %s\n", pf.fileName, pf.bytesPinned); 912 totalSize += pf.bytesPinned; 913 } 914 } 915 pw.format("Total size: %s\n", totalSize); 916 pw.println(); 917 if (!mPendingRepin.isEmpty()) { 918 pw.print("Pending repin: "); 919 for (int key : mPendingRepin.values()) { 920 pw.print(getNameForKey(key)); pw.print(' '); 921 } 922 pw.println(); 923 } 924 } 925 } 926 } 927 928 private static final class PinnedFile implements AutoCloseable { 929 private long mAddress; 930 final int mapSize; 931 final String fileName; 932 final int bytesPinned; 933 PinnedFile(long address, int mapSize, String fileName, int bytesPinned)934 PinnedFile(long address, int mapSize, String fileName, int bytesPinned) { 935 mAddress = address; 936 this.mapSize = mapSize; 937 this.fileName = fileName; 938 this.bytesPinned = bytesPinned; 939 } 940 941 @Override close()942 public void close() { 943 if (mAddress >= 0) { 944 safeMunmap(mAddress, mapSize); 945 mAddress = -1; 946 } 947 } 948 949 @Override finalize()950 public void finalize() { 951 close(); 952 } 953 } 954 955 final static class PinRange { 956 int start; 957 int length; 958 } 959 960 /** 961 * Represents an app that was pinned. 962 */ 963 private final class PinnedApp { 964 965 /** 966 * The uid of the package being pinned. This stays constant while the package stays 967 * installed. 968 */ 969 final int uid; 970 971 /** Whether it is currently active, i.e. there is a running process from that package. */ 972 boolean active; 973 974 /** List of pinned files. */ 975 final ArrayList<PinnedFile> mFiles = new ArrayList<>(); 976 PinnedApp(ApplicationInfo appInfo)977 private PinnedApp(ApplicationInfo appInfo) { 978 uid = appInfo.uid; 979 active = mAmInternal.isUidActive(uid); 980 } 981 } 982 983 final class PinnerHandler extends Handler { 984 static final int PIN_ONSTART_MSG = 4001; 985 PinnerHandler(Looper looper)986 public PinnerHandler(Looper looper) { 987 super(looper, null, true); 988 } 989 990 @Override handleMessage(Message msg)991 public void handleMessage(Message msg) { 992 switch (msg.what) { 993 case PIN_ONSTART_MSG: 994 { 995 handlePinOnStart(); 996 } 997 break; 998 999 default: 1000 super.handleMessage(msg); 1001 } 1002 } 1003 } 1004 1005 } 1006