diff --git a/api/current.txt b/api/current.txt index dc926045642ff..3c9cd8e890bdd 100644 --- a/api/current.txt +++ b/api/current.txt @@ -10506,6 +10506,7 @@ package android.content.pm { method public java.io.OutputStream openWrite(java.lang.String, long, long) throws java.io.IOException; method public void removeSplit(java.lang.String) throws java.io.IOException; method public void setStagingProgress(float); + method public void transfer(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException; } public static abstract class PackageInstaller.SessionCallback { @@ -10523,10 +10524,16 @@ package android.content.pm { method public android.graphics.Bitmap getAppIcon(); method public java.lang.CharSequence getAppLabel(); method public java.lang.String getAppPackageName(); + method public int getInstallLocation(); method public int getInstallReason(); method public java.lang.String getInstallerPackageName(); + method public int getMode(); + method public int getOriginatingUid(); + method public android.net.Uri getOriginatingUri(); method public float getProgress(); + method public android.net.Uri getReferrerUri(); method public int getSessionId(); + method public long getSize(); method public boolean isActive(); method public boolean isSealed(); method public void writeToParcel(android.os.Parcel, int); diff --git a/api/system-current.txt b/api/system-current.txt index da9f9eb911fe0..6c1983dc5e0d8 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -11180,12 +11180,14 @@ package android.content.pm { method public void abandon(); method public void close(); method public void commit(android.content.IntentSender); + method public void commitTransferred(android.content.IntentSender); method public void fsync(java.io.OutputStream) throws java.io.IOException; method public java.lang.String[] getNames() throws java.io.IOException; method public java.io.InputStream openRead(java.lang.String) throws java.io.IOException; method public java.io.OutputStream openWrite(java.lang.String, long, long) throws java.io.IOException; method public void removeSplit(java.lang.String) throws java.io.IOException; method public void setStagingProgress(float); + method public void transfer(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException; } public static abstract class PackageInstaller.SessionCallback { @@ -11200,13 +11202,26 @@ package android.content.pm { public static class PackageInstaller.SessionInfo implements android.os.Parcelable { method public android.content.Intent createDetailsIntent(); method public int describeContents(); + method public boolean getAllocateAggressive(); + method public boolean getAllowDowngrade(); method public android.graphics.Bitmap getAppIcon(); method public java.lang.CharSequence getAppLabel(); method public java.lang.String getAppPackageName(); + method public boolean getDontKillApp(); + method public java.lang.String[] getGrantedRuntimePermissions(); + method public boolean getInstallAsFullApp(boolean); + method public boolean getInstallAsInstantApp(boolean); + method public boolean getInstallAsVirtualPreload(); + method public int getInstallLocation(); method public int getInstallReason(); method public java.lang.String getInstallerPackageName(); + method public int getMode(); + method public int getOriginatingUid(); + method public android.net.Uri getOriginatingUri(); method public float getProgress(); + method public android.net.Uri getReferrerUri(); method public int getSessionId(); + method public long getSize(); method public boolean isActive(); method public boolean isSealed(); method public void writeToParcel(android.os.Parcel, int); diff --git a/api/test-current.txt b/api/test-current.txt index 3e274701ebca7..839964f5fe42a 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -10544,6 +10544,7 @@ package android.content.pm { method public java.io.OutputStream openWrite(java.lang.String, long, long) throws java.io.IOException; method public void removeSplit(java.lang.String) throws java.io.IOException; method public void setStagingProgress(float); + method public void transfer(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException; } public static abstract class PackageInstaller.SessionCallback { @@ -10561,10 +10562,16 @@ package android.content.pm { method public android.graphics.Bitmap getAppIcon(); method public java.lang.CharSequence getAppLabel(); method public java.lang.String getAppPackageName(); + method public int getInstallLocation(); method public int getInstallReason(); method public java.lang.String getInstallerPackageName(); + method public int getMode(); + method public int getOriginatingUid(); + method public android.net.Uri getOriginatingUri(); method public float getProgress(); + method public android.net.Uri getReferrerUri(); method public int getSessionId(); + method public long getSize(); method public boolean isActive(); method public boolean isSealed(); method public void writeToParcel(android.os.Parcel, int); diff --git a/core/java/android/content/pm/IPackageInstallerSession.aidl b/core/java/android/content/pm/IPackageInstallerSession.aidl index 2a3fac341e24a..0b16852246f8e 100644 --- a/core/java/android/content/pm/IPackageInstallerSession.aidl +++ b/core/java/android/content/pm/IPackageInstallerSession.aidl @@ -32,6 +32,7 @@ interface IPackageInstallerSession { void removeSplit(String splitName); void close(); - void commit(in IntentSender statusReceiver); + void commit(in IntentSender statusReceiver, boolean forTransferred); + void transfer(in String packageName); void abandon(); } diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index c3ebf554ea8cd..f4fdcaa44836d 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -38,6 +38,7 @@ import android.os.Message; import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.os.Parcelable; +import android.os.ParcelableException; import android.os.RemoteException; import android.os.SystemProperties; import android.system.ErrnoException; @@ -793,7 +794,7 @@ public class PackageInstaller { * @throws IOException if trouble opening the file for writing, such as * lack of disk space or unavailable media. * @throws SecurityException if called after the session has been - * committed or abandoned. + * sealed or abandoned */ public @NonNull OutputStream openWrite(@NonNull String name, long offsetBytes, long lengthBytes) throws IOException { @@ -918,7 +919,68 @@ public class PackageInstaller { */ public void commit(@NonNull IntentSender statusReceiver) { try { - mSession.commit(statusReceiver); + mSession.commit(statusReceiver, false); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Attempt to commit a session that has been {@link #transfer(String) transferred}. + * + *
If the device reboots before the session has been finalized, you may commit the + * session again. + * + *
The caller of this method is responsible to ensure the safety of the session. As the + * session was created by another - usually less trusted - app, it is paramount that before + * committing all public and system {@link SessionInfo properties of the session} + * and all {@link #openRead(String) APKs} are verified by the caller. It might happen + * that new properties are added to the session with a new API revision. In this case the + * callers need to be updated. + * + * @param statusReceiver Callbacks called when the state of the session changes. + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) + public void commitTransferred(@NonNull IntentSender statusReceiver) { + try { + mSession.commit(statusReceiver, true); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Transfer the session to a new owner. + *
+ * Only sessions that update the installing app can be transferred. + *
+ * After the transfer to a package with a different uid all method calls on the session + * will cause {@link SecurityException}s. + *
+ * Once this method is called, the session is sealed and no additional mutations beside + * committing it may be performed on the session. + * + * @param packageName The package of the new owner. Needs to hold the INSTALL_PACKAGES + * permission. + * + * @throws PackageManager.NameNotFoundException if the new owner could not be found. + * @throws SecurityException if called after the session has been committed or abandoned. + * @throws SecurityException if the session does not update the original installer + * @throws SecurityException if streams opened through + * {@link #openWrite(String, long, long) are still open. + */ + public void transfer(@NonNull String packageName) + throws PackageManager.NameNotFoundException { + Preconditions.checkNotNull(packageName); + + try { + mSession.transfer(packageName); + } catch (ParcelableException e) { + e.maybeRethrow(PackageManager.NameNotFoundException.class); + throw new RuntimeException(e); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -1040,6 +1102,26 @@ public class PackageInstaller { grantedRuntimePermissions = source.readStringArray(); } + /** + * Check if there are hidden options set. + * + *
Hidden options are those options that cannot be verified via public or system-api + * methods on {@link SessionInfo}. + * + * @return {@code true} if any hidden option is set. + * + * @hide + */ + public boolean areHiddenOptionsSet() { + return (installFlags & (PackageManager.INSTALL_ALLOW_DOWNGRADE + | PackageManager.INSTALL_DONT_KILL_APP + | PackageManager.INSTALL_INSTANT_APP + | PackageManager.INSTALL_FULL_APP + | PackageManager.INSTALL_VIRTUAL_PRELOAD + | PackageManager.INSTALL_ALLOCATE_AGGRESSIVE)) != installFlags + || abiOverride != null || volumeUuid != null; + } + /** * Provide value of {@link PackageInfo#installLocation}, which may be used * to determine where the app will be staged. Defaults to @@ -1299,6 +1381,19 @@ public class PackageInstaller { /** {@hide} */ public CharSequence appLabel; + /** {@hide} */ + public int installLocation; + /** {@hide} */ + public Uri originatingUri; + /** {@hide} */ + public int originatingUid; + /** {@hide} */ + public Uri referrerUri; + /** {@hide} */ + public String[] grantedRuntimePermissions; + /** {@hide} */ + public int installFlags; + /** {@hide} */ public SessionInfo() { } @@ -1318,6 +1413,13 @@ public class PackageInstaller { appPackageName = source.readString(); appIcon = source.readParcelable(null); appLabel = source.readString(); + + installLocation = source.readInt(); + originatingUri = source.readParcelable(null); + originatingUid = source.readInt(); + referrerUri = source.readParcelable(null); + grantedRuntimePermissions = source.readStringArray(); + installFlags = source.readInt(); } /** @@ -1441,6 +1543,130 @@ public class PackageInstaller { return intent; } + /** + * Get the mode of the session as set in the constructor of the {@link SessionParams}. + * + * @return One of {@link SessionParams#MODE_FULL_INSTALL} + * or {@link SessionParams#MODE_INHERIT_EXISTING} + */ + public int getMode() { + return mode; + } + + /** + * Get the value set in {@link SessionParams#setInstallLocation(int)}. + */ + public int getInstallLocation() { + return installLocation; + } + + /** + * Get the value as set in {@link SessionParams#setSize(long)}. + * + *
The value is a hint and does not have to match the actual size.
+ */
+ public long getSize() {
+ return sizeBytes;
+ }
+
+ /**
+ * Get the value set in {@link SessionParams#setOriginatingUri(Uri)}.
+ */
+ public @Nullable Uri getOriginatingUri() {
+ return originatingUri;
+ }
+
+ /**
+ * Get the value set in {@link SessionParams#setOriginatingUid(int)}.
+ */
+ public int getOriginatingUid() {
+ return originatingUid;
+ }
+
+ /**
+ * Get the value set in {@link SessionParams#setReferrerUri(Uri)}
+ */
+ public @Nullable Uri getReferrerUri() {
+ return referrerUri;
+ }
+
+ /**
+ * Get the value set in {@link SessionParams#setGrantedRuntimePermissions(String[])}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public @Nullable String[] getGrantedRuntimePermissions() {
+ return grantedRuntimePermissions;
+ }
+
+ /**
+ * Get the value set in {@link SessionParams#setAllowDowngrade(boolean)}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public boolean getAllowDowngrade() {
+ return (installFlags & PackageManager.INSTALL_ALLOW_DOWNGRADE) != 0;
+ }
+
+ /**
+ * Get the value set in {@link SessionParams#setDontKillApp(boolean)}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public boolean getDontKillApp() {
+ return (installFlags & PackageManager.INSTALL_DONT_KILL_APP) != 0;
+ }
+
+ /**
+ * If {@link SessionParams#setInstallAsInstantApp(boolean)} was called with {@code true},
+ * return true. If it was called with {@code false} or if it was not called return false.
+ *
+ * @hide
+ *
+ * @see #getInstallAsFullApp
+ */
+ @SystemApi
+ public boolean getInstallAsInstantApp(boolean isInstantApp) {
+ return (installFlags & PackageManager.INSTALL_INSTANT_APP) != 0;
+ }
+
+ /**
+ * If {@link SessionParams#setInstallAsInstantApp(boolean)} was called with {@code false},
+ * return true. If it was called with {@code true} or if it was not called return false.
+ *
+ * @hide
+ *
+ * @see #getInstallAsInstantApp
+ */
+ @SystemApi
+ public boolean getInstallAsFullApp(boolean isInstantApp) {
+ return (installFlags & PackageManager.INSTALL_FULL_APP) != 0;
+ }
+
+ /**
+ * Get if {@link SessionParams#setInstallAsVirtualPreload()} was called.
+ *
+ * @hide
+ */
+ @SystemApi
+ public boolean getInstallAsVirtualPreload() {
+ return (installFlags & PackageManager.INSTALL_VIRTUAL_PRELOAD) != 0;
+ }
+
+ /**
+ * Get the value set in {@link SessionParams#setAllocateAggressive(boolean)}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public boolean getAllocateAggressive() {
+ return (installFlags & PackageManager.INSTALL_ALLOCATE_AGGRESSIVE) != 0;
+ }
+
+
/** {@hide} */
@Deprecated
public @Nullable Intent getDetailsIntent() {
@@ -1467,6 +1693,13 @@ public class PackageInstaller {
dest.writeString(appPackageName);
dest.writeParcelable(appIcon, flags);
dest.writeString(appLabel != null ? appLabel.toString() : null);
+
+ dest.writeInt(installLocation);
+ dest.writeParcelable(originatingUri, flags);
+ dest.writeInt(originatingUid);
+ dest.writeParcelable(referrerUri, flags);
+ dest.writeStringArray(grantedRuntimePermissions);
+ dest.writeInt(installFlags);
}
public static final Parcelable.Creator This is dependant on the identity of the installer, hence this cannot be cached if the
+ * installer might still {@link #transfer(String) change}.
+ *
+ * @return {@code true} iff we need to ask to confirm the permissions?
+ */
+ private boolean needToAskForPermissionsLocked() {
+ if (mPermissionsManuallyAccepted) {
+ return false;
+ }
+
+ final boolean isPermissionGranted =
+ (mPm.checkUidPermission(android.Manifest.permission.INSTALL_PACKAGES,
+ mInstallerUid) == PackageManager.PERMISSION_GRANTED);
+ final boolean isInstallerRoot = (mInstallerUid == Process.ROOT_UID);
+ final boolean forcePermissionPrompt =
+ (params.installFlags & PackageManager.INSTALL_FORCE_PERMISSION_PROMPT) != 0;
+
+ // Device owners are allowed to silently install packages, so the permission check is
+ // waived if the installer is the device owner.
+ return forcePermissionPrompt || !(isPermissionGranted || isInstallerRoot
+ || isInstallerDeviceOwnerLocked());
+ }
+
public PackageInstallerSession(PackageInstallerService.InternalCallback callback,
Context context, PackageManagerService pm, Looper looper, int sessionId, int userId,
String installerPackageName, int installerUid, SessionParams params, long createdMillis,
@@ -242,8 +339,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
this.sessionId = sessionId;
this.userId = userId;
- this.installerPackageName = installerPackageName;
- this.installerUid = installerUid;
+ mOriginalInstallerUid = installerUid;
+ mInstallerPackageName = installerPackageName;
+ mInstallerUid = installerUid;
this.params = params;
this.createdMillis = createdMillis;
this.stageDir = stageDir;
@@ -257,26 +355,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
mPrepared = prepared;
mSealed = sealed;
- // Device owners are allowed to silently install packages, so the permission check is
- // waived if the installer is the device owner.
- DevicePolicyManager dpm = (DevicePolicyManager) mContext.getSystemService(
- Context.DEVICE_POLICY_SERVICE);
- final boolean isPermissionGranted =
- (mPm.checkUidPermission(android.Manifest.permission.INSTALL_PACKAGES, installerUid)
- == PackageManager.PERMISSION_GRANTED);
- final boolean isInstallerRoot = (installerUid == Process.ROOT_UID);
- final boolean forcePermissionPrompt =
- (params.installFlags & PackageManager.INSTALL_FORCE_PERMISSION_PROMPT) != 0;
- mIsInstallerDeviceOwner = (dpm != null) && dpm.isDeviceOwnerAppOnCallingUser(
- installerPackageName);
- if ((isPermissionGranted
- || isInstallerRoot
- || mIsInstallerDeviceOwner)
- && !forcePermissionPrompt) {
- mPermissionsAccepted = true;
- } else {
- mPermissionsAccepted = false;
- }
final long identity = Binder.clearCallingIdentity();
try {
final int uid = mPm.getPackageUid(PackageManagerService.DEFAULT_CONTAINER_PACKAGE,
@@ -295,7 +373,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
final SessionInfo info = new SessionInfo();
synchronized (mLock) {
info.sessionId = sessionId;
- info.installerPackageName = installerPackageName;
+ info.installerPackageName = mInstallerPackageName;
info.resolvedBaseCodePath = (mResolvedBaseFile != null) ?
mResolvedBaseFile.getAbsolutePath() : null;
info.progress = mProgress;
@@ -310,6 +388,13 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
info.appIcon = params.appIcon;
}
info.appLabel = params.appLabel;
+
+ info.installLocation = params.installLocation;
+ info.originatingUri = params.originatingUri;
+ info.originatingUid = params.originatingUid;
+ info.referrerUri = params.referrerUri;
+ info.grantedRuntimePermissions = params.grantedRuntimePermissions;
+ info.installFlags = params.installFlags;
}
return info;
}
@@ -326,14 +411,26 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
}
- private void assertPreparedAndNotSealed(String cookie) {
- synchronized (mLock) {
- if (!mPrepared) {
- throw new IllegalStateException(cookie + " before prepared");
- }
- if (mSealed) {
- throw new SecurityException(cookie + " not allowed after commit");
- }
+ private void assertPreparedAndNotSealedLocked(String cookie) {
+ assertPreparedAndNotCommittedOrDestroyedLocked(cookie);
+ if (mSealed) {
+ throw new SecurityException(cookie + " not allowed after sealing");
+ }
+ }
+
+ private void assertPreparedAndNotCommittedOrDestroyedLocked(String cookie) {
+ assertPreparedAndNotDestroyedLocked(cookie);
+ if (mCommitted) {
+ throw new SecurityException(cookie + " not allowed after commit");
+ }
+ }
+
+ private void assertPreparedAndNotDestroyedLocked(String cookie) {
+ if (!mPrepared) {
+ throw new IllegalStateException(cookie + " before prepared");
+ }
+ if (mDestroyed) {
+ throw new SecurityException(cookie + " not allowed after destruction");
}
}
@@ -342,27 +439,27 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
* might point at an ASEC mount point, which is why we delay path resolution
* until someone actively works with the session.
*/
- private File resolveStageDir() throws IOException {
- synchronized (mLock) {
- if (mResolvedStageDir == null) {
- if (stageDir != null) {
- mResolvedStageDir = stageDir;
+ private File resolveStageDirLocked() throws IOException {
+ if (mResolvedStageDir == null) {
+ if (stageDir != null) {
+ mResolvedStageDir = stageDir;
+ } else {
+ final String path = PackageHelper.getSdDir(stageCid);
+ if (path != null) {
+ mResolvedStageDir = new File(path);
} else {
- final String path = PackageHelper.getSdDir(stageCid);
- if (path != null) {
- mResolvedStageDir = new File(path);
- } else {
- throw new IOException("Failed to resolve path to container " + stageCid);
- }
+ throw new IOException("Failed to resolve path to container " + stageCid);
}
}
- return mResolvedStageDir;
}
+ return mResolvedStageDir;
}
@Override
public void setClientProgress(float progress) {
synchronized (mLock) {
+ assertCallerIsOwnerOrRootLocked();
+
// Always publish first staging movement
final boolean forcePublish = (mClientProgress == 0);
mClientProgress = progress;
@@ -373,6 +470,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
@Override
public void addClientProgress(float progress) {
synchronized (mLock) {
+ assertCallerIsOwnerOrRootLocked();
+
setClientProgress(mClientProgress + progress);
}
}
@@ -390,11 +489,15 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
@Override
public String[] getNames() {
- assertPreparedAndNotSealed("getNames");
- try {
- return resolveStageDir().list();
- } catch (IOException e) {
- throw ExceptionUtils.wrap(e);
+ synchronized (mLock) {
+ assertCallerIsOwnerOrRootLocked();
+ assertPreparedAndNotCommittedOrDestroyedLocked("getNames");
+
+ try {
+ return resolveStageDirLocked().list();
+ } catch (IOException e) {
+ throw ExceptionUtils.wrap(e);
+ }
}
}
@@ -403,20 +506,26 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
if (TextUtils.isEmpty(params.appPackageName)) {
throw new IllegalStateException("Must specify package name to remove a split");
}
- try {
- createRemoveSplitMarker(splitName);
- } catch (IOException e) {
- throw ExceptionUtils.wrap(e);
+
+ synchronized (mLock) {
+ assertCallerIsOwnerOrRootLocked();
+ assertPreparedAndNotCommittedOrDestroyedLocked("removeSplit");
+
+ try {
+ createRemoveSplitMarkerLocked(splitName);
+ } catch (IOException e) {
+ throw ExceptionUtils.wrap(e);
+ }
}
}
- private void createRemoveSplitMarker(String splitName) throws IOException {
+ private void createRemoveSplitMarkerLocked(String splitName) throws IOException {
try {
final String markerName = splitName + REMOVE_SPLIT_MARKER_EXTENSION;
if (!FileUtils.isValidExtFilename(markerName)) {
throw new IllegalArgumentException("Invalid marker: " + markerName);
}
- final File target = new File(resolveStageDir(), markerName);
+ final File target = new File(resolveStageDirLocked(), markerName);
target.createNewFile();
Os.chmod(target.getAbsolutePath(), 0 /*mode*/);
} catch (ErrnoException e) {
@@ -440,8 +549,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
// will block any attempted install transitions.
final RevocableFileDescriptor fd;
final FileBridge bridge;
+ final File stageDir;
synchronized (mLock) {
- assertPreparedAndNotSealed("openWrite");
+ assertCallerIsOwnerOrRootLocked();
+ assertPreparedAndNotSealedLocked("openWrite");
if (PackageInstaller.ENABLE_REVOCABLE_FD) {
fd = new RevocableFileDescriptor();
@@ -452,6 +563,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
bridge = new FileBridge();
mBridges.add(bridge);
}
+
+ stageDir = resolveStageDirLocked();
}
try {
@@ -462,7 +575,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
final File target;
final long identity = Binder.clearCallingIdentity();
try {
- target = new File(resolveStageDir(), name);
+ target = new File(stageDir, name);
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -500,55 +613,108 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
@Override
public ParcelFileDescriptor openRead(String name) {
- try {
- return openReadInternal(name);
- } catch (IOException e) {
- throw ExceptionUtils.wrap(e);
+ synchronized (mLock) {
+ assertCallerIsOwnerOrRootLocked();
+ assertPreparedAndNotCommittedOrDestroyedLocked("openRead");
+ try {
+ return openReadInternalLocked(name);
+ } catch (IOException e) {
+ throw ExceptionUtils.wrap(e);
+ }
}
}
- private ParcelFileDescriptor openReadInternal(String name) throws IOException {
- assertPreparedAndNotSealed("openRead");
-
+ private ParcelFileDescriptor openReadInternalLocked(String name) throws IOException {
try {
if (!FileUtils.isValidExtFilename(name)) {
throw new IllegalArgumentException("Invalid name: " + name);
}
- final File target = new File(resolveStageDir(), name);
-
+ final File target = new File(resolveStageDirLocked(), name);
final FileDescriptor targetFd = Libcore.os.open(target.getAbsolutePath(), O_RDONLY, 0);
return new ParcelFileDescriptor(targetFd);
-
} catch (ErrnoException e) {
throw e.rethrowAsIOException();
}
}
+ /**
+ * Check if the caller is the owner of this session. Otherwise throw a
+ * {@link SecurityException}.
+ */
+ private void assertCallerIsOwnerOrRootLocked() {
+ final int callingUid = Binder.getCallingUid();
+ if (callingUid != Process.ROOT_UID && callingUid != mInstallerUid) {
+ throw new SecurityException("Session does not belong to uid " + callingUid);
+ }
+ }
+
+ /**
+ * If anybody is reading or writing data of the session, throw an {@link SecurityException}.
+ */
+ private void assertNoWriteFileTransfersOpenLocked() {
+ // Verify that all writers are hands-off
+ for (RevocableFileDescriptor fd : mFds) {
+ if (!fd.isRevoked()) {
+ throw new SecurityException("Files still open");
+ }
+ }
+ for (FileBridge bridge : mBridges) {
+ if (!bridge.isClosed()) {
+ throw new SecurityException("Files still open");
+ }
+ }
+ }
+
@Override
- public void commit(IntentSender statusReceiver) {
+ public void commit(IntentSender statusReceiver, boolean forTransfer) {
Preconditions.checkNotNull(statusReceiver);
+ // Cache package manager data without the lock held
+ final PackageInfo installedPkgInfo = mPm.getPackageInfo(
+ params.appPackageName, PackageManager.GET_SIGNATURES
+ | PackageManager.MATCH_STATIC_SHARED_LIBRARIES /*flags*/, userId);
+
final boolean wasSealed;
synchronized (mLock) {
+ assertCallerIsOwnerOrRootLocked();
+ assertPreparedAndNotDestroyedLocked("commit");
+
+ if (forTransfer) {
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.INSTALL_PACKAGES, null);
+
+ if (mInstallerUid == mOriginalInstallerUid) {
+ throw new IllegalArgumentException("Session has not been transferred");
+ }
+ } else {
+ if (mInstallerUid != mOriginalInstallerUid) {
+ throw new IllegalArgumentException("Session has been transferred");
+ }
+ }
+
wasSealed = mSealed;
if (!mSealed) {
- // Verify that all writers are hands-off
- for (RevocableFileDescriptor fd : mFds) {
- if (!fd.isRevoked()) {
- throw new SecurityException("Files still open");
- }
+ try {
+ sealAndValidateLocked(installedPkgInfo);
+ } catch (PackageManagerException e) {
+ // Do now throw an exception here to stay compatible with O and older
+ destroyInternal();
+ dispatchSessionFinished(e.error, ExceptionUtils.getCompleteMessage(e), null);
+ return;
}
- for (FileBridge bridge : mBridges) {
- if (!bridge.isClosed()) {
- throw new SecurityException("Files still open");
- }
- }
- mSealed = true;
}
// Client staging is fully done at this point
mClientProgress = 1f;
computeProgressLocked(true);
+
+ // This ongoing commit should keep session active, even though client
+ // will probably close their end.
+ mActiveCount.incrementAndGet();
+
+ mCommitted = true;
+ final PackageInstallObserverAdapter adapter = new PackageInstallObserverAdapter(
+ mContext, statusReceiver, sessionId, isInstallerDeviceOwnerLocked(), userId);
+ mHandler.obtainMessage(MSG_COMMIT, adapter.getBinder()).sendToTarget();
}
if (!wasSealed) {
@@ -557,17 +723,82 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
// the session lock, since otherwise it's a lock inversion.
mCallback.onSessionSealedBlocking(this);
}
-
- // This ongoing commit should keep session active, even though client
- // will probably close their end.
- mActiveCount.incrementAndGet();
-
- final PackageInstallObserverAdapter adapter = new PackageInstallObserverAdapter(mContext,
- statusReceiver, sessionId, mIsInstallerDeviceOwner, userId);
- mHandler.obtainMessage(MSG_COMMIT, adapter.getBinder()).sendToTarget();
}
- private void commitLocked(PackageInfo pkgInfo, ApplicationInfo appInfo)
+ /**
+ * Seal the session to prevent further modification and validate the contents of it.
+ *
+ * The session will be sealed after calling this method even if it failed.
+ *
+ * @param pkgInfo The package info for {@link #params}.packagename
+ */
+ private void sealAndValidateLocked(PackageInfo pkgInfo)
+ throws PackageManagerException {
+ assertNoWriteFileTransfersOpenLocked();
+
+ mSealed = true;
+
+ // Verify that stage looks sane with respect to existing application.
+ // This currently only ensures packageName, versionCode, and certificate
+ // consistency.
+ validateInstallLocked(pkgInfo);
+
+ // Read transfers from the original owner stay open, but as the session's data
+ // cannot be modified anymore, there is no leak of information.
+ }
+
+ @Override
+ public void transfer(String packageName) {
+ Preconditions.checkNotNull(packageName);
+
+ ApplicationInfo newOwnerAppInfo = mPm.getApplicationInfo(packageName, 0, userId);
+ if (newOwnerAppInfo == null) {
+ throw new ParcelableException(new PackageManager.NameNotFoundException(packageName));
+ }
+
+ if (PackageManager.PERMISSION_GRANTED != mPm.checkUidPermission(
+ Manifest.permission.INSTALL_PACKAGES, newOwnerAppInfo.uid)) {
+ throw new SecurityException("Destination package " + packageName + " does not have "
+ + "the " + Manifest.permission.INSTALL_PACKAGES + " permission");
+ }
+
+ // Cache package manager data without the lock held
+ final PackageInfo installedPkgInfo = mPm.getPackageInfo(
+ params.appPackageName, PackageManager.GET_SIGNATURES
+ | PackageManager.MATCH_STATIC_SHARED_LIBRARIES /*flags*/, userId);
+
+ // Only install flags that can be verified by the app the session is transferred to are
+ // allowed. The parameters can be read via PackageInstaller.SessionInfo.
+ if (!params.areHiddenOptionsSet()) {
+ throw new SecurityException("Can only transfer sessions that use public options");
+ }
+
+ synchronized (mLock) {
+ assertCallerIsOwnerOrRootLocked();
+ assertPreparedAndNotSealedLocked("transfer");
+
+ try {
+ sealAndValidateLocked(installedPkgInfo);
+ } catch (PackageManagerException e) {
+ throw new IllegalArgumentException("Package is not valid", e);
+ }
+
+ if (!mPackageName.equals(mInstallerPackageName)) {
+ throw new SecurityException("Can only transfer sessions that update the original "
+ + "installer");
+ }
+
+ mInstallerPackageName = packageName;
+ mInstallerUid = newOwnerAppInfo.uid;
+ }
+
+ // Persist the fact that we've sealed ourselves to prevent
+ // mutations of any hard links we create. We do this without holding
+ // the session lock, since otherwise it's a lock inversion.
+ mCallback.onSessionSealedBlocking(this);
+ }
+
+ private void commitLocked()
throws PackageManagerException {
if (mDestroyed) {
throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, "Session destroyed");
@@ -577,22 +808,17 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
try {
- resolveStageDir();
+ resolveStageDirLocked();
} catch (IOException e) {
throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
"Failed to resolve stage location", e);
}
- // Verify that stage looks sane with respect to existing application.
- // This currently only ensures packageName, versionCode, and certificate
- // consistency.
- validateInstallLocked(pkgInfo, appInfo);
-
Preconditions.checkNotNull(mPackageName);
Preconditions.checkNotNull(mSignatures);
Preconditions.checkNotNull(mResolvedBaseFile);
- if (!mPermissionsAccepted) {
+ if (needToAskForPermissionsLocked()) {
// User needs to accept permissions; give installer an intent they
// can use to involve user.
final Intent intent = new Intent(PackageInstaller.ACTION_CONFIRM_PERMISSIONS);
@@ -622,7 +848,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
if (params.mode == SessionParams.MODE_INHERIT_EXISTING) {
try {
final List