Merge "Add PackageInstaller SessionParams restrictions" into rvc-dev am: a817a0515e

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/11621266

Change-Id: I7aecc94ad108866aa4aebcf178592dc2ed1f2ea9
This commit is contained in:
TreeHugger Robot
2020-06-03 16:51:54 +00:00
committed by Automerger Merge Worker
8 changed files with 330 additions and 7 deletions

View File

@@ -1449,6 +1449,13 @@ public class PackageInstaller {
/** {@hide} */
public static final int UID_UNKNOWN = -1;
/**
* This value is derived from the maximum file name length. No package above this limit
* can ever be successfully installed on the device.
* @hide
*/
public static final int MAX_PACKAGE_NAME_LENGTH = 255;
/** {@hide} */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
public int mode = MODE_INVALID;
@@ -1642,6 +1649,8 @@ public class PackageInstaller {
/**
* Optionally set a label representing the app being installed.
*
* This value will be trimmed to the first 1000 characters.
*/
public void setAppLabel(@Nullable CharSequence appLabel) {
this.appLabel = (appLabel != null) ? appLabel.toString() : null;
@@ -1711,7 +1720,8 @@ public class PackageInstaller {
*
* <p>Initially, all restricted permissions are whitelisted but you can change
* which ones are whitelisted by calling this method or the corresponding ones
* on the {@link PackageManager}.
* on the {@link PackageManager}. Only soft or hard restricted permissions on the current
* Android version are supported and any invalid entries will be removed.
*
* @see PackageManager#addWhitelistedRestrictedPermission(String, String, int)
* @see PackageManager#removeWhitelistedRestrictedPermission(String, String, int)

View File

@@ -49,8 +49,16 @@ import java.util.Objects;
* in the implementation of Parcelable in subclasses.
*/
public class PackageItemInfo {
/** The maximum length of a safe label, in characters */
private static final int MAX_SAFE_LABEL_LENGTH = 50000;
/**
* The maximum length of a safe label, in characters
*
* TODO(b/157997155): It may make sense to expose this publicly so that apps can check for the
* value and truncate the strings/use a different label, without having to hardcode and make
* assumptions about the value.
* @hide
*/
public static final int MAX_SAFE_LABEL_LENGTH = 1000;
/** @hide */
public static final float DEFAULT_MAX_LABEL_SIZE_PX = 500f;

View File

@@ -0,0 +1,42 @@
//
// Copyright 2020 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
android_test {
name: "FrameworksCorePackageInstallerSessionsTests",
srcs: [
"src/**/*.kt",
],
static_libs: [
"androidx.test.rules",
"compatibility-device-util-axt",
"frameworks-base-testutils",
"platform-test-annotations",
"testng",
"truth-prebuilt",
],
libs: [
"android.test.runner",
"android.test.base",
"framework",
"framework-res",
],
platform_apis: true,
sdk_version: "core_platform",
test_suites: ["device-tests"],
}

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
-->
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.frameworks.coretests.package_installer_sessions"
>
<application>
<uses-library android:name="android.test.runner" />
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.frameworks.coretests.package_installer_sessions"/>
</manifest>

View File

@@ -0,0 +1,188 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.content.pm
import android.content.Context
import android.content.pm.PackageInstaller.SessionParams
import android.platform.test.annotations.Presubmit
import androidx.test.InstrumentationRegistry
import androidx.test.filters.LargeTest
import com.android.compatibility.common.util.ShellIdentityUtils
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.testng.Assert.assertThrows
import kotlin.random.Random
/**
* For verifying public [PackageInstaller] session APIs. This differs from
* [com.android.server.pm.PackageInstallerSessionTest] in services because that mocks the session,
* whereas this test uses the installer on device.
*/
@Presubmit
class PackageSessionTests {
companion object {
/**
* Permissions marked "hardRestricted" or "softRestricted" in core/res/AndroidManifest.xml.
*/
private val RESTRICTED_PERMISSIONS = listOf(
"android.permission.SEND_SMS",
"android.permission.RECEIVE_SMS",
"android.permission.READ_SMS",
"android.permission.RECEIVE_WAP_PUSH",
"android.permission.RECEIVE_MMS",
"android.permission.READ_CELL_BROADCASTS",
"android.permission.ACCESS_BACKGROUND_LOCATION",
"android.permission.READ_CALL_LOG",
"android.permission.WRITE_CALL_LOG",
"android.permission.PROCESS_OUTGOING_CALLS"
)
}
private val context: Context = InstrumentationRegistry.getContext()
private val installer = context.packageManager.packageInstaller
@Before
@After
fun abandonAllSessions() {
installer.mySessions.asSequence()
.map { it.sessionId }
.forEach {
try {
installer.abandonSession(it)
} catch (ignored: Exception) {
// Querying for sessions checks by calling package name, but abandoning
// checks by UID, which won't match if this test failed to clean up
// on a previous install + run + uninstall, so ignore these failures.
}
}
}
@Test
fun truncateAppLabel() {
val longLabel = invalidAppLabel()
val params = SessionParams(SessionParams.MODE_FULL_INSTALL).apply {
setAppLabel(longLabel)
}
createSession(params) {
assertThat(installer.getSessionInfo(it)?.appLabel)
.isEqualTo(longLabel.take(PackageItemInfo.MAX_SAFE_LABEL_LENGTH))
}
}
@Test
fun removeInvalidAppPackageName() {
val longName = invalidPackageName()
val params = SessionParams(SessionParams.MODE_FULL_INSTALL).apply {
setAppPackageName(longName)
}
createSession(params) {
assertThat(installer.getSessionInfo(it)?.appPackageName)
.isEqualTo(null)
}
}
@Test
fun removeInvalidInstallerPackageName() {
val longName = invalidPackageName()
val params = SessionParams(SessionParams.MODE_FULL_INSTALL).apply {
setInstallerPackageName(longName)
}
createSession(params) {
// If a custom installer name is dropped, it defaults to the caller
assertThat(installer.getSessionInfo(it)?.installerPackageName)
.isEqualTo(context.packageName)
}
}
@Test
fun truncateWhitelistPermissions() {
val params = SessionParams(SessionParams.MODE_FULL_INSTALL).apply {
setWhitelistedRestrictedPermissions(invalidPermissions())
}
createSession(params) {
assertThat(installer.getSessionInfo(it)?.whitelistedRestrictedPermissions!!)
.containsExactlyElementsIn(RESTRICTED_PERMISSIONS)
}
}
@LargeTest
@Test
fun allocateMaxSessionsWithPermission() {
ShellIdentityUtils.invokeWithShellPermissions {
repeat(1024) { createDummySession() }
assertThrows(IllegalStateException::class.java) { createDummySession() }
}
}
@LargeTest
@Test
fun allocateMaxSessionsNoPermission() {
repeat(50) { createDummySession() }
assertThrows(IllegalStateException::class.java) { createDummySession() }
}
private fun createDummySession() {
installer.createSession(SessionParams(SessionParams.MODE_FULL_INSTALL)
.apply {
setAppPackageName(invalidPackageName())
setAppLabel(invalidAppLabel())
setWhitelistedRestrictedPermissions(invalidPermissions())
})
}
private fun invalidPackageName(maxLength: Int = SessionParams.MAX_PACKAGE_NAME_LENGTH): String {
return (0 until (maxLength + 10))
.asSequence()
.mapIndexed { index, _ ->
// A package name needs at least one separator
if (index == 2) {
'.'
} else {
Random.nextInt('z' - 'a').toChar() + 'a'.toInt()
}
}
.joinToString(separator = "")
}
private fun invalidAppLabel() = (0 until PackageItemInfo.MAX_SAFE_LABEL_LENGTH + 10)
.asSequence()
.map { Random.nextInt(Char.MAX_VALUE.toInt()).toChar() }
.joinToString(separator = "")
private fun invalidPermissions() = RESTRICTED_PERMISSIONS.toMutableSet()
.apply {
// Add some invalid permission names
repeat(10) { add(invalidPackageName(300)) }
}
private fun createSession(params: SessionParams, block: (Int) -> Unit = {}) {
val sessionId = installer.createSession(params)
try {
block(sessionId)
} finally {
installer.abandonSession(sessionId)
}
}
}

View File

@@ -40,6 +40,7 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageInstaller.SessionInfo;
import android.content.pm.PackageInstaller.SessionParams;
import android.content.pm.PackageItemInfo;
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
import android.content.pm.VersionedPackage;
@@ -126,8 +127,10 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
private static final long MAX_AGE_MILLIS = 3 * DateUtils.DAY_IN_MILLIS;
/** Automatically destroy staged sessions that have not changed state in this time */
private static final long MAX_TIME_SINCE_UPDATE_MILLIS = 7 * DateUtils.DAY_IN_MILLIS;
/** Upper bound on number of active sessions for a UID */
private static final long MAX_ACTIVE_SESSIONS = 1024;
/** Upper bound on number of active sessions for a UID that has INSTALL_PACKAGES */
private static final long MAX_ACTIVE_SESSIONS_WITH_PERMISSION = 1024;
/** Upper bound on number of active sessions for a UID without INSTALL_PACKAGES */
private static final long MAX_ACTIVE_SESSIONS_NO_PERMISSION = 50;
/** Upper bound on number of historical sessions for a UID */
private static final long MAX_HISTORICAL_SESSIONS = 1048576;
@@ -503,7 +506,18 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
+ "to use a data loader");
}
String requestedInstallerPackageName = params.installerPackageName != null
// App package name and label length is restricted so that really long strings aren't
// written to disk.
if (params.appPackageName != null
&& params.appPackageName.length() > SessionParams.MAX_PACKAGE_NAME_LENGTH) {
params.appPackageName = null;
}
params.appLabel = TextUtils.trimToSize(params.appLabel,
PackageItemInfo.MAX_SAFE_LABEL_LENGTH);
String requestedInstallerPackageName = (params.installerPackageName != null
&& params.installerPackageName.length() < SessionParams.MAX_PACKAGE_NAME_LENGTH)
? params.installerPackageName : installerPackageName;
if ((callingUid == Process.SHELL_UID) || (callingUid == Process.ROOT_UID)) {
@@ -635,12 +649,23 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
}
}
if (params.whitelistedRestrictedPermissions != null) {
mPermissionManager.retainHardAndSoftRestrictedPermissions(
params.whitelistedRestrictedPermissions);
}
final int sessionId;
final PackageInstallerSession session;
synchronized (mSessions) {
// Sanity check that installer isn't going crazy
final int activeCount = getSessionCount(mSessions, callingUid);
if (activeCount >= MAX_ACTIVE_SESSIONS) {
if (mContext.checkCallingOrSelfPermission(Manifest.permission.INSTALL_PACKAGES)
== PackageManager.PERMISSION_GRANTED) {
if (activeCount >= MAX_ACTIVE_SESSIONS_WITH_PERMISSION) {
throw new IllegalStateException(
"Too many active sessions for UID " + callingUid);
}
} else if (activeCount >= MAX_ACTIVE_SESSIONS_NO_PERMISSION) {
throw new IllegalStateException(
"Too many active sessions for UID " + callingUid);
}

View File

@@ -4950,6 +4950,20 @@ public class PermissionManagerService extends IPermissionManager.Stub {
StorageManager.UUID_PRIVATE_INTERNAL, true, mDefaultPermissionCallback);
}
}
@Override
public void retainHardAndSoftRestrictedPermissions(@NonNull List<String> permissions) {
synchronized (mLock) {
Iterator<String> iterator = permissions.iterator();
while (iterator.hasNext()) {
String permission = iterator.next();
BasePermission basePermission = mSettings.mPermissions.get(permission);
if (basePermission == null || !basePermission.isHardOrSoftRestricted()) {
iterator.remove();
}
}
}
}
}
private static final class OnPermissionChangeListeners extends Handler {

View File

@@ -36,6 +36,7 @@ import java.util.function.Consumer;
* TODO: Should be merged into PermissionManagerInternal, but currently uses internal classes.
*/
public abstract class PermissionManagerServiceInternal extends PermissionManagerInternal {
/**
* Provider for package names.
*/
@@ -455,4 +456,10 @@ public abstract class PermissionManagerServiceInternal extends PermissionManager
/** Called when a new user has been created. */
public abstract void onNewUserCreated(@UserIdInt int userId);
/**
* Removes invalid permissions which are not {@link PermissionInfo#FLAG_HARD_RESTRICTED} or
* {@link PermissionInfo#FLAG_SOFT_RESTRICTED} from the input.
*/
public abstract void retainHardAndSoftRestrictedPermissions(@NonNull List<String> permissions);
}