diff --git a/api/current.txt b/api/current.txt index 8d2e9c8b9a541..5f17b8a645f4c 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 { diff --git a/api/system-current.txt b/api/system-current.txt index e2754fdd9cd53..cd8d054fe658b 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 { diff --git a/api/test-current.txt b/api/test-current.txt index fcb404edff99a..f84620523ee38 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 { 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..38b34872d2fa5 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();
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index bab70117659a5..c3b93b428cb52 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -16,17 +16,6 @@
package com.android.server.pm;
-import static com.android.internal.util.XmlUtils.readBitmapAttribute;
-import static com.android.internal.util.XmlUtils.readBooleanAttribute;
-import static com.android.internal.util.XmlUtils.readIntAttribute;
-import static com.android.internal.util.XmlUtils.readLongAttribute;
-import static com.android.internal.util.XmlUtils.readStringAttribute;
-import static com.android.internal.util.XmlUtils.readUriAttribute;
-import static com.android.internal.util.XmlUtils.writeBooleanAttribute;
-import static com.android.internal.util.XmlUtils.writeIntAttribute;
-import static com.android.internal.util.XmlUtils.writeLongAttribute;
-import static com.android.internal.util.XmlUtils.writeStringAttribute;
-import static com.android.internal.util.XmlUtils.writeUriAttribute;
import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
import static org.xmlpull.v1.XmlPullParser.START_TAG;
@@ -54,8 +43,6 @@ import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
import android.content.pm.VersionedPackage;
import android.graphics.Bitmap;
-import android.graphics.Bitmap.CompressFormat;
-import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
@@ -84,9 +71,6 @@ import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
import android.util.Xml;
-import java.io.CharArrayWriter;
-import libcore.io.IoUtils;
-
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.content.PackageHelper;
@@ -97,10 +81,13 @@ import com.android.internal.util.ImageUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.IoThread;
+import libcore.io.IoUtils;
+
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
+import java.io.CharArrayWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
@@ -125,32 +112,6 @@ public class PackageInstallerService extends IPackageInstaller.Stub {
/** XML constants used in {@link #mSessionsFile} */
private static final String TAG_SESSIONS = "sessions";
- private static final String TAG_SESSION = "session";
- private static final String TAG_GRANTED_RUNTIME_PERMISSION = "granted-runtime-permission";
- private static final String ATTR_SESSION_ID = "sessionId";
- private static final String ATTR_USER_ID = "userId";
- private static final String ATTR_INSTALLER_PACKAGE_NAME = "installerPackageName";
- private static final String ATTR_INSTALLER_UID = "installerUid";
- private static final String ATTR_CREATED_MILLIS = "createdMillis";
- private static final String ATTR_SESSION_STAGE_DIR = "sessionStageDir";
- private static final String ATTR_SESSION_STAGE_CID = "sessionStageCid";
- private static final String ATTR_PREPARED = "prepared";
- private static final String ATTR_SEALED = "sealed";
- private static final String ATTR_MODE = "mode";
- private static final String ATTR_INSTALL_FLAGS = "installFlags";
- private static final String ATTR_INSTALL_LOCATION = "installLocation";
- private static final String ATTR_SIZE_BYTES = "sizeBytes";
- private static final String ATTR_APP_PACKAGE_NAME = "appPackageName";
- @Deprecated
- private static final String ATTR_APP_ICON = "appIcon";
- private static final String ATTR_APP_LABEL = "appLabel";
- private static final String ATTR_ORIGINATING_URI = "originatingUri";
- private static final String ATTR_ORIGINATING_UID = "originatingUid";
- private static final String ATTR_REFERRER_URI = "referrerUri";
- private static final String ATTR_ABI_OVERRIDE = "abiOverride";
- private static final String ATTR_VOLUME_UUID = "volumeUuid";
- private static final String ATTR_NAME = "name";
- private static final String ATTR_INSTALL_REASON = "installRason";
/** Automatically destroy sessions older than this */
private static final long MAX_AGE_MILLIS = 3 * DateUtils.DAY_IN_MILLIS;
@@ -357,8 +318,10 @@ public class PackageInstallerService extends IPackageInstaller.Stub {
while ((type = in.next()) != END_DOCUMENT) {
if (type == START_TAG) {
final String tag = in.getName();
- if (TAG_SESSION.equals(tag)) {
- final PackageInstallerSession session = readSessionLocked(in);
+ if (PackageInstallerSession.TAG_SESSION.equals(tag)) {
+ final PackageInstallerSession session = PackageInstallerSession.
+ readFromXml(in, mInternalCallback, mContext, mPm,
+ mInstallThread.getLooper(), mSessionsDir);
final long age = System.currentTimeMillis() - session.createdMillis;
final boolean valid;
@@ -397,53 +360,10 @@ public class PackageInstallerService extends IPackageInstaller.Stub {
session.dump(pw);
mHistoricalSessions.add(writer.toString());
+ int installerUid = session.getInstallerUid();
// Increment the number of sessions by this installerUid.
- mHistoricalSessionsByInstaller.put(
- session.installerUid,
- mHistoricalSessionsByInstaller.get(session.installerUid) + 1);
- }
-
- private PackageInstallerSession readSessionLocked(XmlPullParser in) throws IOException,
- XmlPullParserException {
- final int sessionId = readIntAttribute(in, ATTR_SESSION_ID);
- final int userId = readIntAttribute(in, ATTR_USER_ID);
- final String installerPackageName = readStringAttribute(in, ATTR_INSTALLER_PACKAGE_NAME);
- final int installerUid = readIntAttribute(in, ATTR_INSTALLER_UID, mPm.getPackageUid(
- installerPackageName, PackageManager.MATCH_UNINSTALLED_PACKAGES, userId));
- final long createdMillis = readLongAttribute(in, ATTR_CREATED_MILLIS);
- final String stageDirRaw = readStringAttribute(in, ATTR_SESSION_STAGE_DIR);
- final File stageDir = (stageDirRaw != null) ? new File(stageDirRaw) : null;
- final String stageCid = readStringAttribute(in, ATTR_SESSION_STAGE_CID);
- final boolean prepared = readBooleanAttribute(in, ATTR_PREPARED, true);
- final boolean sealed = readBooleanAttribute(in, ATTR_SEALED);
-
- final SessionParams params = new SessionParams(
- SessionParams.MODE_INVALID);
- params.mode = readIntAttribute(in, ATTR_MODE);
- params.installFlags = readIntAttribute(in, ATTR_INSTALL_FLAGS);
- params.installLocation = readIntAttribute(in, ATTR_INSTALL_LOCATION);
- params.sizeBytes = readLongAttribute(in, ATTR_SIZE_BYTES);
- params.appPackageName = readStringAttribute(in, ATTR_APP_PACKAGE_NAME);
- params.appIcon = readBitmapAttribute(in, ATTR_APP_ICON);
- params.appLabel = readStringAttribute(in, ATTR_APP_LABEL);
- params.originatingUri = readUriAttribute(in, ATTR_ORIGINATING_URI);
- params.originatingUid =
- readIntAttribute(in, ATTR_ORIGINATING_UID, SessionParams.UID_UNKNOWN);
- params.referrerUri = readUriAttribute(in, ATTR_REFERRER_URI);
- params.abiOverride = readStringAttribute(in, ATTR_ABI_OVERRIDE);
- params.volumeUuid = readStringAttribute(in, ATTR_VOLUME_UUID);
- params.grantedRuntimePermissions = readGrantedRuntimePermissions(in);
- params.installReason = readIntAttribute(in, ATTR_INSTALL_REASON);
-
- final File appIconFile = buildAppIconFile(sessionId);
- if (appIconFile.exists()) {
- params.appIcon = BitmapFactory.decodeFile(appIconFile.getAbsolutePath());
- params.appIconLastModified = appIconFile.lastModified();
- }
-
- return new PackageInstallerSession(mInternalCallback, mContext, mPm,
- mInstallThread.getLooper(), sessionId, userId, installerPackageName, installerUid,
- params, createdMillis, stageDir, stageCid, prepared, sealed);
+ mHistoricalSessionsByInstaller.put(installerUid,
+ mHistoricalSessionsByInstaller.get(installerUid) + 1);
}
private void writeSessionsLocked() {
@@ -460,7 +380,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub {
final int size = mSessions.size();
for (int i = 0; i < size; i++) {
final PackageInstallerSession session = mSessions.valueAt(i);
- writeSessionLocked(out, session);
+ session.write(out, mSessionsDir);
}
out.endTag(null, TAG_SESSIONS);
out.endDocument();
@@ -473,106 +393,6 @@ public class PackageInstallerService extends IPackageInstaller.Stub {
}
}
- private void writeSessionLocked(XmlSerializer out, PackageInstallerSession session)
- throws IOException {
- final SessionParams params = session.params;
-
- out.startTag(null, TAG_SESSION);
-
- writeIntAttribute(out, ATTR_SESSION_ID, session.sessionId);
- writeIntAttribute(out, ATTR_USER_ID, session.userId);
- writeStringAttribute(out, ATTR_INSTALLER_PACKAGE_NAME,
- session.installerPackageName);
- writeIntAttribute(out, ATTR_INSTALLER_UID, session.installerUid);
- writeLongAttribute(out, ATTR_CREATED_MILLIS, session.createdMillis);
- if (session.stageDir != null) {
- writeStringAttribute(out, ATTR_SESSION_STAGE_DIR,
- session.stageDir.getAbsolutePath());
- }
- if (session.stageCid != null) {
- writeStringAttribute(out, ATTR_SESSION_STAGE_CID, session.stageCid);
- }
- writeBooleanAttribute(out, ATTR_PREPARED, session.isPrepared());
- writeBooleanAttribute(out, ATTR_SEALED, session.isSealed());
-
- writeIntAttribute(out, ATTR_MODE, params.mode);
- writeIntAttribute(out, ATTR_INSTALL_FLAGS, params.installFlags);
- writeIntAttribute(out, ATTR_INSTALL_LOCATION, params.installLocation);
- writeLongAttribute(out, ATTR_SIZE_BYTES, params.sizeBytes);
- writeStringAttribute(out, ATTR_APP_PACKAGE_NAME, params.appPackageName);
- writeStringAttribute(out, ATTR_APP_LABEL, params.appLabel);
- writeUriAttribute(out, ATTR_ORIGINATING_URI, params.originatingUri);
- writeIntAttribute(out, ATTR_ORIGINATING_UID, params.originatingUid);
- writeUriAttribute(out, ATTR_REFERRER_URI, params.referrerUri);
- writeStringAttribute(out, ATTR_ABI_OVERRIDE, params.abiOverride);
- writeStringAttribute(out, ATTR_VOLUME_UUID, params.volumeUuid);
- writeIntAttribute(out, ATTR_INSTALL_REASON, params.installReason);
-
- // Persist app icon if changed since last written
- final File appIconFile = buildAppIconFile(session.sessionId);
- if (params.appIcon == null && appIconFile.exists()) {
- appIconFile.delete();
- } else if (params.appIcon != null
- && appIconFile.lastModified() != params.appIconLastModified) {
- if (LOGD) Slog.w(TAG, "Writing changed icon " + appIconFile);
- FileOutputStream os = null;
- try {
- os = new FileOutputStream(appIconFile);
- params.appIcon.compress(CompressFormat.PNG, 90, os);
- } catch (IOException e) {
- Slog.w(TAG, "Failed to write icon " + appIconFile + ": " + e.getMessage());
- } finally {
- IoUtils.closeQuietly(os);
- }
-
- params.appIconLastModified = appIconFile.lastModified();
- }
-
- writeGrantedRuntimePermissions(out, params.grantedRuntimePermissions);
-
- out.endTag(null, TAG_SESSION);
- }
-
- private static void writeGrantedRuntimePermissions(XmlSerializer out,
- String[] grantedRuntimePermissions) throws IOException {
- if (grantedRuntimePermissions != null) {
- for (String permission : grantedRuntimePermissions) {
- out.startTag(null, TAG_GRANTED_RUNTIME_PERMISSION);
- writeStringAttribute(out, ATTR_NAME, permission);
- out.endTag(null, TAG_GRANTED_RUNTIME_PERMISSION);
- }
- }
- }
-
- private static String[] readGrantedRuntimePermissions(XmlPullParser in)
- throws IOException, XmlPullParserException {
- List 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;
@@ -326,14 +404,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 +432,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 +463,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
@Override
public void addClientProgress(float progress) {
synchronized (mLock) {
+ assertCallerIsOwnerOrRootLocked();
+
setClientProgress(mClientProgress + progress);
}
}
@@ -390,11 +482,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 +499,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 +542,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 +556,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
bridge = new FileBridge();
mBridges.add(bridge);
}
+
+ stageDir = resolveStageDirLocked();
}
try {
@@ -462,7 +568,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 +606,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 +716,76 @@ 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);
+
+ 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 +795,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 +835,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
if (params.mode == SessionParams.MODE_INHERIT_EXISTING) {
try {
final List