• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 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.pm;
18 
19 import static android.content.pm.PackageManager.INSTALL_FAILED_ABORTED;
20 import static android.content.pm.PackageManager.INSTALL_FAILED_CONTAINER_ERROR;
21 import static android.content.pm.PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
22 import static android.content.pm.PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
23 import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK;
24 import static android.system.OsConstants.O_CREAT;
25 import static android.system.OsConstants.O_RDONLY;
26 import static android.system.OsConstants.O_WRONLY;
27 import static com.android.server.pm.PackageInstallerService.prepareExternalStageCid;
28 import static com.android.server.pm.PackageInstallerService.prepareStageDir;
29 
30 import android.app.admin.DevicePolicyManager;
31 import android.content.Context;
32 import android.content.Intent;
33 import android.content.IntentSender;
34 import android.content.pm.ApplicationInfo;
35 import android.content.pm.IPackageInstallObserver2;
36 import android.content.pm.IPackageInstallerSession;
37 import android.content.pm.PackageInfo;
38 import android.content.pm.PackageInstaller;
39 import android.content.pm.PackageInstaller.SessionInfo;
40 import android.content.pm.PackageInstaller.SessionParams;
41 import android.content.pm.PackageManager;
42 import android.content.pm.PackageParser;
43 import android.content.pm.PackageParser.ApkLite;
44 import android.content.pm.PackageParser.PackageLite;
45 import android.content.pm.PackageParser.PackageParserException;
46 import android.content.pm.Signature;
47 import android.os.Bundle;
48 import android.os.FileBridge;
49 import android.os.FileUtils;
50 import android.os.Handler;
51 import android.os.Looper;
52 import android.os.Message;
53 import android.os.ParcelFileDescriptor;
54 import android.os.Process;
55 import android.os.RemoteException;
56 import android.os.SELinux;
57 import android.os.UserHandle;
58 import android.system.ErrnoException;
59 import android.system.Os;
60 import android.system.OsConstants;
61 import android.system.StructStat;
62 import android.text.TextUtils;
63 import android.util.ArraySet;
64 import android.util.ExceptionUtils;
65 import android.util.MathUtils;
66 import android.util.Slog;
67 
68 import libcore.io.IoUtils;
69 import libcore.io.Libcore;
70 
71 import com.android.internal.annotations.GuardedBy;
72 import com.android.internal.content.NativeLibraryHelper;
73 import com.android.internal.content.PackageHelper;
74 import com.android.internal.os.InstallerConnection.InstallerException;
75 import com.android.internal.util.ArrayUtils;
76 import com.android.internal.util.IndentingPrintWriter;
77 import com.android.internal.util.Preconditions;
78 import com.android.server.pm.PackageInstallerService.PackageInstallObserverAdapter;
79 
80 import java.io.File;
81 import java.io.FileDescriptor;
82 import java.io.FileFilter;
83 import java.io.IOException;
84 import java.security.cert.Certificate;
85 import java.util.ArrayList;
86 import java.util.Arrays;
87 import java.util.List;
88 import java.util.concurrent.atomic.AtomicInteger;
89 
90 public class PackageInstallerSession extends IPackageInstallerSession.Stub {
91     private static final String TAG = "PackageInstaller";
92     private static final boolean LOGD = true;
93     private static final String REMOVE_SPLIT_MARKER_EXTENSION = ".removed";
94 
95     private static final int MSG_COMMIT = 0;
96 
97     // TODO: enforce INSTALL_ALLOW_TEST
98     // TODO: enforce INSTALL_ALLOW_DOWNGRADE
99 
100     private final PackageInstallerService.InternalCallback mCallback;
101     private final Context mContext;
102     private final PackageManagerService mPm;
103     private final Handler mHandler;
104     private final boolean mIsInstallerDeviceOwner;
105 
106     final int sessionId;
107     final int userId;
108     final String installerPackageName;
109     final int installerUid;
110     final SessionParams params;
111     final long createdMillis;
112 
113     /** Staging location where client data is written. */
114     final File stageDir;
115     final String stageCid;
116 
117     private final AtomicInteger mActiveCount = new AtomicInteger();
118 
119     private final Object mLock = new Object();
120 
121     @GuardedBy("mLock")
122     private float mClientProgress = 0;
123     @GuardedBy("mLock")
124     private float mInternalProgress = 0;
125 
126     @GuardedBy("mLock")
127     private float mProgress = 0;
128     @GuardedBy("mLock")
129     private float mReportedProgress = -1;
130 
131     @GuardedBy("mLock")
132     private boolean mPrepared = false;
133     @GuardedBy("mLock")
134     private boolean mSealed = false;
135     @GuardedBy("mLock")
136     private boolean mPermissionsAccepted = false;
137     @GuardedBy("mLock")
138     private boolean mRelinquished = false;
139     @GuardedBy("mLock")
140     private boolean mDestroyed = false;
141 
142     private int mFinalStatus;
143     private String mFinalMessage;
144 
145     @GuardedBy("mLock")
146     private ArrayList<FileBridge> mBridges = new ArrayList<>();
147 
148     @GuardedBy("mLock")
149     private IPackageInstallObserver2 mRemoteObserver;
150 
151     /** Fields derived from commit parsing */
152     private String mPackageName;
153     private int mVersionCode;
154     private Signature[] mSignatures;
155     private Certificate[][] mCertificates;
156 
157     /**
158      * Path to the validated base APK for this session, which may point at an
159      * APK inside the session (when the session defines the base), or it may
160      * point at the existing base APK (when adding splits to an existing app).
161      * <p>
162      * This is used when confirming permissions, since we can't fully stage the
163      * session inside an ASEC before confirming with user.
164      */
165     @GuardedBy("mLock")
166     private File mResolvedBaseFile;
167 
168     @GuardedBy("mLock")
169     private File mResolvedStageDir;
170 
171     @GuardedBy("mLock")
172     private final List<File> mResolvedStagedFiles = new ArrayList<>();
173     @GuardedBy("mLock")
174     private final List<File> mResolvedInheritedFiles = new ArrayList<>();
175     @GuardedBy("mLock")
176     private final List<String> mResolvedInstructionSets = new ArrayList<>();
177     @GuardedBy("mLock")
178     private File mInheritedFilesBase;
179 
180     private static final FileFilter sAddedFilter = new FileFilter() {
181         @Override
182         public boolean accept(File file) {
183             // Installers can't stage directories, so it's fine to ignore
184             // entries like "lost+found".
185             if (file.isDirectory()) return false;
186             if (file.getName().endsWith(REMOVE_SPLIT_MARKER_EXTENSION)) return false;
187             return true;
188         }
189     };
190     private static final FileFilter sRemovedFilter = new FileFilter() {
191         @Override
192         public boolean accept(File file) {
193             if (file.isDirectory()) return false;
194             if (!file.getName().endsWith(REMOVE_SPLIT_MARKER_EXTENSION)) return false;
195             return true;
196         }
197     };
198 
199     private final Handler.Callback mHandlerCallback = new Handler.Callback() {
200         @Override
201         public boolean handleMessage(Message msg) {
202             synchronized (mLock) {
203                 if (msg.obj != null) {
204                     mRemoteObserver = (IPackageInstallObserver2) msg.obj;
205                 }
206 
207                 try {
208                     commitLocked();
209                 } catch (PackageManagerException e) {
210                     final String completeMsg = ExceptionUtils.getCompleteMessage(e);
211                     Slog.e(TAG, "Commit of session " + sessionId + " failed: " + completeMsg);
212                     destroyInternal();
213                     dispatchSessionFinished(e.error, completeMsg, null);
214                 }
215 
216                 return true;
217             }
218         }
219     };
220 
PackageInstallerSession(PackageInstallerService.InternalCallback callback, Context context, PackageManagerService pm, Looper looper, int sessionId, int userId, String installerPackageName, int installerUid, SessionParams params, long createdMillis, File stageDir, String stageCid, boolean prepared, boolean sealed)221     public PackageInstallerSession(PackageInstallerService.InternalCallback callback,
222             Context context, PackageManagerService pm, Looper looper, int sessionId, int userId,
223             String installerPackageName, int installerUid, SessionParams params, long createdMillis,
224             File stageDir, String stageCid, boolean prepared, boolean sealed) {
225         mCallback = callback;
226         mContext = context;
227         mPm = pm;
228         mHandler = new Handler(looper, mHandlerCallback);
229 
230         this.sessionId = sessionId;
231         this.userId = userId;
232         this.installerPackageName = installerPackageName;
233         this.installerUid = installerUid;
234         this.params = params;
235         this.createdMillis = createdMillis;
236         this.stageDir = stageDir;
237         this.stageCid = stageCid;
238 
239         if ((stageDir == null) == (stageCid == null)) {
240             throw new IllegalArgumentException(
241                     "Exactly one of stageDir or stageCid stage must be set");
242         }
243 
244         mPrepared = prepared;
245         mSealed = sealed;
246 
247         // Device owners are allowed to silently install packages, so the permission check is
248         // waived if the installer is the device owner.
249         DevicePolicyManager dpm = (DevicePolicyManager) mContext.getSystemService(
250                 Context.DEVICE_POLICY_SERVICE);
251         final boolean isPermissionGranted =
252                 (mPm.checkUidPermission(android.Manifest.permission.INSTALL_PACKAGES, installerUid)
253                         == PackageManager.PERMISSION_GRANTED);
254         final boolean isInstallerRoot = (installerUid == Process.ROOT_UID);
255         final boolean forcePermissionPrompt =
256                 (params.installFlags & PackageManager.INSTALL_FORCE_PERMISSION_PROMPT) != 0;
257         mIsInstallerDeviceOwner = (dpm != null) && dpm.isDeviceOwnerAppOnCallingUser(
258                 installerPackageName);
259         if ((isPermissionGranted
260                         || isInstallerRoot
261                         || mIsInstallerDeviceOwner)
262                 && !forcePermissionPrompt) {
263             mPermissionsAccepted = true;
264         } else {
265             mPermissionsAccepted = false;
266         }
267     }
268 
generateInfo()269     public SessionInfo generateInfo() {
270         final SessionInfo info = new SessionInfo();
271         synchronized (mLock) {
272             info.sessionId = sessionId;
273             info.installerPackageName = installerPackageName;
274             info.resolvedBaseCodePath = (mResolvedBaseFile != null) ?
275                     mResolvedBaseFile.getAbsolutePath() : null;
276             info.progress = mProgress;
277             info.sealed = mSealed;
278             info.active = mActiveCount.get() > 0;
279 
280             info.mode = params.mode;
281             info.sizeBytes = params.sizeBytes;
282             info.appPackageName = params.appPackageName;
283             info.appIcon = params.appIcon;
284             info.appLabel = params.appLabel;
285         }
286         return info;
287     }
288 
isPrepared()289     public boolean isPrepared() {
290         synchronized (mLock) {
291             return mPrepared;
292         }
293     }
294 
isSealed()295     public boolean isSealed() {
296         synchronized (mLock) {
297             return mSealed;
298         }
299     }
300 
assertPreparedAndNotSealed(String cookie)301     private void assertPreparedAndNotSealed(String cookie) {
302         synchronized (mLock) {
303             if (!mPrepared) {
304                 throw new IllegalStateException(cookie + " before prepared");
305             }
306             if (mSealed) {
307                 throw new SecurityException(cookie + " not allowed after commit");
308             }
309         }
310     }
311 
312     /**
313      * Resolve the actual location where staged data should be written. This
314      * might point at an ASEC mount point, which is why we delay path resolution
315      * until someone actively works with the session.
316      */
resolveStageDir()317     private File resolveStageDir() throws IOException {
318         synchronized (mLock) {
319             if (mResolvedStageDir == null) {
320                 if (stageDir != null) {
321                     mResolvedStageDir = stageDir;
322                 } else {
323                     final String path = PackageHelper.getSdDir(stageCid);
324                     if (path != null) {
325                         mResolvedStageDir = new File(path);
326                     } else {
327                         throw new IOException("Failed to resolve path to container " + stageCid);
328                     }
329                 }
330             }
331             return mResolvedStageDir;
332         }
333     }
334 
335     @Override
setClientProgress(float progress)336     public void setClientProgress(float progress) {
337         synchronized (mLock) {
338             // Always publish first staging movement
339             final boolean forcePublish = (mClientProgress == 0);
340             mClientProgress = progress;
341             computeProgressLocked(forcePublish);
342         }
343     }
344 
345     @Override
addClientProgress(float progress)346     public void addClientProgress(float progress) {
347         synchronized (mLock) {
348             setClientProgress(mClientProgress + progress);
349         }
350     }
351 
computeProgressLocked(boolean forcePublish)352     private void computeProgressLocked(boolean forcePublish) {
353         mProgress = MathUtils.constrain(mClientProgress * 0.8f, 0f, 0.8f)
354                 + MathUtils.constrain(mInternalProgress * 0.2f, 0f, 0.2f);
355 
356         // Only publish when meaningful change
357         if (forcePublish || Math.abs(mProgress - mReportedProgress) >= 0.01) {
358             mReportedProgress = mProgress;
359             mCallback.onSessionProgressChanged(this, mProgress);
360         }
361     }
362 
363     @Override
getNames()364     public String[] getNames() {
365         assertPreparedAndNotSealed("getNames");
366         try {
367             return resolveStageDir().list();
368         } catch (IOException e) {
369             throw ExceptionUtils.wrap(e);
370         }
371     }
372 
373     @Override
removeSplit(String splitName)374     public void removeSplit(String splitName) {
375         if (TextUtils.isEmpty(params.appPackageName)) {
376             throw new IllegalStateException("Must specify package name to remove a split");
377         }
378         try {
379             createRemoveSplitMarker(splitName);
380         } catch (IOException e) {
381             throw ExceptionUtils.wrap(e);
382         }
383     }
384 
createRemoveSplitMarker(String splitName)385     private void createRemoveSplitMarker(String splitName) throws IOException {
386         try {
387             final String markerName = splitName + REMOVE_SPLIT_MARKER_EXTENSION;
388             if (!FileUtils.isValidExtFilename(markerName)) {
389                 throw new IllegalArgumentException("Invalid marker: " + markerName);
390             }
391             final File target = new File(resolveStageDir(), markerName);
392             target.createNewFile();
393             Os.chmod(target.getAbsolutePath(), 0 /*mode*/);
394         } catch (ErrnoException e) {
395             throw e.rethrowAsIOException();
396         }
397     }
398 
399     @Override
openWrite(String name, long offsetBytes, long lengthBytes)400     public ParcelFileDescriptor openWrite(String name, long offsetBytes, long lengthBytes) {
401         try {
402             return openWriteInternal(name, offsetBytes, lengthBytes);
403         } catch (IOException e) {
404             throw ExceptionUtils.wrap(e);
405         }
406     }
407 
openWriteInternal(String name, long offsetBytes, long lengthBytes)408     private ParcelFileDescriptor openWriteInternal(String name, long offsetBytes, long lengthBytes)
409             throws IOException {
410         // Quick sanity check of state, and allocate a pipe for ourselves. We
411         // then do heavy disk allocation outside the lock, but this open pipe
412         // will block any attempted install transitions.
413         final FileBridge bridge;
414         synchronized (mLock) {
415             assertPreparedAndNotSealed("openWrite");
416 
417             bridge = new FileBridge();
418             mBridges.add(bridge);
419         }
420 
421         try {
422             // Use installer provided name for now; we always rename later
423             if (!FileUtils.isValidExtFilename(name)) {
424                 throw new IllegalArgumentException("Invalid name: " + name);
425             }
426             final File target = new File(resolveStageDir(), name);
427 
428             // TODO: this should delegate to DCS so the system process avoids
429             // holding open FDs into containers.
430             final FileDescriptor targetFd = Libcore.os.open(target.getAbsolutePath(),
431                     O_CREAT | O_WRONLY, 0644);
432             Os.chmod(target.getAbsolutePath(), 0644);
433 
434             // If caller specified a total length, allocate it for them. Free up
435             // cache space to grow, if needed.
436             if (lengthBytes > 0) {
437                 final StructStat stat = Libcore.os.fstat(targetFd);
438                 final long deltaBytes = lengthBytes - stat.st_size;
439                 // Only need to free up space when writing to internal stage
440                 if (stageDir != null && deltaBytes > 0) {
441                     mPm.freeStorage(params.volumeUuid, deltaBytes);
442                 }
443                 Libcore.os.posix_fallocate(targetFd, 0, lengthBytes);
444             }
445 
446             if (offsetBytes > 0) {
447                 Libcore.os.lseek(targetFd, offsetBytes, OsConstants.SEEK_SET);
448             }
449 
450             bridge.setTargetFile(targetFd);
451             bridge.start();
452             return new ParcelFileDescriptor(bridge.getClientSocket());
453 
454         } catch (ErrnoException e) {
455             throw e.rethrowAsIOException();
456         }
457     }
458 
459     @Override
openRead(String name)460     public ParcelFileDescriptor openRead(String name) {
461         try {
462             return openReadInternal(name);
463         } catch (IOException e) {
464             throw ExceptionUtils.wrap(e);
465         }
466     }
467 
openReadInternal(String name)468     private ParcelFileDescriptor openReadInternal(String name) throws IOException {
469         assertPreparedAndNotSealed("openRead");
470 
471         try {
472             if (!FileUtils.isValidExtFilename(name)) {
473                 throw new IllegalArgumentException("Invalid name: " + name);
474             }
475             final File target = new File(resolveStageDir(), name);
476 
477             final FileDescriptor targetFd = Libcore.os.open(target.getAbsolutePath(), O_RDONLY, 0);
478             return new ParcelFileDescriptor(targetFd);
479 
480         } catch (ErrnoException e) {
481             throw e.rethrowAsIOException();
482         }
483     }
484 
485     @Override
commit(IntentSender statusReceiver)486     public void commit(IntentSender statusReceiver) {
487         Preconditions.checkNotNull(statusReceiver);
488 
489         final boolean wasSealed;
490         synchronized (mLock) {
491             wasSealed = mSealed;
492             if (!mSealed) {
493                 // Verify that all writers are hands-off
494                 for (FileBridge bridge : mBridges) {
495                     if (!bridge.isClosed()) {
496                         throw new SecurityException("Files still open");
497                     }
498                 }
499                 mSealed = true;
500             }
501 
502             // Client staging is fully done at this point
503             mClientProgress = 1f;
504             computeProgressLocked(true);
505         }
506 
507         if (!wasSealed) {
508             // Persist the fact that we've sealed ourselves to prevent
509             // mutations of any hard links we create. We do this without holding
510             // the session lock, since otherwise it's a lock inversion.
511             mCallback.onSessionSealedBlocking(this);
512         }
513 
514         // This ongoing commit should keep session active, even though client
515         // will probably close their end.
516         mActiveCount.incrementAndGet();
517 
518         final PackageInstallObserverAdapter adapter = new PackageInstallObserverAdapter(mContext,
519                 statusReceiver, sessionId, mIsInstallerDeviceOwner, userId);
520         mHandler.obtainMessage(MSG_COMMIT, adapter.getBinder()).sendToTarget();
521     }
522 
commitLocked()523     private void commitLocked() throws PackageManagerException {
524         if (mDestroyed) {
525             throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, "Session destroyed");
526         }
527         if (!mSealed) {
528             throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, "Session not sealed");
529         }
530 
531         try {
532             resolveStageDir();
533         } catch (IOException e) {
534             throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
535                     "Failed to resolve stage location", e);
536         }
537 
538         // Verify that stage looks sane with respect to existing application.
539         // This currently only ensures packageName, versionCode, and certificate
540         // consistency.
541         validateInstallLocked();
542 
543         Preconditions.checkNotNull(mPackageName);
544         Preconditions.checkNotNull(mSignatures);
545         Preconditions.checkNotNull(mResolvedBaseFile);
546 
547         if (!mPermissionsAccepted) {
548             // User needs to accept permissions; give installer an intent they
549             // can use to involve user.
550             final Intent intent = new Intent(PackageInstaller.ACTION_CONFIRM_PERMISSIONS);
551             intent.setPackage(mContext.getPackageManager().getPermissionControllerPackageName());
552             intent.putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId);
553             try {
554                 mRemoteObserver.onUserActionRequired(intent);
555             } catch (RemoteException ignored) {
556             }
557 
558             // Commit was keeping session marked as active until now; release
559             // that extra refcount so session appears idle.
560             close();
561             return;
562         }
563 
564         if (stageCid != null) {
565             // Figure out the final installed size and resize the container once
566             // and for all. Internally the parser handles straddling between two
567             // locations when inheriting.
568             final long finalSize = calculateInstalledSize();
569             resizeContainer(stageCid, finalSize);
570         }
571 
572         // Inherit any packages and native libraries from existing install that
573         // haven't been overridden.
574         if (params.mode == SessionParams.MODE_INHERIT_EXISTING) {
575             try {
576                 final List<File> fromFiles = mResolvedInheritedFiles;
577                 final File toDir = resolveStageDir();
578 
579                 if (LOGD) Slog.d(TAG, "Inherited files: " + mResolvedInheritedFiles);
580                 if (!mResolvedInheritedFiles.isEmpty() && mInheritedFilesBase == null) {
581                     throw new IllegalStateException("mInheritedFilesBase == null");
582                 }
583 
584                 if (isLinkPossible(fromFiles, toDir)) {
585                     if (!mResolvedInstructionSets.isEmpty()) {
586                         final File oatDir = new File(toDir, "oat");
587                         createOatDirs(mResolvedInstructionSets, oatDir);
588                     }
589                     linkFiles(fromFiles, toDir, mInheritedFilesBase);
590                 } else {
591                     // TODO: this should delegate to DCS so the system process
592                     // avoids holding open FDs into containers.
593                     copyFiles(fromFiles, toDir);
594                 }
595             } catch (IOException e) {
596                 throw new PackageManagerException(INSTALL_FAILED_INSUFFICIENT_STORAGE,
597                         "Failed to inherit existing install", e);
598             }
599         }
600 
601         // TODO: surface more granular state from dexopt
602         mInternalProgress = 0.5f;
603         computeProgressLocked(true);
604 
605         // Unpack native libraries
606         extractNativeLibraries(mResolvedStageDir, params.abiOverride);
607 
608         // Container is ready to go, let's seal it up!
609         if (stageCid != null) {
610             finalizeAndFixContainer(stageCid);
611         }
612 
613         // We've reached point of no return; call into PMS to install the stage.
614         // Regardless of success or failure we always destroy session.
615         final IPackageInstallObserver2 localObserver = new IPackageInstallObserver2.Stub() {
616             @Override
617             public void onUserActionRequired(Intent intent) {
618                 throw new IllegalStateException();
619             }
620 
621             @Override
622             public void onPackageInstalled(String basePackageName, int returnCode, String msg,
623                     Bundle extras) {
624                 destroyInternal();
625                 dispatchSessionFinished(returnCode, msg, extras);
626             }
627         };
628 
629         final UserHandle user;
630         if ((params.installFlags & PackageManager.INSTALL_ALL_USERS) != 0) {
631             user = UserHandle.ALL;
632         } else {
633             user = new UserHandle(userId);
634         }
635 
636         mRelinquished = true;
637         mPm.installStage(mPackageName, stageDir, stageCid, localObserver, params,
638                 installerPackageName, installerUid, user, mCertificates);
639     }
640 
641     /**
642      * Validate install by confirming that all application packages are have
643      * consistent package name, version code, and signing certificates.
644      * <p>
645      * Clears and populates {@link #mResolvedBaseFile},
646      * {@link #mResolvedStagedFiles}, and {@link #mResolvedInheritedFiles}.
647      * <p>
648      * Renames package files in stage to match split names defined inside.
649      * <p>
650      * Note that upgrade compatibility is still performed by
651      * {@link PackageManagerService}.
652      */
validateInstallLocked()653     private void validateInstallLocked() throws PackageManagerException {
654         mPackageName = null;
655         mVersionCode = -1;
656         mSignatures = null;
657 
658         mResolvedBaseFile = null;
659         mResolvedStagedFiles.clear();
660         mResolvedInheritedFiles.clear();
661 
662         final File[] removedFiles = mResolvedStageDir.listFiles(sRemovedFilter);
663         final List<String> removeSplitList = new ArrayList<>();
664         if (!ArrayUtils.isEmpty(removedFiles)) {
665             for (File removedFile : removedFiles) {
666                 final String fileName = removedFile.getName();
667                 final String splitName = fileName.substring(
668                         0, fileName.length() - REMOVE_SPLIT_MARKER_EXTENSION.length());
669                 removeSplitList.add(splitName);
670             }
671         }
672 
673         final File[] addedFiles = mResolvedStageDir.listFiles(sAddedFilter);
674         if (ArrayUtils.isEmpty(addedFiles) && removeSplitList.size() == 0) {
675             throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, "No packages staged");
676         }
677         // Verify that all staged packages are internally consistent
678         final ArraySet<String> stagedSplits = new ArraySet<>();
679         for (File addedFile : addedFiles) {
680             final ApkLite apk;
681             try {
682                 apk = PackageParser.parseApkLite(
683                         addedFile, PackageParser.PARSE_COLLECT_CERTIFICATES);
684             } catch (PackageParserException e) {
685                 throw PackageManagerException.from(e);
686             }
687 
688             if (!stagedSplits.add(apk.splitName)) {
689                 throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
690                         "Split " + apk.splitName + " was defined multiple times");
691             }
692 
693             // Use first package to define unknown values
694             if (mPackageName == null) {
695                 mPackageName = apk.packageName;
696                 mVersionCode = apk.versionCode;
697             }
698             if (mSignatures == null) {
699                 mSignatures = apk.signatures;
700                 mCertificates = apk.certificates;
701             }
702 
703             assertApkConsistent(String.valueOf(addedFile), apk);
704 
705             // Take this opportunity to enforce uniform naming
706             final String targetName;
707             if (apk.splitName == null) {
708                 targetName = "base.apk";
709             } else {
710                 targetName = "split_" + apk.splitName + ".apk";
711             }
712             if (!FileUtils.isValidExtFilename(targetName)) {
713                 throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
714                         "Invalid filename: " + targetName);
715             }
716 
717             final File targetFile = new File(mResolvedStageDir, targetName);
718             if (!addedFile.equals(targetFile)) {
719                 addedFile.renameTo(targetFile);
720             }
721 
722             // Base is coming from session
723             if (apk.splitName == null) {
724                 mResolvedBaseFile = targetFile;
725             }
726 
727             mResolvedStagedFiles.add(targetFile);
728         }
729 
730         if (removeSplitList.size() > 0) {
731             // validate split names marked for removal
732             final int flags = mSignatures == null ? PackageManager.GET_SIGNATURES : 0;
733             final PackageInfo pkg = mPm.getPackageInfo(params.appPackageName, flags, userId);
734             for (String splitName : removeSplitList) {
735                 if (!ArrayUtils.contains(pkg.splitNames, splitName)) {
736                     throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
737                             "Split not found: " + splitName);
738                 }
739             }
740 
741             // ensure we've got appropriate package name, version code and signatures
742             if (mPackageName == null) {
743                 mPackageName = pkg.packageName;
744                 mVersionCode = pkg.versionCode;
745             }
746             if (mSignatures == null) {
747                 mSignatures = pkg.signatures;
748             }
749         }
750 
751         if (params.mode == SessionParams.MODE_FULL_INSTALL) {
752             // Full installs must include a base package
753             if (!stagedSplits.contains(null)) {
754                 throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
755                         "Full install must include a base package");
756             }
757 
758         } else {
759             // Partial installs must be consistent with existing install
760             final ApplicationInfo app = mPm.getApplicationInfo(mPackageName, 0, userId);
761             if (app == null) {
762                 throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
763                         "Missing existing base package for " + mPackageName);
764             }
765 
766             final PackageLite existing;
767             final ApkLite existingBase;
768             try {
769                 existing = PackageParser.parsePackageLite(new File(app.getCodePath()), 0);
770                 existingBase = PackageParser.parseApkLite(new File(app.getBaseCodePath()),
771                         PackageParser.PARSE_COLLECT_CERTIFICATES);
772             } catch (PackageParserException e) {
773                 throw PackageManagerException.from(e);
774             }
775 
776             assertApkConsistent("Existing base", existingBase);
777 
778             // Inherit base if not overridden
779             if (mResolvedBaseFile == null) {
780                 mResolvedBaseFile = new File(app.getBaseCodePath());
781                 mResolvedInheritedFiles.add(mResolvedBaseFile);
782             }
783 
784             // Inherit splits if not overridden
785             if (!ArrayUtils.isEmpty(existing.splitNames)) {
786                 for (int i = 0; i < existing.splitNames.length; i++) {
787                     final String splitName = existing.splitNames[i];
788                     final File splitFile = new File(existing.splitCodePaths[i]);
789                     final boolean splitRemoved = removeSplitList.contains(splitName);
790                     if (!stagedSplits.contains(splitName) && !splitRemoved) {
791                         mResolvedInheritedFiles.add(splitFile);
792                     }
793                 }
794             }
795 
796             // Inherit compiled oat directory.
797             final File packageInstallDir = (new File(app.getBaseCodePath())).getParentFile();
798             mInheritedFilesBase = packageInstallDir;
799             final File oatDir = new File(packageInstallDir, "oat");
800             if (oatDir.exists()) {
801                 final File[] archSubdirs = oatDir.listFiles();
802 
803                 // Keep track of all instruction sets we've seen compiled output for.
804                 // If we're linking (and not copying) inherited files, we can recreate the
805                 // instruction set hierarchy and link compiled output.
806                 if (archSubdirs != null && archSubdirs.length > 0) {
807                     final String[] instructionSets = InstructionSets.getAllDexCodeInstructionSets();
808                     for (File archSubDir : archSubdirs) {
809                         // Skip any directory that isn't an ISA subdir.
810                         if (!ArrayUtils.contains(instructionSets, archSubDir.getName())) {
811                             continue;
812                         }
813 
814                         mResolvedInstructionSets.add(archSubDir.getName());
815                         List<File> oatFiles = Arrays.asList(archSubDir.listFiles());
816                         if (!oatFiles.isEmpty()) {
817                             mResolvedInheritedFiles.addAll(oatFiles);
818                         }
819                     }
820                 }
821             }
822         }
823     }
824 
assertApkConsistent(String tag, ApkLite apk)825     private void assertApkConsistent(String tag, ApkLite apk) throws PackageManagerException {
826         if (!mPackageName.equals(apk.packageName)) {
827             throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, tag + " package "
828                     + apk.packageName + " inconsistent with " + mPackageName);
829         }
830         if (params.appPackageName != null && !params.appPackageName.equals(apk.packageName)) {
831             throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, tag
832                     + " specified package " + params.appPackageName
833                     + " inconsistent with " + apk.packageName);
834         }
835         if (mVersionCode != apk.versionCode) {
836             throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, tag
837                     + " version code " + apk.versionCode + " inconsistent with "
838                     + mVersionCode);
839         }
840         if (!Signature.areExactMatch(mSignatures, apk.signatures)) {
841             throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
842                     tag + " signatures are inconsistent");
843         }
844     }
845 
846     /**
847      * Calculate the final install footprint size, combining both staged and
848      * existing APKs together and including unpacked native code from both.
849      */
calculateInstalledSize()850     private long calculateInstalledSize() throws PackageManagerException {
851         Preconditions.checkNotNull(mResolvedBaseFile);
852 
853         final ApkLite baseApk;
854         try {
855             baseApk = PackageParser.parseApkLite(mResolvedBaseFile, 0);
856         } catch (PackageParserException e) {
857             throw PackageManagerException.from(e);
858         }
859 
860         final List<String> splitPaths = new ArrayList<>();
861         for (File file : mResolvedStagedFiles) {
862             if (mResolvedBaseFile.equals(file)) continue;
863             splitPaths.add(file.getAbsolutePath());
864         }
865         for (File file : mResolvedInheritedFiles) {
866             if (mResolvedBaseFile.equals(file)) continue;
867             splitPaths.add(file.getAbsolutePath());
868         }
869 
870         // This is kind of hacky; we're creating a half-parsed package that is
871         // straddled between the inherited and staged APKs.
872         final PackageLite pkg = new PackageLite(null, baseApk, null,
873                 splitPaths.toArray(new String[splitPaths.size()]), null);
874         final boolean isForwardLocked =
875                 (params.installFlags & PackageManager.INSTALL_FORWARD_LOCK) != 0;
876 
877         try {
878             return PackageHelper.calculateInstalledSize(pkg, isForwardLocked, params.abiOverride);
879         } catch (IOException e) {
880             throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
881                     "Failed to calculate install size", e);
882         }
883     }
884 
885     /**
886      * Determine if creating hard links between source and destination is
887      * possible. That is, do they all live on the same underlying device.
888      */
isLinkPossible(List<File> fromFiles, File toDir)889     private boolean isLinkPossible(List<File> fromFiles, File toDir) {
890         try {
891             final StructStat toStat = Os.stat(toDir.getAbsolutePath());
892             for (File fromFile : fromFiles) {
893                 final StructStat fromStat = Os.stat(fromFile.getAbsolutePath());
894                 if (fromStat.st_dev != toStat.st_dev) {
895                     return false;
896                 }
897             }
898         } catch (ErrnoException e) {
899             Slog.w(TAG, "Failed to detect if linking possible: " + e);
900             return false;
901         }
902         return true;
903     }
904 
getRelativePath(File file, File base)905     private static String getRelativePath(File file, File base) throws IOException {
906         final String pathStr = file.getAbsolutePath();
907         final String baseStr = base.getAbsolutePath();
908         // Don't allow relative paths.
909         if (pathStr.contains("/.") ) {
910             throw new IOException("Invalid path (was relative) : " + pathStr);
911         }
912 
913         if (pathStr.startsWith(baseStr)) {
914             return pathStr.substring(baseStr.length());
915         }
916 
917         throw new IOException("File: " + pathStr + " outside base: " + baseStr);
918     }
919 
createOatDirs(List<String> instructionSets, File fromDir)920     private void createOatDirs(List<String> instructionSets, File fromDir)
921             throws PackageManagerException {
922         for (String instructionSet : instructionSets) {
923             try {
924                 mPm.mInstaller.createOatDir(fromDir.getAbsolutePath(), instructionSet);
925             } catch (InstallerException e) {
926                 throw PackageManagerException.from(e);
927             }
928         }
929     }
930 
linkFiles(List<File> fromFiles, File toDir, File fromDir)931     private void linkFiles(List<File> fromFiles, File toDir, File fromDir)
932             throws IOException {
933         for (File fromFile : fromFiles) {
934             final String relativePath = getRelativePath(fromFile, fromDir);
935             try {
936                 mPm.mInstaller.linkFile(relativePath, fromDir.getAbsolutePath(),
937                         toDir.getAbsolutePath());
938             } catch (InstallerException e) {
939                 throw new IOException("failed linkOrCreateDir(" + relativePath + ", "
940                         + fromDir + ", " + toDir + ")", e);
941             }
942         }
943 
944         Slog.d(TAG, "Linked " + fromFiles.size() + " files into " + toDir);
945     }
946 
copyFiles(List<File> fromFiles, File toDir)947     private static void copyFiles(List<File> fromFiles, File toDir) throws IOException {
948         // Remove any partial files from previous attempt
949         for (File file : toDir.listFiles()) {
950             if (file.getName().endsWith(".tmp")) {
951                 file.delete();
952             }
953         }
954 
955         for (File fromFile : fromFiles) {
956             final File tmpFile = File.createTempFile("inherit", ".tmp", toDir);
957             if (LOGD) Slog.d(TAG, "Copying " + fromFile + " to " + tmpFile);
958             if (!FileUtils.copyFile(fromFile, tmpFile)) {
959                 throw new IOException("Failed to copy " + fromFile + " to " + tmpFile);
960             }
961             try {
962                 Os.chmod(tmpFile.getAbsolutePath(), 0644);
963             } catch (ErrnoException e) {
964                 throw new IOException("Failed to chmod " + tmpFile);
965             }
966             final File toFile = new File(toDir, fromFile.getName());
967             if (LOGD) Slog.d(TAG, "Renaming " + tmpFile + " to " + toFile);
968             if (!tmpFile.renameTo(toFile)) {
969                 throw new IOException("Failed to rename " + tmpFile + " to " + toFile);
970             }
971         }
972         Slog.d(TAG, "Copied " + fromFiles.size() + " files into " + toDir);
973     }
974 
extractNativeLibraries(File packageDir, String abiOverride)975     private static void extractNativeLibraries(File packageDir, String abiOverride)
976             throws PackageManagerException {
977         // Always start from a clean slate
978         final File libDir = new File(packageDir, NativeLibraryHelper.LIB_DIR_NAME);
979         NativeLibraryHelper.removeNativeBinariesFromDirLI(libDir, true);
980 
981         NativeLibraryHelper.Handle handle = null;
982         try {
983             handle = NativeLibraryHelper.Handle.create(packageDir);
984             final int res = NativeLibraryHelper.copyNativeBinariesWithOverride(handle, libDir,
985                     abiOverride);
986             if (res != PackageManager.INSTALL_SUCCEEDED) {
987                 throw new PackageManagerException(res,
988                         "Failed to extract native libraries, res=" + res);
989             }
990         } catch (IOException e) {
991             throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
992                     "Failed to extract native libraries", e);
993         } finally {
994             IoUtils.closeQuietly(handle);
995         }
996     }
997 
resizeContainer(String cid, long targetSize)998     private static void resizeContainer(String cid, long targetSize)
999             throws PackageManagerException {
1000         String path = PackageHelper.getSdDir(cid);
1001         if (path == null) {
1002             throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
1003                     "Failed to find mounted " + cid);
1004         }
1005 
1006         final long currentSize = new File(path).getTotalSpace();
1007         if (currentSize > targetSize) {
1008             Slog.w(TAG, "Current size " + currentSize + " is larger than target size "
1009                     + targetSize + "; skipping resize");
1010             return;
1011         }
1012 
1013         if (!PackageHelper.unMountSdDir(cid)) {
1014             throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
1015                     "Failed to unmount " + cid + " before resize");
1016         }
1017 
1018         if (!PackageHelper.resizeSdDir(targetSize, cid,
1019                 PackageManagerService.getEncryptKey())) {
1020             throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
1021                     "Failed to resize " + cid + " to " + targetSize + " bytes");
1022         }
1023 
1024         path = PackageHelper.mountSdDir(cid, PackageManagerService.getEncryptKey(),
1025                 Process.SYSTEM_UID, false);
1026         if (path == null) {
1027             throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
1028                     "Failed to mount " + cid + " after resize");
1029         }
1030     }
1031 
finalizeAndFixContainer(String cid)1032     private void finalizeAndFixContainer(String cid) throws PackageManagerException {
1033         if (!PackageHelper.finalizeSdDir(cid)) {
1034             throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
1035                     "Failed to finalize container " + cid);
1036         }
1037 
1038         final int uid = mPm.getPackageUid(PackageManagerService.DEFAULT_CONTAINER_PACKAGE,
1039                 PackageManager.MATCH_SYSTEM_ONLY, UserHandle.USER_SYSTEM);
1040         final int gid = UserHandle.getSharedAppGid(uid);
1041         if (!PackageHelper.fixSdPermissions(cid, gid, null)) {
1042             throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
1043                     "Failed to fix permissions on container " + cid);
1044         }
1045     }
1046 
setPermissionsResult(boolean accepted)1047     void setPermissionsResult(boolean accepted) {
1048         if (!mSealed) {
1049             throw new SecurityException("Must be sealed to accept permissions");
1050         }
1051 
1052         if (accepted) {
1053             // Mark and kick off another install pass
1054             synchronized (mLock) {
1055                 mPermissionsAccepted = true;
1056             }
1057             mHandler.obtainMessage(MSG_COMMIT).sendToTarget();
1058         } else {
1059             destroyInternal();
1060             dispatchSessionFinished(INSTALL_FAILED_ABORTED, "User rejected permissions", null);
1061         }
1062     }
1063 
open()1064     public void open() throws IOException {
1065         if (mActiveCount.getAndIncrement() == 0) {
1066             mCallback.onSessionActiveChanged(this, true);
1067         }
1068 
1069         synchronized (mLock) {
1070             if (!mPrepared) {
1071                 if (stageDir != null) {
1072                     prepareStageDir(stageDir);
1073                 } else if (stageCid != null) {
1074                     prepareExternalStageCid(stageCid, params.sizeBytes);
1075 
1076                     // TODO: deliver more granular progress for ASEC allocation
1077                     mInternalProgress = 0.25f;
1078                     computeProgressLocked(true);
1079                 } else {
1080                     throw new IllegalArgumentException(
1081                             "Exactly one of stageDir or stageCid stage must be set");
1082                 }
1083 
1084                 mPrepared = true;
1085                 mCallback.onSessionPrepared(this);
1086             }
1087         }
1088     }
1089 
1090     @Override
close()1091     public void close() {
1092         if (mActiveCount.decrementAndGet() == 0) {
1093             mCallback.onSessionActiveChanged(this, false);
1094         }
1095     }
1096 
1097     @Override
abandon()1098     public void abandon() {
1099         if (mRelinquished) {
1100             Slog.d(TAG, "Ignoring abandon after commit relinquished control");
1101             return;
1102         }
1103         destroyInternal();
1104         dispatchSessionFinished(INSTALL_FAILED_ABORTED, "Session was abandoned", null);
1105     }
1106 
dispatchSessionFinished(int returnCode, String msg, Bundle extras)1107     private void dispatchSessionFinished(int returnCode, String msg, Bundle extras) {
1108         mFinalStatus = returnCode;
1109         mFinalMessage = msg;
1110 
1111         if (mRemoteObserver != null) {
1112             try {
1113                 mRemoteObserver.onPackageInstalled(mPackageName, returnCode, msg, extras);
1114             } catch (RemoteException ignored) {
1115             }
1116         }
1117 
1118         final boolean success = (returnCode == PackageManager.INSTALL_SUCCEEDED);
1119         mCallback.onSessionFinished(this, success);
1120     }
1121 
destroyInternal()1122     private void destroyInternal() {
1123         synchronized (mLock) {
1124             mSealed = true;
1125             mDestroyed = true;
1126 
1127             // Force shut down all bridges
1128             for (FileBridge bridge : mBridges) {
1129                 bridge.forceClose();
1130             }
1131         }
1132         if (stageDir != null) {
1133             try {
1134                 mPm.mInstaller.rmPackageDir(stageDir.getAbsolutePath());
1135             } catch (InstallerException ignored) {
1136             }
1137         }
1138         if (stageCid != null) {
1139             PackageHelper.destroySdDir(stageCid);
1140         }
1141     }
1142 
dump(IndentingPrintWriter pw)1143     void dump(IndentingPrintWriter pw) {
1144         synchronized (mLock) {
1145             dumpLocked(pw);
1146         }
1147     }
1148 
dumpLocked(IndentingPrintWriter pw)1149     private void dumpLocked(IndentingPrintWriter pw) {
1150         pw.println("Session " + sessionId + ":");
1151         pw.increaseIndent();
1152 
1153         pw.printPair("userId", userId);
1154         pw.printPair("installerPackageName", installerPackageName);
1155         pw.printPair("installerUid", installerUid);
1156         pw.printPair("createdMillis", createdMillis);
1157         pw.printPair("stageDir", stageDir);
1158         pw.printPair("stageCid", stageCid);
1159         pw.println();
1160 
1161         params.dump(pw);
1162 
1163         pw.printPair("mClientProgress", mClientProgress);
1164         pw.printPair("mProgress", mProgress);
1165         pw.printPair("mSealed", mSealed);
1166         pw.printPair("mPermissionsAccepted", mPermissionsAccepted);
1167         pw.printPair("mRelinquished", mRelinquished);
1168         pw.printPair("mDestroyed", mDestroyed);
1169         pw.printPair("mBridges", mBridges.size());
1170         pw.printPair("mFinalStatus", mFinalStatus);
1171         pw.printPair("mFinalMessage", mFinalMessage);
1172         pw.println();
1173 
1174         pw.decreaseIndent();
1175     }
1176 }
1177