diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2022-11-11 00:28:15 +0000 |
---|---|---|
committer | Justin Dunlap <justindunlap@google.com> | 2022-12-05 14:08:21 -0800 |
commit | eac683523560b7e3d392eca74fa7b53939df3c3c (patch) | |
tree | 2cbdae43778cb3ce9d7ca1ec53c012a2d6b70987 | |
parent | b02406e67c742f130b1d4f857071cce0e8177554 (diff) | |
parent | b973b5c8190a967cad12428be7ec87464fed8ddf (diff) | |
download | base-eac683523560b7e3d392eca74fa7b53939df3c3c.tar.gz |
Merge cherrypicks of [18563202, 19970324, 15982545, 20120242, 20208339, 20211078, 20291140, 20251345, 20223219, 20122955, 20234866, 20219552, 20426455] into security-aosp-qt-release.android-security-10.0.0_r74
Change-Id: Ifcd157219e4cecce5376bae406c5d57ffd96716e
17 files changed, 610 insertions, 105 deletions
diff --git a/core/java/android/app/AutomaticZenRule.java b/core/java/android/app/AutomaticZenRule.java index 37b336382769..9a92515ee794 100644 --- a/core/java/android/app/AutomaticZenRule.java +++ b/core/java/android/app/AutomaticZenRule.java @@ -125,7 +125,7 @@ public final class AutomaticZenRule implements Parcelable { name = getTrimmedString(source.readString()); } interruptionFilter = source.readInt(); - conditionId = source.readParcelable(null); + conditionId = getTrimmedUri(source.readParcelable(null)); owner = getTrimmedComponentName(source.readParcelable(null)); configurationActivity = getTrimmedComponentName(source.readParcelable(null)); creationTime = source.readLong(); diff --git a/core/java/android/hardware/usb/UsbDeviceConnection.java b/core/java/android/hardware/usb/UsbDeviceConnection.java index 71297c187e5c..5c7a1b0aea3b 100644 --- a/core/java/android/hardware/usb/UsbDeviceConnection.java +++ b/core/java/android/hardware/usb/UsbDeviceConnection.java @@ -52,6 +52,8 @@ public class UsbDeviceConnection { private final CloseGuard mCloseGuard = CloseGuard.get(); + private final Object mLock = new Object(); + /** * UsbDevice should only be instantiated by UsbService implementation * @hide @@ -62,13 +64,23 @@ public class UsbDeviceConnection { /* package */ boolean open(String name, ParcelFileDescriptor pfd, @NonNull Context context) { mContext = context.getApplicationContext(); - boolean wasOpened = native_open(name, pfd.getFileDescriptor()); - if (wasOpened) { - mCloseGuard.open("close"); + synchronized (mLock) { + boolean wasOpened = native_open(name, pfd.getFileDescriptor()); + + if (wasOpened) { + mCloseGuard.open("close"); + } + + return wasOpened; } + } - return wasOpened; + /*** + * @return If this connection is currently open and usable. + */ + boolean isOpen() { + return mNativeContext != 0; } /** @@ -81,15 +93,60 @@ public class UsbDeviceConnection { } /** + * Cancel a request which relates to this connection. + * + * @return true if the request was successfully cancelled. + */ + /* package */ boolean cancelRequest(UsbRequest request) { + synchronized (mLock) { + if (!isOpen()) { + return false; + } + + return request.cancelIfOpen(); + } + } + + /** + * This is meant to be called by UsbRequest's queue() in order to synchronize on + * UsbDeviceConnection's mLock to prevent the connection being closed while queueing. + */ + /* package */ boolean queueRequest(UsbRequest request, ByteBuffer buffer, int length) { + synchronized (mLock) { + if (!isOpen()) { + return false; + } + + return request.queueIfConnectionOpen(buffer, length); + } + } + + /** + * This is meant to be called by UsbRequest's queue() in order to synchronize on + * UsbDeviceConnection's mLock to prevent the connection being closed while queueing. + */ + /* package */ boolean queueRequest(UsbRequest request, @Nullable ByteBuffer buffer) { + synchronized (mLock) { + if (!isOpen()) { + return false; + } + + return request.queueIfConnectionOpen(buffer); + } + } + + /** * Releases all system resources related to the device. * Once the object is closed it cannot be used again. * The client must call {@link UsbManager#openDevice} again * to retrieve a new instance to reestablish communication with the device. */ public void close() { - if (mNativeContext != 0) { - native_close(); - mCloseGuard.close(); + synchronized (mLock) { + if (isOpen()) { + native_close(); + mCloseGuard.close(); + } } } diff --git a/core/java/android/hardware/usb/UsbRequest.java b/core/java/android/hardware/usb/UsbRequest.java index 7abf3e9bcc7a..11c9d06cd7fe 100644 --- a/core/java/android/hardware/usb/UsbRequest.java +++ b/core/java/android/hardware/usb/UsbRequest.java @@ -112,11 +112,13 @@ public class UsbRequest { * Releases all resources related to this request. */ public void close() { - if (mNativeContext != 0) { - mEndpoint = null; - mConnection = null; - native_close(); - mCloseGuard.close(); + synchronized (mLock) { + if (mNativeContext != 0) { + mEndpoint = null; + mConnection = null; + native_close(); + mCloseGuard.close(); + } } } @@ -190,10 +192,32 @@ public class UsbRequest { */ @Deprecated public boolean queue(ByteBuffer buffer, int length) { + UsbDeviceConnection connection = mConnection; + if (connection == null) { + // The expected exception by CTS Verifier - USB Device test + throw new NullPointerException("invalid connection"); + } + + // Calling into the underlying UsbDeviceConnection to synchronize on its lock, to prevent + // the connection being closed while queueing. + return connection.queueRequest(this, buffer, length); + } + + /** + * This is meant to be called from UsbDeviceConnection after synchronizing using the lock over + * there, to prevent the connection being closed while queueing. + */ + /* package */ boolean queueIfConnectionOpen(ByteBuffer buffer, int length) { + UsbDeviceConnection connection = mConnection; + if (connection == null || !connection.isOpen()) { + // The expected exception by CTS Verifier - USB Device test + throw new NullPointerException("invalid connection"); + } + boolean out = (mEndpoint.getDirection() == UsbConstants.USB_DIR_OUT); boolean result; - if (mConnection.getContext().getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.P + if (connection.getContext().getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.P && length > MAX_USBFS_BUFFER_SIZE) { length = MAX_USBFS_BUFFER_SIZE; } @@ -242,6 +266,28 @@ public class UsbRequest { * @return true if the queueing operation succeeded */ public boolean queue(@Nullable ByteBuffer buffer) { + UsbDeviceConnection connection = mConnection; + if (connection == null) { + // The expected exception by CTS Verifier - USB Device test + throw new IllegalStateException("invalid connection"); + } + + // Calling into the underlying UsbDeviceConnection to synchronize on its lock, to prevent + // the connection being closed while queueing. + return connection.queueRequest(this, buffer); + } + + /** + * This is meant to be called from UsbDeviceConnection after synchronizing using the lock over + * there, to prevent the connection being closed while queueing. + */ + /* package */ boolean queueIfConnectionOpen(@Nullable ByteBuffer buffer) { + UsbDeviceConnection connection = mConnection; + if (connection == null || !connection.isOpen()) { + // The expected exception by CTS Verifier - USB Device test + throw new IllegalStateException("invalid connection"); + } + // Request need to be initialized Preconditions.checkState(mNativeContext != 0, "request is not initialized"); @@ -259,7 +305,7 @@ public class UsbRequest { mIsUsingNewQueue = true; wasQueued = native_queue(null, 0, 0); } else { - if (mConnection.getContext().getApplicationInfo().targetSdkVersion + if (connection.getContext().getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.P) { // Can only send/receive MAX_USBFS_BUFFER_SIZE bytes at once Preconditions.checkArgumentInRange(buffer.remaining(), 0, MAX_USBFS_BUFFER_SIZE, @@ -362,6 +408,32 @@ public class UsbRequest { * @return true if cancelling succeeded */ public boolean cancel() { + UsbDeviceConnection connection = mConnection; + if (connection == null) { + return false; + } + + return connection.cancelRequest(this); + } + + /** + * Cancels a pending queue operation (for use when the UsbDeviceConnection associated + * with this request is synchronized). This ensures we don't have a race where the + * device is closed and then the request is canceled which would lead to a + * use-after-free because the cancel operation uses the device connection + * information freed in the when UsbDeviceConnection is closed.<br/> + * + * This method assumes the connected is not closed while this method is executed. + * + * @return true if cancelling succeeded. + */ + /* package */ boolean cancelIfOpen() { + UsbDeviceConnection connection = mConnection; + if (mNativeContext == 0 || (connection != null && !connection.isOpen())) { + Log.w(TAG, + "Detected attempt to cancel a request on a connection which isn't open"); + return false; + } return native_cancel(); } diff --git a/core/java/android/service/notification/Condition.java b/core/java/android/service/notification/Condition.java index e506509bb1be..50597ec3972e 100644 --- a/core/java/android/service/notification/Condition.java +++ b/core/java/android/service/notification/Condition.java @@ -90,6 +90,12 @@ public final class Condition implements Parcelable { public final int icon; /** + * The maximum string length for any string contained in this condition. + * @hide + */ + public static final int MAX_STRING_LENGTH = 1000; + + /** * An object representing the current state of a {@link android.app.AutomaticZenRule}. * @param id the {@link android.app.AutomaticZenRule#getConditionId()} of the zen rule * @param summary a user visible description of the rule state. @@ -103,16 +109,19 @@ public final class Condition implements Parcelable { if (id == null) throw new IllegalArgumentException("id is required"); if (summary == null) throw new IllegalArgumentException("summary is required"); if (!isValidState(state)) throw new IllegalArgumentException("state is invalid: " + state); - this.id = id; - this.summary = summary; - this.line1 = line1; - this.line2 = line2; + this.id = getTrimmedUri(id); + this.summary = getTrimmedString(summary); + this.line1 = getTrimmedString(line1); + this.line2 = getTrimmedString(line2); this.icon = icon; this.state = state; this.flags = flags; } public Condition(Parcel source) { + // This constructor passes all fields directly into the constructor that takes all the + // fields as arguments; that constructor will trim each of the input strings to + // max length if necessary. this((Uri)source.readParcelable(Condition.class.getClassLoader()), source.readString(), source.readString(), @@ -239,4 +248,25 @@ public final class Condition implements Parcelable { return new Condition[size]; } }; + + /** + * Returns a truncated copy of the string if the string is longer than MAX_STRING_LENGTH. + */ + private static String getTrimmedString(String input) { + if (input != null && input.length() > MAX_STRING_LENGTH) { + return input.substring(0, MAX_STRING_LENGTH); + } + return input; + } + + /** + * Returns a truncated copy of the Uri by trimming the string representation to the maximum + * string length. + */ + private static Uri getTrimmedUri(Uri input) { + if (input != null && input.toString().length() > MAX_STRING_LENGTH) { + return Uri.parse(getTrimmedString(input.toString())); + } + return input; + } } diff --git a/core/tests/coretests/src/android/service/notification/ConditionTest.java b/core/tests/coretests/src/android/service/notification/ConditionTest.java new file mode 100644 index 000000000000..42629ba41287 --- /dev/null +++ b/core/tests/coretests/src/android/service/notification/ConditionTest.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2022 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.service.notification; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.fail; + +import android.net.Uri; +import android.os.Parcel; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import com.google.common.base.Strings; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.lang.reflect.Field; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class ConditionTest { + private static final String CLASS = "android.service.notification.Condition"; + + @Test + public void testLongFields_inConstructors() { + String longString = Strings.repeat("A", 65536); + Uri longUri = Uri.parse("uri://" + Strings.repeat("A", 65530)); + + // Confirm strings are truncated via short constructor + Condition cond1 = new Condition(longUri, longString, Condition.STATE_TRUE); + + assertEquals(Condition.MAX_STRING_LENGTH, cond1.id.toString().length()); + assertEquals(Condition.MAX_STRING_LENGTH, cond1.summary.length()); + + // Confirm strings are truncated via long constructor + Condition cond2 = new Condition(longUri, longString, longString, longString, + -1, Condition.STATE_TRUE, Condition.FLAG_RELEVANT_ALWAYS); + + assertEquals(Condition.MAX_STRING_LENGTH, cond2.id.toString().length()); + assertEquals(Condition.MAX_STRING_LENGTH, cond2.summary.length()); + assertEquals(Condition.MAX_STRING_LENGTH, cond2.line1.length()); + assertEquals(Condition.MAX_STRING_LENGTH, cond2.line2.length()); + } + + @Test + public void testLongFields_viaParcel() { + // Set fields via reflection to force them to be long, then parcel and unparcel to make sure + // it gets truncated upon unparcelling. + Condition cond = new Condition(Uri.parse("uri://placeholder"), "placeholder", + Condition.STATE_TRUE); + + try { + String longString = Strings.repeat("A", 65536); + Uri longUri = Uri.parse("uri://" + Strings.repeat("A", 65530)); + Field id = Class.forName(CLASS).getDeclaredField("id"); + id.setAccessible(true); + id.set(cond, longUri); + Field summary = Class.forName(CLASS).getDeclaredField("summary"); + summary.setAccessible(true); + summary.set(cond, longString); + Field line1 = Class.forName(CLASS).getDeclaredField("line1"); + line1.setAccessible(true); + line1.set(cond, longString); + Field line2 = Class.forName(CLASS).getDeclaredField("line2"); + line2.setAccessible(true); + line2.set(cond, longString); + } catch (NoSuchFieldException e) { + fail(e.toString()); + } catch (ClassNotFoundException e) { + fail(e.toString()); + } catch (IllegalAccessException e) { + fail(e.toString()); + } + + Parcel parcel = Parcel.obtain(); + cond.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + + Condition fromParcel = new Condition(parcel); + assertEquals(Condition.MAX_STRING_LENGTH, fromParcel.id.toString().length()); + assertEquals(Condition.MAX_STRING_LENGTH, fromParcel.summary.length()); + assertEquals(Condition.MAX_STRING_LENGTH, fromParcel.line1.length()); + assertEquals(Condition.MAX_STRING_LENGTH, fromParcel.line2.length()); + } +} diff --git a/packages/SettingsProvider/Android.bp b/packages/SettingsProvider/Android.bp index 1c97fc37cf50..825a0bd98aff 100644 --- a/packages/SettingsProvider/Android.bp +++ b/packages/SettingsProvider/Android.bp @@ -24,7 +24,10 @@ android_test { "src/com/android/providers/settings/SettingsState.java", "src/com/android/providers/settings/SettingsHelper.java", ], - static_libs: ["androidx.test.rules"], + static_libs: [ + "androidx.test.rules", + "truth-prebuilt", + ], libs: ["android.test.base"], resource_dirs: ["res"], aaptflags: [ diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java index c05c4cdf72d7..451b37391a9d 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java @@ -48,6 +48,7 @@ import android.util.Xml; import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import com.android.server.LocalServices; @@ -367,9 +368,11 @@ final class SettingsState { Setting newSetting = new Setting(name, oldSetting.getValue(), null, oldSetting.getPackageName(), oldSetting.getTag(), false, oldSetting.getId()); + int newSize = getNewMemoryUsagePerPackageLocked(newSetting.getPackageName(), 0, + oldValue, newSetting.getValue(), oldDefaultValue, newSetting.getDefaultValue()); + checkNewMemoryUsagePerPackageLocked(newSetting.getPackageName(), newSize); mSettings.put(name, newSetting); - updateMemoryUsagePerPackageLocked(newSetting.getPackageName(), oldValue, - newSetting.getValue(), oldDefaultValue, newSetting.getDefaultValue()); + updateMemoryUsagePerPackageLocked(newSetting.getPackageName(), newSize); scheduleWriteIfNeededLocked(); } } @@ -392,6 +395,13 @@ final class SettingsState { Setting oldState = mSettings.get(name); String oldValue = (oldState != null) ? oldState.value : null; String oldDefaultValue = (oldState != null) ? oldState.defaultValue : null; + String newDefaultValue = makeDefault ? value : oldDefaultValue; + + int newSize = getNewMemoryUsagePerPackageLocked(packageName, + oldValue == null ? name.length() : 0 /* deltaKeySize */, + oldValue, value, oldDefaultValue, newDefaultValue); + checkNewMemoryUsagePerPackageLocked(packageName, newSize); + Setting newState; if (oldState != null) { @@ -409,8 +419,7 @@ final class SettingsState { addHistoricalOperationLocked(HISTORICAL_OPERATION_UPDATE, newState); - updateMemoryUsagePerPackageLocked(packageName, oldValue, value, - oldDefaultValue, newState.getDefaultValue()); + updateMemoryUsagePerPackageLocked(packageName, newSize); scheduleWriteIfNeededLocked(); @@ -431,13 +440,18 @@ final class SettingsState { } Setting oldState = mSettings.remove(name); + if (oldState == null) { + return false; + } + int newSize = getNewMemoryUsagePerPackageLocked(oldState.packageName, + -name.length() /* deltaKeySize */, + oldState.value, null, oldState.defaultValue, null); StatsLog.write(StatsLog.SETTING_CHANGED, name, /* value= */ "", /* newValue= */ "", oldState.value, /* tag */ "", false, getUserIdFromKey(mKey), StatsLog.SETTING_CHANGED__REASON__DELETED); - updateMemoryUsagePerPackageLocked(oldState.packageName, oldState.value, - null, oldState.defaultValue, null); + updateMemoryUsagePerPackageLocked(oldState.packageName, newSize); addHistoricalOperationLocked(HISTORICAL_OPERATION_DELETE, oldState); @@ -454,20 +468,23 @@ final class SettingsState { } Setting setting = mSettings.get(name); + if (setting == null) { + return false; + } Setting oldSetting = new Setting(setting); String oldValue = setting.getValue(); String oldDefaultValue = setting.getDefaultValue(); + int newSize = getNewMemoryUsagePerPackageLocked(setting.packageName, 0, oldValue, + oldDefaultValue, oldDefaultValue, oldDefaultValue); + checkNewMemoryUsagePerPackageLocked(setting.packageName, newSize); + if (!setting.reset()) { return false; } - String newValue = setting.getValue(); - String newDefaultValue = setting.getDefaultValue(); - - updateMemoryUsagePerPackageLocked(setting.packageName, oldValue, - newValue, oldDefaultValue, newDefaultValue); + updateMemoryUsagePerPackageLocked(setting.packageName, newSize); addHistoricalOperationLocked(HISTORICAL_OPERATION_RESET, oldSetting); @@ -575,38 +592,49 @@ final class SettingsState { } @GuardedBy("mLock") - private void updateMemoryUsagePerPackageLocked(String packageName, String oldValue, - String newValue, String oldDefaultValue, String newDefaultValue) { - if (mMaxBytesPerAppPackage == MAX_BYTES_PER_APP_PACKAGE_UNLIMITED) { - return; - } + private boolean isExemptFromMemoryUsageCap(String packageName) { + return mMaxBytesPerAppPackage == MAX_BYTES_PER_APP_PACKAGE_UNLIMITED + || SYSTEM_PACKAGE_NAME.equals(packageName); + } - if (SYSTEM_PACKAGE_NAME.equals(packageName)) { + @GuardedBy("mLock") + private void checkNewMemoryUsagePerPackageLocked(String packageName, int newSize) + throws IllegalStateException { + if (isExemptFromMemoryUsageCap(packageName)) { return; } + if (newSize > mMaxBytesPerAppPackage) { + throw new IllegalStateException("You are adding too many system settings. " + + "You should stop using system settings for app specific data" + + " package: " + packageName); + } + } + @GuardedBy("mLock") + private int getNewMemoryUsagePerPackageLocked(String packageName, int deltaKeySize, + String oldValue, String newValue, String oldDefaultValue, String newDefaultValue) { + if (isExemptFromMemoryUsageCap(packageName)) { + return 0; + } + final Integer currentSize = mPackageToMemoryUsage.get(packageName); final int oldValueSize = (oldValue != null) ? oldValue.length() : 0; final int newValueSize = (newValue != null) ? newValue.length() : 0; final int oldDefaultValueSize = (oldDefaultValue != null) ? oldDefaultValue.length() : 0; final int newDefaultValueSize = (newDefaultValue != null) ? newDefaultValue.length() : 0; - final int deltaSize = newValueSize + newDefaultValueSize + final int deltaSize = deltaKeySize + newValueSize + newDefaultValueSize - oldValueSize - oldDefaultValueSize; + return Math.max((currentSize != null) ? currentSize + deltaSize : deltaSize, 0); + } - Integer currentSize = mPackageToMemoryUsage.get(packageName); - final int newSize = Math.max((currentSize != null) - ? currentSize + deltaSize : deltaSize, 0); - - if (newSize > mMaxBytesPerAppPackage) { - throw new IllegalStateException("You are adding too many system settings. " - + "You should stop using system settings for app specific data" - + " package: " + packageName); + @GuardedBy("mLock") + private void updateMemoryUsagePerPackageLocked(String packageName, int newSize) { + if (isExemptFromMemoryUsageCap(packageName)) { + return; } - if (DEBUG) { Slog.i(LOG_TAG, "Settings for package: " + packageName + " size: " + newSize + " bytes."); } - mPackageToMemoryUsage.put(packageName, newSize); } @@ -1220,4 +1248,11 @@ final class SettingsState { return false; } } + + @VisibleForTesting + public int getMemoryUsage(String packageName) { + synchronized (mLock) { + return mPackageToMemoryUsage.getOrDefault(packageName, 0); + } + } } diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java index 3f68554ffe87..adb356726eec 100644 --- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java +++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java @@ -21,6 +21,8 @@ import android.util.Xml; import org.xmlpull.v1.XmlSerializer; +import com.google.common.base.Strings; + import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; @@ -46,7 +48,6 @@ public class SettingsStateTest extends AndroidTestCase { "\uD800ab\uDC00 " + // broken surrogate pairs "日本語"; - public void testIsBinary() { assertFalse(SettingsState.isBinary(" abc 日本語")); @@ -182,4 +183,140 @@ public class SettingsStateTest extends AndroidTestCase { assertEquals("p2", s.getPackageName()); } } + + public void testInsertSetting_memoryUsage() { + final Object lock = new Object(); + final File file = new File(getContext().getCacheDir(), "setting.xml"); + final String settingName = "test_setting"; + + SettingsState settingsState = new SettingsState(getContext(), lock, file, 1, + SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper()); + // No exception should be thrown when there is no cap + settingsState.insertSettingLocked(settingName, Strings.repeat("A", 20001), + null, false, "p1"); + settingsState.deleteSettingLocked(settingName); + + settingsState = new SettingsState(getContext(), lock, file, 1, + SettingsState.MAX_BYTES_PER_APP_PACKAGE_LIMITED, Looper.getMainLooper()); + // System package doesn't have memory usage limit + settingsState.insertSettingLocked(settingName, Strings.repeat("A", 20001), + null, false, "android"); + settingsState.deleteSettingLocked(settingName); + + // Should not throw if usage is under the cap + settingsState.insertSettingLocked(settingName, Strings.repeat("A", 19975), + null, false, "p1"); + settingsState.deleteSettingLocked(settingName); + try { + settingsState.insertSettingLocked(settingName, Strings.repeat("A", 20001), + null, false, "p1"); + fail("Should throw because it exceeded per package memory usage"); + } catch (IllegalStateException ex) { + assertTrue(ex.getMessage().contains("p1")); + } + try { + settingsState.insertSettingLocked(settingName, Strings.repeat("A", 20001), + null, false, "p1"); + fail("Should throw because it exceeded per package memory usage"); + } catch (IllegalStateException ex) { + assertTrue(ex.getMessage().contains("p1")); + } + assertTrue(settingsState.getSettingLocked(settingName).isNull()); + try { + settingsState.insertSettingLocked(Strings.repeat("A", 20001), "", + null, false, "p1"); + fail("Should throw because it exceeded per package memory usage"); + } catch (IllegalStateException ex) { + assertTrue(ex.getMessage().contains("You are adding too many system settings")); + } + } + + public void testMemoryUsagePerPackage() { + final Object lock = new Object(); + final File file = new File(getContext().getCacheDir(), "setting.xml"); + final String testPackage = "package"; + SettingsState settingsState = new SettingsState(getContext(), lock, file, 1, + SettingsState.MAX_BYTES_PER_APP_PACKAGE_LIMITED, Looper.getMainLooper()); + + // Test inserting one key with default + final String settingName = "test_setting"; + final String testKey1 = settingName; + final String testValue1 = Strings.repeat("A", 100); + settingsState.insertSettingLocked(testKey1, testValue1, null, true, testPackage); + int expectedMemUsage = testKey1.length() + testValue1.length() + + testValue1.length() /* size for default */; + assertEquals(expectedMemUsage, settingsState.getMemoryUsage(testPackage)); + + // Test inserting another key + final String testKey2 = settingName + "2"; + settingsState.insertSettingLocked(testKey2, testValue1, null, false, testPackage); + expectedMemUsage += testKey2.length() + testValue1.length(); + assertEquals(expectedMemUsage, settingsState.getMemoryUsage(testPackage)); + + // Test updating first key with new default + final String testValue2 = Strings.repeat("A", 300); + settingsState.insertSettingLocked(testKey1, testValue2, null, true, testPackage); + expectedMemUsage += (testValue2.length() - testValue1.length()) * 2; + assertEquals(expectedMemUsage, settingsState.getMemoryUsage(testPackage)); + + // Test updating first key without new default + final String testValue3 = Strings.repeat("A", 50); + settingsState.insertSettingLocked(testKey1, testValue3, null, false, testPackage); + expectedMemUsage -= testValue2.length() - testValue3.length(); + assertEquals(expectedMemUsage, settingsState.getMemoryUsage(testPackage)); + + // Test updating second key + settingsState.insertSettingLocked(testKey2, testValue2, null, false, testPackage); + expectedMemUsage -= testValue1.length() - testValue2.length(); + assertEquals(expectedMemUsage, settingsState.getMemoryUsage(testPackage)); + + // Test resetting key + settingsState.resetSettingLocked(testKey1); + expectedMemUsage += testValue2.length() - testValue3.length(); + assertEquals(expectedMemUsage, settingsState.getMemoryUsage(testPackage)); + + // Test resetting default value + settingsState.resetSettingDefaultValueLocked(testKey1); + expectedMemUsage -= testValue2.length(); + assertEquals(expectedMemUsage, settingsState.getMemoryUsage(testPackage)); + + // Test deletion + settingsState.deleteSettingLocked(testKey2); + expectedMemUsage -= testValue2.length() + testKey2.length() /* key is deleted too */; + assertEquals(expectedMemUsage, settingsState.getMemoryUsage(testPackage)); + + // Test another package with a different key + final String testPackage2 = testPackage + "2"; + final String testKey3 = settingName + "3"; + settingsState.insertSettingLocked(testKey3, testValue1, null, true, testPackage2); + assertEquals(expectedMemUsage, settingsState.getMemoryUsage(testPackage)); + final int expectedMemUsage2 = testKey3.length() + testValue1.length() * 2; + assertEquals(expectedMemUsage2, settingsState.getMemoryUsage(testPackage2)); + + // Test system package + settingsState.insertSettingLocked(testKey1, testValue1, null, true, "android"); + assertEquals(expectedMemUsage, settingsState.getMemoryUsage(testPackage)); + assertEquals(expectedMemUsage2, settingsState.getMemoryUsage(testPackage2)); + assertEquals(0, settingsState.getMemoryUsage("android")); + + // Test invalid value + try { + settingsState.insertSettingLocked(testKey1, Strings.repeat("A", 20001), null, false, + testPackage); + fail("Should throw because it exceeded per package memory usage"); + } catch (IllegalStateException ex) { + assertTrue(ex.getMessage().contains("You are adding too many system settings")); + } + assertEquals(expectedMemUsage, settingsState.getMemoryUsage(testPackage)); + + // Test invalid key + try { + settingsState.insertSettingLocked(Strings.repeat("A", 20001), "", null, false, + testPackage); + fail("Should throw because it exceeded per package memory usage"); + } catch (IllegalStateException ex) { + assertTrue(ex.getMessage().contains("You are adding too many system settings")); + } + assertEquals(expectedMemUsage, settingsState.getMemoryUsage(testPackage)); + } } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 3ca6bf421fae..92c06d4b2d39 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -427,26 +427,28 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } UserState userState = getUserStateLocked(userId); Iterator<ComponentName> it = userState.mEnabledServices.iterator(); + boolean anyServiceRemoved = false; while (it.hasNext()) { ComponentName comp = it.next(); String compPkg = comp.getPackageName(); if (compPkg.equals(packageName)) { it.remove(); userState.mBindingServices.remove(comp); - // Update the enabled services setting. - persistComponentNamesToSettingLocked( - Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, - userState.mEnabledServices, userId); - // Update the touch exploration granted services setting. userState.mTouchExplorationGrantedServices.remove(comp); - persistComponentNamesToSettingLocked( - Settings.Secure. - TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES, - userState.mTouchExplorationGrantedServices, userId); - onUserStateChangedLocked(userState); - return; + anyServiceRemoved = true; } } + if (anyServiceRemoved) { + // Update the enabled services setting. + persistComponentNamesToSettingLocked( + Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, + userState.mEnabledServices, userId); + // Update the touch exploration granted services setting. + persistComponentNamesToSettingLocked( + Settings.Secure.TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES, + userState.mTouchExplorationGrantedServices, userId); + onUserStateChangedLocked(userState); + } } } diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java index 54504c3c1e24..44d67ed0d642 100644 --- a/services/core/java/com/android/server/am/PendingIntentRecord.java +++ b/services/core/java/com/android/server/am/PendingIntentRecord.java @@ -317,11 +317,16 @@ public final class PendingIntentRecord extends IIntentSender.Stub { resolvedType = key.requestResolvedType; } - // Apply any launch flags from the ActivityOptions. This is to ensure that the caller - // can specify a consistent launch mode even if the PendingIntent is immutable + // Apply any launch flags from the ActivityOptions. This is used only by SystemUI + // to ensure that we can launch the pending intent with a consistent launch mode even + // if the provided PendingIntent is immutable (ie. to force an activity to launch into + // a new task, or to launch multiple instances if supported by the app) final ActivityOptions opts = ActivityOptions.fromBundle(options); if (opts != null) { - finalIntent.addFlags(opts.getPendingIntentLaunchFlags()); + // TODO(b/254490217): Move this check into SafeActivityOptions + if (controller.mAtmInternal.isCallerRecents(Binder.getCallingUid())) { + finalIntent.addFlags(opts.getPendingIntentLaunchFlags()); + } } // Extract options before clearing calling identity diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 20021db28aa6..3fb750023348 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -12320,9 +12320,7 @@ public class PackageManagerService extends IPackageManager.Stub AsyncTask.execute(() -> { if (hasOldPkg) { - mPermissionManager.revokeRuntimePermissionsIfGroupChanged(pkg, oldPkg, - allPackageNames, mPermissionCallback); - mPermissionManager.revokeStoragePermissionsIfScopeExpanded(pkg, oldPkg, + mPermissionManager.onPackageUpdated(pkg, oldPkg, allPackageNames, mPermissionCallback); } if (hasPermissionDefinitionChanges) { diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java index c7fca39de562..3acf3594ca4c 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -647,6 +647,45 @@ public class PermissionManagerService { } /** + * If the package was below api 23, got the SYSTEM_ALERT_WINDOW permission automatically, and + * then updated past api 23, and the app does not satisfy any of the other SAW permission flags, + * the permission should be revoked. + * + * @param newPackage The new package that was installed + * @param oldPackage The old package that was updated + */ + private void revokeSystemAlertWindowIfUpgradedPast23( + @NonNull PackageParser.Package newPackage, + @NonNull PackageParser.Package oldPackage, + @NonNull PermissionCallback permissionCallback) { + if (oldPackage.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.M + || newPackage.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M + || !newPackage.requestedPermissions + .contains(Manifest.permission.SYSTEM_ALERT_WINDOW)) { + return; + } + + BasePermission saw; + synchronized (mLock) { + saw = mSettings.getPermissionLocked(Manifest.permission.SYSTEM_ALERT_WINDOW); + } + final PackageSetting ps = (PackageSetting) newPackage.mExtras; + if (grantSignaturePermission(Manifest.permission.SYSTEM_ALERT_WINDOW, newPackage, saw, + ps.getPermissionsState())) { + return; + } + for (int userId: mUserManagerInt.getUserIds()) { + try { + revokeRuntimePermission(Manifest.permission.SYSTEM_ALERT_WINDOW, + newPackage.packageName, false, userId, permissionCallback); + } catch (IllegalStateException | SecurityException e) { + Log.e(TAG, "unable to revoke SYSTEM_ALERT_WINDOW for " + + newPackage.packageName + " user " + userId, e); + } + } + } + + /** * We might auto-grant permissions if any permission of the group is already granted. Hence if * the group of a granted permission changes we need to revoke it to avoid having permissions of * the new group auto-granted. @@ -942,8 +981,8 @@ public class PermissionManagerService { BasePermission bp = mSettings.getPermissionLocked(info.name); added = bp == null; int fixedLevel = PermissionInfo.fixProtectionLevel(info.protectionLevel); + enforcePermissionCapLocked(info, tree); if (added) { - enforcePermissionCapLocked(info, tree); bp = new BasePermission(info.name, tree.getSourcePackageName(), BasePermission.TYPE_DYNAMIC); } else if (!bp.isDynamic()) { @@ -3170,25 +3209,22 @@ public class PermissionManagerService { } /** - * If the app is updated, and has scoped storage permissions, then it is possible that the - * app updated in an attempt to get unscoped storage. If so, revoke all storage permissions. + * If the app is updated, then some checks need to be performed to ensure the + * package is not attempting to expoit permission changes across API boundaries. * @param newPackage The new package that was installed * @param oldPackage The old package that was updated + * @param allPackageNames The current packages in the system + * @param permissionCallback Callback for permission changed */ - public void revokeStoragePermissionsIfScopeExpanded( + public void onPackageUpdated( @NonNull PackageParser.Package newPackage, @NonNull PackageParser.Package oldPackage, + @NonNull ArrayList<String> allPackageNames, @NonNull PermissionCallback permissionCallback) { PermissionManagerService.this.revokeStoragePermissionsIfScopeExpanded(newPackage, oldPackage, permissionCallback); - } - - @Override - public void revokeRuntimePermissionsIfGroupChanged( - @NonNull PackageParser.Package newPackage, - @NonNull PackageParser.Package oldPackage, - @NonNull ArrayList<String> allPackageNames, - @NonNull PermissionCallback permissionCallback) { + PermissionManagerService.this.revokeSystemAlertWindowIfUpgradedPast23(newPackage, + oldPackage, permissionCallback); PermissionManagerService.this.revokeRuntimePermissionsIfGroupChanged(newPackage, oldPackage, allPackageNames, permissionCallback); } diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java index 46fa4ffdbd45..a2f64eafe151 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java @@ -97,17 +97,15 @@ public abstract class PermissionManagerServiceInternal extends PermissionManager public abstract void updateAllPermissions(@Nullable String volumeUuid, boolean sdkUpdate, @NonNull Collection<PackageParser.Package> allPacakges, PermissionCallback callback); - /** - * We might auto-grant permissions if any permission of the group is already granted. Hence if - * the group of a granted permission changes we need to revoke it to avoid having permissions of - * the new group auto-granted. - * - * @param newPackage The new package that was installed - * @param oldPackage The old package that was updated - * @param allPackageNames All packages - * @param permissionCallback Callback for permission changed - */ - public abstract void revokeRuntimePermissionsIfGroupChanged( + /** + * If the app is updated, then some checks need to be performed to ensure the package is not + * attempting to expoit permission changes across API boundaries. + * @param newPackage The new package that was installed + * @param oldPackage The old package that was updated + * @param allPackageNames The current packages in the system + * @param permissionCallback Callback for permission changed + */ + public abstract void onPackageUpdated( @NonNull PackageParser.Package newPackage, @NonNull PackageParser.Package oldPackage, @NonNull ArrayList<String> allPackageNames, @@ -127,17 +125,6 @@ public abstract class PermissionManagerServiceInternal extends PermissionManager @NonNull PermissionCallback permissionCallback); /** - * If the app is updated, and has scoped storage permissions, then it is possible that the - * app updated in an attempt to get unscoped storage. If so, revoke all storage permissions. - * @param newPackage The new package that was installed - * @param oldPackage The old package that was updated - */ - public abstract void revokeStoragePermissionsIfScopeExpanded( - @NonNull PackageParser.Package newPackage, - @NonNull PackageParser.Package oldPackage, - @NonNull PermissionCallback permissionCallback); - - /** * Add all permissions in the given package. * <p> * NOTE: argument {@code groupTEMP} is temporary until mPermissionGroups is moved to diff --git a/services/core/java/com/android/server/wm/AppTaskImpl.java b/services/core/java/com/android/server/wm/AppTaskImpl.java index 1eb7455135c7..900b59e0a8a2 100644 --- a/services/core/java/com/android/server/wm/AppTaskImpl.java +++ b/services/core/java/com/android/server/wm/AppTaskImpl.java @@ -84,7 +84,8 @@ class AppTaskImpl extends IAppTask.Stub { if (tr == null) { throw new IllegalArgumentException("Unable to find task ID " + mTaskId); } - return mService.getRecentTasks().createRecentTaskInfo(tr); + return mService.getRecentTasks().createRecentTaskInfo(tr, + true /* getTasksAllowed */); } finally { Binder.restoreCallingIdentity(origId); } diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java index 541a8bbc8865..56367f42886d 100644 --- a/services/core/java/com/android/server/wm/RecentTasks.java +++ b/services/core/java/com/android/server/wm/RecentTasks.java @@ -944,7 +944,7 @@ class RecentTasks { continue; } - final ActivityManager.RecentTaskInfo rti = createRecentTaskInfo(tr); + final ActivityManager.RecentTaskInfo rti = createRecentTaskInfo(tr, getTasksAllowed); if (!getDetailedTasks) { rti.baseIntent.replaceExtras((Bundle) null); } @@ -1715,12 +1715,15 @@ class RecentTasks { /** * Creates a new RecentTaskInfo from a TaskRecord. */ - ActivityManager.RecentTaskInfo createRecentTaskInfo(TaskRecord tr) { + ActivityManager.RecentTaskInfo createRecentTaskInfo(TaskRecord tr, boolean getTasksAllowed) { ActivityManager.RecentTaskInfo rti = new ActivityManager.RecentTaskInfo(); tr.fillTaskInfo(rti); // Fill in some deprecated values rti.id = rti.isRunning ? rti.taskId : INVALID_TASK_ID; rti.persistentId = rti.taskId; + if (!getTasksAllowed) { + TaskRecord.trimIneffectiveInfo(tr, rti); + } return rti; } diff --git a/services/core/java/com/android/server/wm/RunningTasks.java b/services/core/java/com/android/server/wm/RunningTasks.java index 3bf437d38bcc..20cb336a3030 100644 --- a/services/core/java/com/android/server/wm/RunningTasks.java +++ b/services/core/java/com/android/server/wm/RunningTasks.java @@ -68,7 +68,7 @@ class RunningTasks { } final TaskRecord task = iter.next(); - list.add(createRunningTaskInfo(task)); + list.add(createRunningTaskInfo(task, allowed)); maxNum--; } } @@ -76,11 +76,15 @@ class RunningTasks { /** * Constructs a {@link RunningTaskInfo} from a given {@param task}. */ - private RunningTaskInfo createRunningTaskInfo(TaskRecord task) { + private RunningTaskInfo createRunningTaskInfo(TaskRecord task, boolean allowed) { final RunningTaskInfo rti = new RunningTaskInfo(); task.fillTaskInfo(rti); // Fill in some deprecated values rti.id = rti.taskId; + + if (!allowed) { + TaskRecord.trimIneffectiveInfo(task, rti); + } return rti; } } diff --git a/services/core/java/com/android/server/wm/TaskRecord.java b/services/core/java/com/android/server/wm/TaskRecord.java index 361f66e3106a..9de4c8121e4d 100644 --- a/services/core/java/com/android/server/wm/TaskRecord.java +++ b/services/core/java/com/android/server/wm/TaskRecord.java @@ -2437,6 +2437,40 @@ class TaskRecord extends ConfigurationContainer { } /** + * Removes the activity info if the activity belongs to a different uid, which is + * different from the app that hosts the task. + */ + static void trimIneffectiveInfo(TaskRecord task, TaskInfo info) { + int topActivityUid = task.effectiveUid; + for (int i = task.mActivities.size() - 1; i >= 0; --i) { + final ActivityRecord r = task.mActivities.get(i); + if (r.finishing || r.isState(ActivityState.INITIALIZING)) { + continue; + } + topActivityUid = r.info.applicationInfo.uid; + break; + } + + if (task.effectiveUid != topActivityUid) { + info.topActivity = null; + } + + int baseActivityUid = task.effectiveUid; + for (int i = 0; i < task.mActivities.size(); ++i) { + final ActivityRecord r = task.mActivities.get(i); + if (r.finishing) { + continue; + } + baseActivityUid = r.info.applicationInfo.uid; + break; + } + + if (task.effectiveUid != baseActivityUid) { + info.baseActivity = null; + } + } + + /** * Returns a {@link TaskInfo} with information from this task. */ ActivityManager.RunningTaskInfo getTaskInfo() { |