From 7460c5917b47a5ba183517b5459c254130fc85b5 Mon Sep 17 00:00:00 2001 From: "Philip P. Moltmann" Date: Tue, 8 Aug 2017 20:07:11 +0000 Subject: [PATCH] Revert "Revert session-transfer change" This reverts commit 9890f8b426550485aaab164a7bedbcd545862b85. Bug: 64467704 Test: cts-tradefed run singleCommand cts-dev -m CtsContentTestCases --test=android.content.pm.cts.InstallSessionParamsUnitTest cts-tradefed run singleCommand cts-dev -m CtsContentTestCases --test=android.content.pm.cts.InstallSessionTransferTest Change-Id: I0cc7e1129d85e4d0de16ade44232a9bd381d6c04 --- api/current.txt | 7 + api/system-current.txt | 15 + api/test-current.txt | 7 + .../content/pm/IPackageInstallerSession.aidl | 3 +- core/java/android/content/pm/PackageInstaller.java | 237 ++++++- .../android/server/pm/PackageInstallerService.java | 212 +------ .../android/server/pm/PackageInstallerSession.java | 681 +++++++++++++++++---- 7 files changed, 835 insertions(+), 327 deletions(-) diff --git a/api/current.txt b/api/current.txt index 6fa840ecb08e..ec6215856fdd 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 c102ccc3fc5a..f2d2b34249e1 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 630ca8e9f566..c80c3641c316 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 2a3fac341e24..0b16852246f8 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 c3ebf554ea8c..f4fdcaa44836 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(); } @@ -1041,6 +1103,26 @@ public class PackageInstaller { } /** + * 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 * {@link PackageInfo#INSTALL_LOCATION_INTERNAL_ONLY}. @@ -1300,6 +1382,19 @@ public class PackageInstaller { 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 diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index bab70117659a..c3b93b428cb5 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 permissions = null; - - final int outerDepth = in.getDepth(); - int type; - while ((type = in.next()) != XmlPullParser.END_DOCUMENT - && (type != XmlPullParser.END_TAG || in.getDepth() > outerDepth)) { - if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { - continue; - } - if (TAG_GRANTED_RUNTIME_PERMISSION.equals(in.getName())) { - String permission = readStringAttribute(in, ATTR_NAME); - if (permissions == null) { - permissions = new ArrayList<>(); - } - permissions.add(permission); - } - } - - if (permissions == null) { - return null; - } - - String[] permissionsArray = new String[permissions.size()]; - permissions.toArray(permissionsArray); - return permissionsArray; - } - private File buildAppIconFile(int sessionId) { return new File(mSessionsDir, "app_icon." + sessionId + ".png"); } @@ -885,9 +705,11 @@ public class PackageInstallerService extends IPackageInstaller.Stub { synchronized (mSessions) { for (int i = 0; i < mSessions.size(); i++) { final PackageInstallerSession session = mSessions.valueAt(i); - if (Objects.equals(session.installerPackageName, installerPackageName) + + SessionInfo info = session.generateInfo(false); + if (Objects.equals(info.getInstallerPackageName(), installerPackageName) && session.userId == userId) { - result.add(session.generateInfo(false)); + result.add(info); } } } @@ -962,7 +784,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub { final int size = sessions.size(); for (int i = 0; i < size; i++) { final PackageInstallerSession session = sessions.valueAt(i); - if (session.installerUid == installerUid) { + if (session.getInstallerUid() == installerUid) { count++; } } @@ -974,7 +796,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub { if (callingUid == Process.ROOT_UID) { return true; } else { - return (session != null) && (callingUid == session.installerUid); + return (session != null) && (callingUid == session.getInstallerUid()); } } diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 58237713d793..0fd696fbcd90 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -25,9 +25,22 @@ import static android.system.OsConstants.O_CREAT; import static android.system.OsConstants.O_RDONLY; import static android.system.OsConstants.O_WRONLY; +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 com.android.server.pm.PackageInstallerService.prepareExternalStageCid; import static com.android.server.pm.PackageInstallerService.prepareStageDir; +import android.Manifest; +import android.annotation.NonNull; import android.app.admin.DevicePolicyManager; import android.content.Context; import android.content.Intent; @@ -45,6 +58,8 @@ import android.content.pm.PackageParser.ApkLite; import android.content.pm.PackageParser.PackageLite; import android.content.pm.PackageParser.PackageParserException; import android.content.pm.Signature; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; import android.os.Binder; import android.os.Bundle; import android.os.FileBridge; @@ -53,6 +68,7 @@ import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.ParcelFileDescriptor; +import android.os.ParcelableException; import android.os.Process; import android.os.RemoteException; import android.os.RevocableFileDescriptor; @@ -80,9 +96,14 @@ import com.android.server.pm.PackageInstallerService.PackageInstallObserverAdapt import libcore.io.IoUtils; import libcore.io.Libcore; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + import java.io.File; import java.io.FileDescriptor; import java.io.FileFilter; +import java.io.FileOutputStream; import java.io.IOException; import java.security.cert.Certificate; import java.util.ArrayList; @@ -97,6 +118,34 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { private static final int MSG_COMMIT = 0; + /** XML constants used for persisting a session */ + 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"; + // TODO: enforce INSTALL_ALLOW_TEST // TODO: enforce INSTALL_ALLOW_DOWNGRADE @@ -104,12 +153,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { private final Context mContext; private final PackageManagerService mPm; private final Handler mHandler; - private final boolean mIsInstallerDeviceOwner; final int sessionId; final int userId; - final String installerPackageName; - final int installerUid; final SessionParams params; final long createdMillis; final int defaultContainerGid; @@ -122,6 +168,17 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { private final Object mLock = new Object(); + /** Uid of the creator of this session. */ + private final int mOriginalInstallerUid; + + /** Package of the owner of the installer session */ + @GuardedBy("mLock") + private String mInstallerPackageName; + + /** Uid of the owner of the installer session */ + @GuardedBy("mLock") + private int mInstallerUid; + @GuardedBy("mLock") private float mClientProgress = 0; @GuardedBy("mLock") @@ -132,18 +189,25 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { @GuardedBy("mLock") private float mReportedProgress = -1; + /** State of the session. */ @GuardedBy("mLock") private boolean mPrepared = false; @GuardedBy("mLock") private boolean mSealed = false; @GuardedBy("mLock") - private boolean mPermissionsAccepted = false; + private boolean mCommitted = false; @GuardedBy("mLock") private boolean mRelinquished = false; @GuardedBy("mLock") private boolean mDestroyed = false; + /** Permissions have been accepted by the user (see {@link #setPermissionsResult}) */ + @GuardedBy("mLock") + private boolean mPermissionsManuallyAccepted = false; + + @GuardedBy("mLock") private int mFinalStatus; + @GuardedBy("mLock") private String mFinalMessage; @GuardedBy("mLock") @@ -155,9 +219,13 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { private IPackageInstallObserver2 mRemoteObserver; /** Fields derived from commit parsing */ + @GuardedBy("mLock") private String mPackageName; + @GuardedBy("mLock") private int mVersionCode; + @GuardedBy("mLock") private Signature[] mSignatures; + @GuardedBy("mLock") private Certificate[][] mCertificates; /** @@ -205,32 +273,61 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { private final Handler.Callback mHandlerCallback = new Handler.Callback() { @Override public boolean handleMessage(Message msg) { - // Cache package manager data without the lock held - final PackageInfo pkgInfo = mPm.getPackageInfo( - params.appPackageName, PackageManager.GET_SIGNATURES - | PackageManager.MATCH_STATIC_SHARED_LIBRARIES /*flags*/, userId); - final ApplicationInfo appInfo = mPm.getApplicationInfo( - params.appPackageName, 0, userId); - synchronized (mLock) { if (msg.obj != null) { mRemoteObserver = (IPackageInstallObserver2) msg.obj; } - try { - commitLocked(pkgInfo, appInfo); + commitLocked(); } catch (PackageManagerException e) { final String completeMsg = ExceptionUtils.getCompleteMessage(e); Slog.e(TAG, "Commit of session " + sessionId + " failed: " + completeMsg); destroyInternal(); dispatchSessionFinished(e.error, completeMsg, null); } - - return true; } + + return true; } }; + /** + * @return {@code true} iff the installing is app an device owner? + */ + private boolean isInstallerDeviceOwnerLocked() { + DevicePolicyManager dpm = (DevicePolicyManager) mContext.getSystemService( + Context.DEVICE_POLICY_SERVICE); + + return (dpm != null) && dpm.isDeviceOwnerAppOnCallingUser( + mInstallerPackageName); + } + + /** + * Checks if the permissions still need to be confirmed. + * + *

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"); - } - } - for (FileBridge bridge : mBridges) { - if (!bridge.isClosed()) { - 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; } - 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(); + /** + * 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); - final PackageInstallObserverAdapter adapter = new PackageInstallObserverAdapter(mContext, - statusReceiver, sessionId, mIsInstallerDeviceOwner, userId); - mHandler.obtainMessage(MSG_COMMIT, adapter.getBinder()).sendToTarget(); + // Read transfers from the original owner stay open, but as the session's data + // cannot be modified anymore, there is no leak of information. } - private void commitLocked(PackageInfo pkgInfo, ApplicationInfo appInfo) + @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 fromFiles = mResolvedInheritedFiles; - final File toDir = resolveStageDir(); + final File toDir = resolveStageDirLocked(); if (LOGD) Slog.d(TAG, "Inherited files: " + mResolvedInheritedFiles); if (!mResolvedInheritedFiles.isEmpty() && mInheritedFilesBase == null) { @@ -683,7 +909,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { mRelinquished = true; mPm.installStage(mPackageName, stageDir, stageCid, localObserver, params, - installerPackageName, installerUid, user, mCertificates); + mInstallerPackageName, mInstallerUid, user, mCertificates); } /** @@ -698,8 +924,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { * Note that upgrade compatibility is still performed by * {@link PackageManagerService}. */ - private void validateInstallLocked(PackageInfo pkgInfo, ApplicationInfo appInfo) - throws PackageManagerException { + private void validateInstallLocked(PackageInfo pkgInfo) throws PackageManagerException { mPackageName = null; mVersionCode = -1; mSignatures = null; @@ -752,7 +977,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { mCertificates = apk.certificates; } - assertApkConsistent(String.valueOf(addedFile), apk); + assertApkConsistentLocked(String.valueOf(addedFile), apk); // Take this opportunity to enforce uniform naming final String targetName; @@ -807,13 +1032,14 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } else { // Partial installs must be consistent with existing install - if (appInfo == null) { + if (pkgInfo == null || pkgInfo.applicationInfo == null) { throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, "Missing existing base package for " + mPackageName); } final PackageLite existing; final ApkLite existingBase; + ApplicationInfo appInfo = pkgInfo.applicationInfo; try { existing = PackageParser.parsePackageLite(new File(appInfo.getCodePath()), 0); existingBase = PackageParser.parseApkLite(new File(appInfo.getBaseCodePath()), @@ -822,7 +1048,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { throw PackageManagerException.from(e); } - assertApkConsistent("Existing base", existingBase); + assertApkConsistentLocked("Existing base", existingBase); // Inherit base if not overridden if (mResolvedBaseFile == null) { @@ -878,7 +1104,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } - private void assertApkConsistent(String tag, ApkLite apk) + private void assertApkConsistentLocked(String tag, ApkLite apk) throws PackageManagerException { if (!mPackageName.equals(apk.packageName)) { throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, tag + " package " @@ -959,6 +1185,15 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { return true; } + /** + * @return the uid of the owner this session + */ + public int getInstallerUid() { + synchronized (mLock) { + return mInstallerUid; + } + } + private static String getRelativePath(File file, File base) throws IOException { final String pathStr = file.getAbsolutePath(); final String baseStr = base.getAbsolutePath(); @@ -1106,9 +1341,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { if (accepted) { // Mark and kick off another install pass synchronized (mLock) { - mPermissionsAccepted = true; + mPermissionsManuallyAccepted = true; + mHandler.obtainMessage(MSG_COMMIT).sendToTarget(); } - mHandler.obtainMessage(MSG_COMMIT).sendToTarget(); } else { destroyInternal(); dispatchSessionFinished(INSTALL_FAILED_ABORTED, "User rejected permissions", null); @@ -1120,7 +1355,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { mCallback.onSessionActiveChanged(this, true); } + boolean wasPrepared; synchronized (mLock) { + wasPrepared = mPrepared; if (!mPrepared) { if (stageDir != null) { prepareStageDir(stageDir); @@ -1141,13 +1378,20 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } mPrepared = true; - mCallback.onSessionPrepared(this); } } + + if (!wasPrepared) { + mCallback.onSessionPrepared(this); + } } @Override public void close() { + synchronized (mLock) { + assertCallerIsOwnerOrRootLocked(); + } + if (mActiveCount.decrementAndGet() == 0) { mCallback.onSessionActiveChanged(this, false); } @@ -1155,21 +1399,33 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { @Override public void abandon() { - if (mRelinquished) { - Slog.d(TAG, "Ignoring abandon after commit relinquished control"); - return; + synchronized (mLock) { + assertCallerIsOwnerOrRootLocked(); + + if (mRelinquished) { + Slog.d(TAG, "Ignoring abandon after commit relinquished control"); + return; + } + destroyInternal(); } - destroyInternal(); + dispatchSessionFinished(INSTALL_FAILED_ABORTED, "Session was abandoned", null); } private void dispatchSessionFinished(int returnCode, String msg, Bundle extras) { - mFinalStatus = returnCode; - mFinalMessage = msg; + IPackageInstallObserver2 observer; + String packageName; + synchronized (mLock) { + mFinalStatus = returnCode; + mFinalMessage = msg; - if (mRemoteObserver != null) { + observer = mRemoteObserver; + packageName = mPackageName; + } + + if (observer != null) { try { - mRemoteObserver.onPackageInstalled(mPackageName, returnCode, msg, extras); + observer.onPackageInstalled(packageName, returnCode, msg, extras); } catch (RemoteException ignored) { } } @@ -1220,8 +1476,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { pw.increaseIndent(); pw.printPair("userId", userId); - pw.printPair("installerPackageName", installerPackageName); - pw.printPair("installerUid", installerUid); + pw.printPair("mOriginalInstallerUid", mOriginalInstallerUid); + pw.printPair("mInstallerPackageName", mInstallerPackageName); + pw.printPair("mInstallerUid", mInstallerUid); pw.printPair("createdMillis", createdMillis); pw.printPair("stageDir", stageDir); pw.printPair("stageCid", stageCid); @@ -1232,7 +1489,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { pw.printPair("mClientProgress", mClientProgress); pw.printPair("mProgress", mProgress); pw.printPair("mSealed", mSealed); - pw.printPair("mPermissionsAccepted", mPermissionsAccepted); + pw.printPair("mPermissionsManuallyAccepted", mPermissionsManuallyAccepted); pw.printPair("mRelinquished", mRelinquished); pw.printPair("mDestroyed", mDestroyed); pw.printPair("mFds", mFds.size()); @@ -1243,4 +1500,170 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { pw.decreaseIndent(); } + + private static void writeGrantedRuntimePermissionsLocked(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 File buildAppIconFile(int sessionId, @NonNull File sessionsDir) { + return new File(sessionsDir, "app_icon." + sessionId + ".png"); + } + + /** + * Write this session to a {@link XmlSerializer}. + * + * @param out Where to write the session to + * @param sessionsDir The directory containing the sessions + */ + void write(@NonNull XmlSerializer out, @NonNull File sessionsDir) throws IOException { + synchronized (mLock) { + out.startTag(null, TAG_SESSION); + + writeIntAttribute(out, ATTR_SESSION_ID, sessionId); + writeIntAttribute(out, ATTR_USER_ID, userId); + writeStringAttribute(out, ATTR_INSTALLER_PACKAGE_NAME, + mInstallerPackageName); + writeIntAttribute(out, ATTR_INSTALLER_UID, mInstallerUid); + writeLongAttribute(out, ATTR_CREATED_MILLIS, createdMillis); + if (stageDir != null) { + writeStringAttribute(out, ATTR_SESSION_STAGE_DIR, + stageDir.getAbsolutePath()); + } + if (stageCid != null) { + writeStringAttribute(out, ATTR_SESSION_STAGE_CID, stageCid); + } + writeBooleanAttribute(out, ATTR_PREPARED, isPrepared()); + writeBooleanAttribute(out, ATTR_SEALED, 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 + File appIconFile = buildAppIconFile(sessionId, sessionsDir); + 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(Bitmap.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(); + } + + writeGrantedRuntimePermissionsLocked(out, params.grantedRuntimePermissions); + } + + out.endTag(null, TAG_SESSION); + } + + private static String[] readGrantedRuntimePermissions(XmlPullParser in) + throws IOException, XmlPullParserException { + List permissions = null; + + final int outerDepth = in.getDepth(); + int type; + while ((type = in.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG || in.getDepth() > outerDepth)) { + if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { + continue; + } + if (TAG_GRANTED_RUNTIME_PERMISSION.equals(in.getName())) { + String permission = readStringAttribute(in, ATTR_NAME); + if (permissions == null) { + permissions = new ArrayList<>(); + } + permissions.add(permission); + } + } + + if (permissions == null) { + return null; + } + + String[] permissionsArray = new String[permissions.size()]; + permissions.toArray(permissionsArray); + return permissionsArray; + } + + /** + * Read new session from a {@link XmlPullParser xml description} and create it. + * + * @param in The source of the description + * @param callback Callback the session uses to notify about changes of it's state + * @param context Context to be used by the session + * @param pm PackageManager to use by the session + * @param installerThread Thread to be used for callbacks of this session + * @param sessionsDir The directory the sessions are stored in + * + * @return The newly created session + */ + public static PackageInstallerSession readFromXml(@NonNull XmlPullParser in, + @NonNull PackageInstallerService.InternalCallback callback, @NonNull Context context, + @NonNull PackageManagerService pm, Looper installerThread, @NonNull File sessionsDir) + 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, pm.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, sessionsDir); + if (appIconFile.exists()) { + params.appIcon = BitmapFactory.decodeFile(appIconFile.getAbsolutePath()); + params.appIconLastModified = appIconFile.lastModified(); + } + + return new PackageInstallerSession(callback, context, pm, + installerThread, sessionId, userId, installerPackageName, installerUid, + params, createdMillis, stageDir, stageCid, prepared, sealed); + } } -- 2.11.0