From ff2e133becd23dbefa1ea43c00e7938d7d82249f Mon Sep 17 00:00:00 2001 From: AlamIntel Date: Fri, 20 Dec 2024 12:07:27 +0530 Subject: [PATCH] ASB JAN 2025 Security Patches integration Integrating Google Android Security Bulletin Patches Test done: STS r34 TCs Passed. Tracked-On: OAM-128964 Signed-off-by: AlamIntel --- ...9-Update-security_patch_level-string.patch | 2 +- ...-when-calculating-ImageSize.bulletin.patch | 39 + ...toneManager-allow-video-ringtone-URI.patch | 79 ++ ...msFilterSettings-properties.bulletin.patch | 84 ++ .../84_0084-Fix-allowlist-token-issues.patch | 616 ++++++++++++++ ..._0085-Update-checkKeyIntent.bulletin.patch | 54 ++ ...ways-show-all-approved-apps.bulletin.patch | 201 +++++ ...caller-for-startActivityInT.bulletin.patch | 71 ++ ...6-address-in-Struct-parsing.bulletin.patch | 47 ++ ...ed-IPv6-address-from-IPv4-a.bulletin.patch | 74 ++ ...D-handling-in-continueWrite.bulletin.patch | 396 +++++++++ ...grow-rejects-large-data-pos.bulletin.patch | 41 + ...date-read-data-before-write.bulletin.patch | 59 ++ ...ckjacking-attack-in-DocsUi-.bulletin.patch | 84 ++ ...ervice-has-an-intent-filter.bulletin.patch | 147 ++++ ...e-factory-reset-in-DSU-mode.bulletin.patch | 64 ++ ...ed-incoming-HID-connections.bulletin.patch | 463 +++++++++++ ...r-SMP-authentication-bypass.bulletin.patch | 49 ++ ...ard-based-on-dstip-ifindex-.bulletin.patch | 124 +++ ...s-discard-bpf-map-key-value.bulletin.patch | 102 +++ ...rdRule-bpf-map-to-BpfNetMap.bulletin.patch | 266 ++++++ ...ssing-via-non-VPN-interface.bulletin.patch | 156 ++++ ...-discard-rule-to-legacy-VPN.bulletin.patch | 50 ++ ...ess-discard-rule-to-OEM-VPN.bulletin.patch | 55 ++ ...n-group-auto-grant-behaivor.bulletin.patch | 763 ++++++++++++++++++ ...fault-dialer-for-VisualVoic.bulletin.patch | 37 + 26 files changed, 4122 insertions(+), 1 deletion(-) create mode 100644 aosp_diff/preliminary/external/giflib/0001-Fix-potential-overflow-when-calculating-ImageSize.bulletin.patch create mode 100644 aosp_diff/preliminary/frameworks/base/82_0082-RingtoneManager-allow-video-ringtone-URI.patch create mode 100644 aosp_diff/preliminary/frameworks/base/83_0083-enforce-limits-for-VisualVoicemailSmsFilterSettings-properties.bulletin.patch create mode 100644 aosp_diff/preliminary/frameworks/base/84_0084-Fix-allowlist-token-issues.patch create mode 100644 aosp_diff/preliminary/frameworks/base/85_0085-Update-checkKeyIntent.bulletin.patch create mode 100644 aosp_diff/preliminary/frameworks/base/86_0086-Always-show-all-approved-apps.bulletin.patch create mode 100644 aosp_diff/preliminary/frameworks/base/86_0086-Pass-SafeActivityOptions-with-actual-caller-for-startActivityInT.bulletin.patch create mode 100644 aosp_diff/preliminary/frameworks/libs/net/0001-Handle-v4-mapped-v6-address-in-Struct-parsing.bulletin.patch create mode 100644 aosp_diff/preliminary/frameworks/libs/net/0002-Add-util-method-to-generate-IPv4-mapped-IPv6-address-from-IPv4-a.bulletin.patch create mode 100644 aosp_diff/preliminary/frameworks/native/08_0008-binder-fix-FD-handling-in-continueWrite.bulletin.patch create mode 100644 aosp_diff/preliminary/frameworks/native/09_0009-libbinder-Parcel-grow-rejects-large-data-pos.bulletin.patch create mode 100644 aosp_diff/preliminary/frameworks/native/10_0010-libbinder-Parcel-validate-read-data-before-write.bulletin.patch create mode 100644 aosp_diff/preliminary/packages/apps/DocumentsUI/0001-Prevent-clickjacking-attack-in-DocsUi-.bulletin.patch create mode 100644 aosp_diff/preliminary/packages/apps/Settings/21_0021--CDM-NLS-Check-if-the-NLS-service-has-an-intent-filter.bulletin.patch create mode 100644 aosp_diff/preliminary/packages/apps/Settings/22_0022-Disable-factory-reset-in-DSU-mode.bulletin.patch create mode 100644 aosp_diff/preliminary/packages/modules/Bluetooth/0018-RESTRICT-AUTOMERGE-Disallow-unexpected-incoming-HID-connections.bulletin.patch create mode 100644 aosp_diff/preliminary/packages/modules/Bluetooth/0019-Resolve-incomplete-fix-for-SMP-authentication-bypass.bulletin.patch create mode 100644 aosp_diff/preliminary/packages/modules/Connectivity/0001-netd-bpf-implement-ingress-discard-based-on-dstip-ifindex-.bulletin.patch create mode 100644 aosp_diff/preliminary/packages/modules/Connectivity/0002-Add-java-class-for-Ingress-discard-bpf-map-key-value.bulletin.patch create mode 100644 aosp_diff/preliminary/packages/modules/Connectivity/0003-Add-methods-for-updating-ingressDiscardRule-bpf-map-to-BpfNetMap.bulletin.patch create mode 100644 aosp_diff/preliminary/packages/modules/Connectivity/0004-Drop-packets-to-VPN-address-ingressing-via-non-VPN-interface.bulletin.patch create mode 100644 aosp_diff/preliminary/packages/modules/Connectivity/0005-Skip-adding-ingress-discard-rule-to-legacy-VPN.bulletin.patch create mode 100644 aosp_diff/preliminary/packages/modules/Connectivity/0006-Skip-adding-ingress-discard-rule-to-OEM-VPN.bulletin.patch create mode 100644 aosp_diff/preliminary/packages/modules/Permission/0002-Fix-Dynamic-Permission-group-auto-grant-behaivor.bulletin.patch create mode 100644 aosp_diff/preliminary/packages/services/Telephony/04_0004-enforce-the-calling-package-is-the-default-dialer-for-VisualVoic.bulletin.patch diff --git a/aosp_diff/preliminary/build/make/0009-Update-security_patch_level-string.patch b/aosp_diff/preliminary/build/make/0009-Update-security_patch_level-string.patch index a5f79ebed7..68f4cb648e 100644 --- a/aosp_diff/preliminary/build/make/0009-Update-security_patch_level-string.patch +++ b/aosp_diff/preliminary/build/make/0009-Update-security_patch_level-string.patch @@ -21,7 +21,7 @@ index dba897a9c3..a2dae42533 100644 # It must match one of the Android Security Patch Level strings of the Public Security Bulletins. # If there is no $PLATFORM_SECURITY_PATCH set, keep it empty. - PLATFORM_SECURITY_PATCH := 2024-02-05 -+ PLATFORM_SECURITY_PATCH := 2024-12-01 ++ PLATFORM_SECURITY_PATCH := 2025-01-01 endif include $(BUILD_SYSTEM)/version_util.mk diff --git a/aosp_diff/preliminary/external/giflib/0001-Fix-potential-overflow-when-calculating-ImageSize.bulletin.patch b/aosp_diff/preliminary/external/giflib/0001-Fix-potential-overflow-when-calculating-ImageSize.bulletin.patch new file mode 100644 index 0000000000..68ae8a5a7e --- /dev/null +++ b/aosp_diff/preliminary/external/giflib/0001-Fix-potential-overflow-when-calculating-ImageSize.bulletin.patch @@ -0,0 +1,39 @@ +From 3281e14f6b9b85473145ee0ae33b8d4b8fbabffe Mon Sep 17 00:00:00 2001 +From: Sadaf Ebrahimi +Date: Tue, 17 Sep 2024 21:02:42 +0000 +Subject: [PATCH] Fix potential overflow when calculating ImageSize + +The modified if statement doesn't check the size of ImageDesc.Width and +ImageDesc.Height. If ImageDesc.Width and ImageDesc.Height are larger +than SIZE_MAX, then ImageSize overflows. + +Bug: 355461643 +Test: TreeHugger +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:a6ede43ad88693f782f3a6c5b8b9b9c451151ac7) +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:da6189e11ed4ea9199a82b4b0d2bd7bed04a7efb) +Merged-In: Ieef04e789acf783eda2dff2cd9284ed204f1d117 +Change-Id: Ieef04e789acf783eda2dff2cd9284ed204f1d117 +--- + dgif_lib.c | 6 ++++-- + 1 file changed, 4 insertions(+), 2 deletions(-) + +diff --git a/dgif_lib.c b/dgif_lib.c +index 66a1d6a..7b43a6a 100644 +--- a/dgif_lib.c ++++ b/dgif_lib.c +@@ -1099,8 +1099,10 @@ DGifSlurp(GifFileType *GifFile) + + sp = &GifFile->SavedImages[GifFile->ImageCount - 1]; + /* Allocate memory for the image */ +- if (sp->ImageDesc.Width < 0 && sp->ImageDesc.Height < 0 && +- sp->ImageDesc.Width > (INT_MAX / sp->ImageDesc.Height)) { ++ if (sp->ImageDesc.Width <= 0 || ++ sp->ImageDesc.Height <= 0 || ++ sp->ImageDesc.Width > ++ (INT_MAX / sp->ImageDesc.Height)) { + return GIF_ERROR; + } + ImageSize = sp->ImageDesc.Width * sp->ImageDesc.Height; +-- +2.46.1.824.gd892dcdcdd-goog + diff --git a/aosp_diff/preliminary/frameworks/base/82_0082-RingtoneManager-allow-video-ringtone-URI.patch b/aosp_diff/preliminary/frameworks/base/82_0082-RingtoneManager-allow-video-ringtone-URI.patch new file mode 100644 index 0000000000..179132efe7 --- /dev/null +++ b/aosp_diff/preliminary/frameworks/base/82_0082-RingtoneManager-allow-video-ringtone-URI.patch @@ -0,0 +1,79 @@ +From 8a867d29099b893204eecc1d3fbaffa467e7713f Mon Sep 17 00:00:00 2001 +From: Jean-Michel Trivi +Date: Mon, 24 Jun 2024 17:29:14 -0700 +Subject: [PATCH] RingtoneManager: allow video ringtone URI + +When checking the MIME type for the default ringtone, also +allow it to refer to video content. + +Bug: 205837340 +Test: see POC + atest android.media.audio.cts.RingtoneManagerTest +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:a513c6e476844c16176e57c222b1a5ac9417cbb4) +Merged-In: Iac9f27f14bae29e0fabc31e05da2357f6f4f16c7 +Change-Id: Iac9f27f14bae29e0fabc31e05da2357f6f4f16c7 +--- + media/java/android/media/RingtoneManager.java | 8 ++++++-- + .../android/providers/settings/SettingsProvider.java | 11 +++++++---- + 2 files changed, 13 insertions(+), 6 deletions(-) + +diff --git a/media/java/android/media/RingtoneManager.java b/media/java/android/media/RingtoneManager.java +index ff369c8a5eee..11c80d3578b1 100644 +--- a/media/java/android/media/RingtoneManager.java ++++ b/media/java/android/media/RingtoneManager.java +@@ -924,9 +924,13 @@ public class RingtoneManager { + + " ignored: failure to find mimeType (no access from this context?)"); + return; + } +- if (!(mimeType.startsWith("audio/") || mimeType.equals("application/ogg"))) { ++ if (!(mimeType.startsWith("audio/") || mimeType.equals("application/ogg") ++ || mimeType.equals("application/x-flac") ++ // also check for video ringtones ++ || mimeType.startsWith("video/") || mimeType.equals("application/mp4"))) { + Log.e(TAG, "setActualDefaultRingtoneUri for URI:" + ringtoneUri +- + " ignored: associated mimeType:" + mimeType + " is not an audio type"); ++ + " ignored: associated MIME type:" + mimeType ++ + " is not a recognized audio or video type"); + return; + } + } +diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +index b785d6f7f858..44f043ee75aa 100644 +--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java ++++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +@@ -1985,7 +1985,7 @@ public class SettingsProvider extends ContentProvider { + + File cacheFile = getCacheFile(name, callingUserId); + if (cacheFile != null) { +- if (!isValidAudioUri(name, value)) { ++ if (!isValidMediaUri(name, value)) { + return false; + } + // Invalidate any relevant cache files +@@ -2046,7 +2046,7 @@ public class SettingsProvider extends ContentProvider { + return true; + } + +- private boolean isValidAudioUri(String name, String uri) { ++ private boolean isValidMediaUri(String name, String uri) { + if (uri != null) { + Uri audioUri = Uri.parse(uri); + if (Settings.AUTHORITY.equals( +@@ -2064,10 +2064,13 @@ public class SettingsProvider extends ContentProvider { + return false; + } + if (!(mimeType.startsWith("audio/") || mimeType.equals("application/ogg") +- || mimeType.equals("application/x-flac"))) { ++ || mimeType.equals("application/x-flac") ++ // also check for video ringtones ++ || mimeType.startsWith("video/") || mimeType.equals("application/mp4"))) { + Slog.e(LOG_TAG, + "mutateSystemSetting for setting: " + name + " URI: " + audioUri +- + " ignored: associated mimeType: " + mimeType + " is not an audio type"); ++ + " ignored: associated MIME type: " + mimeType ++ + " is not a recognized audio or video type"); + return false; + } + } +-- +2.46.0 + diff --git a/aosp_diff/preliminary/frameworks/base/83_0083-enforce-limits-for-VisualVoicemailSmsFilterSettings-properties.bulletin.patch b/aosp_diff/preliminary/frameworks/base/83_0083-enforce-limits-for-VisualVoicemailSmsFilterSettings-properties.bulletin.patch new file mode 100644 index 0000000000..51848ded6a --- /dev/null +++ b/aosp_diff/preliminary/frameworks/base/83_0083-enforce-limits-for-VisualVoicemailSmsFilterSettings-properties.bulletin.patch @@ -0,0 +1,84 @@ +From ea3337c88a8cb8922447065c16f464300d0b8465 Mon Sep 17 00:00:00 2001 +From: Thomas Stuart +Date: Thu, 6 Jun 2024 22:36:40 +0000 +Subject: [PATCH] enforce limits for VisualVoicemailSmsFilterSettings + properties + +- clientPrefix is now limited to 256 characters +- originatingNumbers is now limited to a list size of 100 and + each element is also limited to 256 characters + +Bug: 308932906 +Test: CTS +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:f39f15e2389d967d5dda329bd733a477b90d589c) +Merged-In: Id4b4358b141bb211a7e340b979774850b4bd2403 +Change-Id: Id4b4358b141bb211a7e340b979774850b4bd2403 +--- + .../VisualVoicemailSmsFilterSettings.java | 27 +++++++++++++++++++ + 1 file changed, 27 insertions(+) + +diff --git a/telephony/java/android/telephony/VisualVoicemailSmsFilterSettings.java b/telephony/java/android/telephony/VisualVoicemailSmsFilterSettings.java +index eadb726bf63b..2b515c9b5cd1 100644 +--- a/telephony/java/android/telephony/VisualVoicemailSmsFilterSettings.java ++++ b/telephony/java/android/telephony/VisualVoicemailSmsFilterSettings.java +@@ -64,6 +64,14 @@ public final class VisualVoicemailSmsFilterSettings implements Parcelable { + * @hide + */ + public static final int DEFAULT_DESTINATION_PORT = DESTINATION_PORT_ANY; ++ /** ++ * @hide ++ */ ++ public static final int MAX_STRING_LENGTH = 256; ++ /** ++ * @hide ++ */ ++ public static final int MAX_LIST_SIZE = 100; + + /** + * Builder class for {@link VisualVoicemailSmsFilterSettings} objects. +@@ -82,11 +90,16 @@ public final class VisualVoicemailSmsFilterSettings implements Parcelable { + /** + * Sets the client prefix for the visual voicemail SMS filter. The client prefix will appear + * at the start of a visual voicemail SMS message, followed by a colon(:). ++ * @throws IllegalArgumentException if the string length is greater than 256 characters + */ + public Builder setClientPrefix(String clientPrefix) { + if (clientPrefix == null) { + throw new IllegalArgumentException("Client prefix cannot be null"); + } ++ if (clientPrefix.length() > MAX_STRING_LENGTH) { ++ throw new IllegalArgumentException("Client prefix cannot be greater than " ++ + MAX_STRING_LENGTH + " characters"); ++ } + mClientPrefix = clientPrefix; + return this; + } +@@ -95,11 +108,25 @@ public final class VisualVoicemailSmsFilterSettings implements Parcelable { + * Sets the originating number allow list for the visual voicemail SMS filter. If the list + * is not null only the SMS messages from a number in the list can be considered as a visual + * voicemail SMS. Otherwise, messages from any address will be considered. ++ * @throws IllegalArgumentException if the size of the originatingNumbers list is greater ++ * than 100 elements ++ * @throws IllegalArgumentException if an element within the originatingNumbers list has ++ * a string length greater than 256 + */ + public Builder setOriginatingNumbers(List originatingNumbers) { + if (originatingNumbers == null) { + throw new IllegalArgumentException("Originating numbers cannot be null"); + } ++ if (originatingNumbers.size() > MAX_LIST_SIZE) { ++ throw new IllegalArgumentException("The originatingNumbers list size cannot be" ++ + " greater than " + MAX_STRING_LENGTH + " elements"); ++ } ++ for (String num : originatingNumbers) { ++ if (num != null && num.length() > MAX_STRING_LENGTH) { ++ throw new IllegalArgumentException("Numbers within the originatingNumbers list" ++ + " cannot be greater than" + MAX_STRING_LENGTH + " characters"); ++ } ++ } + mOriginatingNumbers = originatingNumbers; + return this; + } +-- +2.46.1.824.gd892dcdcdd-goog + diff --git a/aosp_diff/preliminary/frameworks/base/84_0084-Fix-allowlist-token-issues.patch b/aosp_diff/preliminary/frameworks/base/84_0084-Fix-allowlist-token-issues.patch new file mode 100644 index 0000000000..41bce544e2 --- /dev/null +++ b/aosp_diff/preliminary/frameworks/base/84_0084-Fix-allowlist-token-issues.patch @@ -0,0 +1,616 @@ +From ba9aa02a5cbeed94cd9ea08ef98906cecd2bbd89 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Mat=C3=ADas=20Hern=C3=A1ndez?= +Date: Tue, 12 Mar 2024 16:51:58 +0100 +Subject: [PATCH] Fix allowlist token issues + +1) Don't accept enqueued notifications with an unexpected token. +2) Ensure allowlist token matches for all parceled and unparceled notifications (by only using the "root notification" one). +3) Simplify cookie usage in allowlist token serialization. +4) Ensure group summary (and any notifications added directly by NMS) have the correct token. + +Bug: 328254922 +Bug: 305695605 +Test: atest NotificationManagerServiceTest ParcelTest CloseSystemDialogsTest + manually +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:390b7840ee8738377758f9750fd6d462ea55b8ee) +Merged-In: I232e9b74eece745560ed2e762071b48984b3f176 +Change-Id: I232e9b74eece745560ed2e762071b48984b3f176 +--- + core/java/android/app/Notification.java | 48 ++- + core/java/android/os/Parcel.java | 22 ++ + .../coretests/src/android/os/ParcelTest.java | 49 +++ + .../NotificationManagerService.java | 18 +- + .../NotificationManagerServiceTest.java | 324 ++++++++++++++++++ + 5 files changed, 448 insertions(+), 13 deletions(-) + +diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java +index ced35549769a..b1261ae719e6 100644 +--- a/core/java/android/app/Notification.java ++++ b/core/java/android/app/Notification.java +@@ -2598,8 +2598,11 @@ public class Notification implements Parcelable + if (mAllowlistToken == null) { + mAllowlistToken = processAllowlistToken; + } +- // Propagate this token to all pending intents that are unmarshalled from the parcel. +- parcel.setClassCookie(PendingIntent.class, mAllowlistToken); ++ // Propagate this token to all pending intents that are unmarshalled from the parcel, ++ // or keep the one we're already propagating, if that's the case. ++ if (!parcel.hasClassCookie(PendingIntent.class)) { ++ parcel.setClassCookie(PendingIntent.class, mAllowlistToken); ++ } + + when = parcel.readLong(); + creationTime = parcel.readLong(); +@@ -3061,9 +3064,24 @@ public class Notification implements Parcelable + PendingIntent.addOnMarshaledListener(addedListener); + } + try { +- // IMPORTANT: Add marshaling code in writeToParcelImpl as we +- // want to intercept all pending events written to the parcel. +- writeToParcelImpl(parcel, flags); ++ boolean mustClearCookie = false; ++ if (!parcel.hasClassCookie(Notification.class)) { ++ // This is the "root" notification, and not an "inner" notification (including ++ // publicVersion or anything else that might be embedded in extras). So we want ++ // to use its token for every inner notification (might be null). ++ parcel.setClassCookie(Notification.class, mAllowlistToken); ++ mustClearCookie = true; ++ } ++ try { ++ // IMPORTANT: Add marshaling code in writeToParcelImpl as we ++ // want to intercept all pending events written to the parcel. ++ writeToParcelImpl(parcel, flags); ++ } finally { ++ if (mustClearCookie) { ++ parcel.removeClassCookie(Notification.class, mAllowlistToken); ++ } ++ } ++ + synchronized (this) { + // Must be written last! + parcel.writeArraySet(allPendingIntents); +@@ -3078,7 +3096,10 @@ public class Notification implements Parcelable + private void writeToParcelImpl(Parcel parcel, int flags) { + parcel.writeInt(1); + +- parcel.writeStrongBinder(mAllowlistToken); ++ // Always use the same token as the root notification (might be null). ++ IBinder rootNotificationToken = (IBinder) parcel.getClassCookie(Notification.class); ++ parcel.writeStrongBinder(rootNotificationToken); ++ + parcel.writeLong(when); + parcel.writeLong(creationTime); + if (mSmallIcon == null && icon != 0) { +@@ -3471,18 +3492,23 @@ public class Notification implements Parcelable + * Sets the token used for background operations for the pending intents associated with this + * notification. + * +- * This token is automatically set during deserialization for you, you usually won't need to +- * call this unless you want to change the existing token, if any. ++ * Note: Should only be invoked by NotificationManagerService, since this is normally ++ * populated by unparceling (and also used there). Any other usage is suspect. + * + * @hide + */ +- public void clearAllowlistToken() { +- mAllowlistToken = null; ++ public void overrideAllowlistToken(IBinder token) { ++ mAllowlistToken = token; + if (publicVersion != null) { +- publicVersion.clearAllowlistToken(); ++ publicVersion.overrideAllowlistToken(token); + } + } + ++ /** @hide */ ++ public IBinder getAllowlistToken() { ++ return mAllowlistToken; ++ } ++ + /** + * @hide + */ +diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java +index e784c2669575..453aba34dbcf 100644 +--- a/core/java/android/os/Parcel.java ++++ b/core/java/android/os/Parcel.java +@@ -815,6 +815,28 @@ public final class Parcel { + return mClassCookies != null ? mClassCookies.get(clz) : null; + } + ++ /** @hide */ ++ public void removeClassCookie(Class clz, Object expectedCookie) { ++ if (mClassCookies != null) { ++ Object removedCookie = mClassCookies.remove(clz); ++ if (removedCookie != expectedCookie) { ++ Log.wtf(TAG, "Expected to remove " + expectedCookie + " (with key=" + clz ++ + ") but instead removed " + removedCookie); ++ } ++ } else { ++ Log.wtf(TAG, "Expected to remove " + expectedCookie + " (with key=" + clz ++ + ") but no cookies were present"); ++ } ++ } ++ ++ /** ++ * Whether {@link #setClassCookie} has been called with the specified {@code clz}. ++ * @hide ++ */ ++ public boolean hasClassCookie(Class clz) { ++ return mClassCookies != null && mClassCookies.containsKey(clz); ++ } ++ + /** @hide */ + public final void adoptClassCookies(Parcel from) { + mClassCookies = from.mClassCookies; +diff --git a/core/tests/coretests/src/android/os/ParcelTest.java b/core/tests/coretests/src/android/os/ParcelTest.java +index e2fe87b4cfe3..9143f74ea054 100644 +--- a/core/tests/coretests/src/android/os/ParcelTest.java ++++ b/core/tests/coretests/src/android/os/ParcelTest.java +@@ -16,18 +16,23 @@ + + package android.os; + ++import static com.google.common.truth.Truth.assertThat; ++ + import static org.junit.Assert.assertEquals; + import static org.junit.Assert.assertFalse; + import static org.junit.Assert.assertThrows; + import static org.junit.Assert.assertTrue; + + import android.platform.test.annotations.Presubmit; ++import android.util.Log; + + import androidx.test.runner.AndroidJUnit4; + + import org.junit.Test; + import org.junit.runner.RunWith; + ++import java.util.ArrayList; ++ + @Presubmit + @RunWith(AndroidJUnit4.class) + public class ParcelTest { +@@ -246,4 +251,48 @@ public class ParcelTest { + assertThrows(IllegalArgumentException.class, () -> Parcel.compareData(pA, -1, pB, iB, 0)); + assertThrows(IllegalArgumentException.class, () -> Parcel.compareData(pA, 0, pB, -1, 0)); + } ++ ++ @Test ++ public void testClassCookies() { ++ Parcel p = Parcel.obtain(); ++ assertThat(p.hasClassCookie(ParcelTest.class)).isFalse(); ++ ++ p.setClassCookie(ParcelTest.class, "string_cookie"); ++ assertThat(p.hasClassCookie(ParcelTest.class)).isTrue(); ++ assertThat(p.getClassCookie(ParcelTest.class)).isEqualTo("string_cookie"); ++ ++ p.removeClassCookie(ParcelTest.class, "string_cookie"); ++ assertThat(p.hasClassCookie(ParcelTest.class)).isFalse(); ++ assertThat(p.getClassCookie(ParcelTest.class)).isEqualTo(null); ++ ++ p.setClassCookie(ParcelTest.class, "to_be_discarded_cookie"); ++ p.recycle(); ++ assertThat(p.getClassCookie(ParcelTest.class)).isNull(); ++ } ++ ++ @Test ++ public void testClassCookies_removeUnexpected() { ++ Parcel p = Parcel.obtain(); ++ ++ assertLogsWtf(() -> p.removeClassCookie(ParcelTest.class, "not_present")); ++ ++ p.setClassCookie(ParcelTest.class, "value"); ++ ++ assertLogsWtf(() -> p.removeClassCookie(ParcelTest.class, "different")); ++ assertThat(p.getClassCookie(ParcelTest.class)).isNull(); // still removed ++ ++ p.recycle(); ++ } ++ ++ private static void assertLogsWtf(Runnable test) { ++ ArrayList wtfs = new ArrayList<>(); ++ Log.TerribleFailureHandler oldHandler = Log.setWtfHandler( ++ (tag, what, system) -> wtfs.add(what)); ++ try { ++ test.run(); ++ } finally { ++ Log.setWtfHandler(oldHandler); ++ } ++ assertThat(wtfs).hasSize(1); ++ } + } +diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java +index 0e48c3f83bbe..bb1de5eada8b 100644 +--- a/services/core/java/com/android/server/notification/NotificationManagerService.java ++++ b/services/core/java/com/android/server/notification/NotificationManagerService.java +@@ -665,7 +665,7 @@ public class NotificationManagerService extends SystemService { + + private static final int MY_UID = Process.myUid(); + private static final int MY_PID = Process.myPid(); +- private static final IBinder ALLOWLIST_TOKEN = new Binder(); ++ static final IBinder ALLOWLIST_TOKEN = new Binder(); + protected RankingHandler mRankingHandler; + private long mLastOverRateLogTime; + private float mMaxPackageEnqueueRate = DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE; +@@ -4477,7 +4477,7 @@ public class NotificationManagerService extends SystemService { + // Remove background token before returning notification to untrusted app, this + // ensures the app isn't able to perform background operations that are + // associated with notification interactions. +- notification.clearAllowlistToken(); ++ notification.overrideAllowlistToken(null); + return new StatusBarNotification( + sbn.getPackageName(), + sbn.getOpPkg(), +@@ -6736,6 +6736,15 @@ public class NotificationManagerService extends SystemService { + + " trying to post for invalid pkg " + pkg + " in user " + incomingUserId); + } + ++ IBinder allowlistToken = notification.getAllowlistToken(); ++ if (allowlistToken != null && allowlistToken != ALLOWLIST_TOKEN) { ++ throw new SecurityException( ++ "Unexpected allowlist token received from " + callingUid); ++ } ++ // allowlistToken is populated by unparceling, so it can be null if the notification was ++ // posted from inside system_server. Ensure it's the expected value. ++ notification.overrideAllowlistToken(ALLOWLIST_TOKEN); ++ + checkRestrictedCategories(notification); + + // Notifications passed to setForegroundService() have FLAG_FOREGROUND_SERVICE, +@@ -7800,6 +7809,11 @@ public class NotificationManagerService extends SystemService { + */ + private boolean enqueueNotification() { + synchronized (mNotificationLock) { ++ // allowlistToken is populated by unparceling, so it will be absent if the ++ // EnqueueNotificationRunnable is created directly by NMS (as we do for group ++ // summaries) instead of via notify(). Fix that. ++ r.getNotification().overrideAllowlistToken(ALLOWLIST_TOKEN); ++ + final long snoozeAt = + mSnoozeHelper.getSnoozeTimeForUnpostedNotification( + r.getUser().getIdentifier(), +diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +index 4deaea99f117..7858f37cdd0c 100755 +--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java ++++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +@@ -309,6 +309,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { + + private final int mUid = Binder.getCallingUid(); + private final @UserIdInt int mUserId = UserHandle.getUserId(mUid); ++ private final UserHandle mUser = UserHandle.of(mUserId); ++ private final String mPkg = mContext.getPackageName(); + + private TestableNotificationManagerService mService; + private INotificationManager mBinderService; +@@ -12356,6 +12358,328 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { + assertFalse(n.isUserInitiatedJob()); + } + ++ @Test +++ public void enqueueNotification_acceptsCorrectToken() throws RemoteException { ++ Notification sent = new Notification.Builder(mContext, TEST_CHANNEL_ID) ++ .setContentIntent(createPendingIntent("content")) ++ .build(); ++ Notification received = parcelAndUnparcel(sent, Notification.CREATOR); ++ assertThat(received.getAllowlistToken()).isEqualTo( ++ NotificationManagerService.ALLOWLIST_TOKEN); ++ ++ mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "tag", 1, ++ parcelAndUnparcel(received, Notification.CREATOR), mUserId); ++ waitForIdle(); ++ ++ assertThat(mService.mNotificationList).hasSize(1); ++ assertThat(mService.mNotificationList.get(0).getNotification().getAllowlistToken()) ++ .isEqualTo(NotificationManagerService.ALLOWLIST_TOKEN); ++ } ++ ++ @Test ++ public void enqueueNotification_acceptsNullToken_andPopulatesIt() throws RemoteException { ++ Notification receivedWithoutParceling = new Notification.Builder(mContext, TEST_CHANNEL_ID) ++ .setContentIntent(createPendingIntent("content")) ++ .build(); ++ assertThat(receivedWithoutParceling.getAllowlistToken()).isNull(); ++ ++ mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "tag", 1, ++ parcelAndUnparcel(receivedWithoutParceling, Notification.CREATOR), mUserId); ++ waitForIdle(); ++ ++ assertThat(mService.mNotificationList).hasSize(1); ++ assertThat(mService.mNotificationList.get(0).getNotification().getAllowlistToken()) ++ .isEqualTo(NotificationManagerService.ALLOWLIST_TOKEN); ++ } ++ ++ @Test ++ public void enqueueNotification_directlyThroughRunnable_populatesAllowlistToken() { ++ Notification receivedWithoutParceling = new Notification.Builder(mContext, TEST_CHANNEL_ID) ++ .setContentIntent(createPendingIntent("content")) ++ .build(); ++ NotificationRecord record = new NotificationRecord( ++ mContext, ++ new StatusBarNotification(mPkg, mPkg, 1, "tag", mUid, 44, receivedWithoutParceling, ++ mUser, "groupKey", 0), ++ mTestNotificationChannel); ++ assertThat(record.getNotification().getAllowlistToken()).isNull(); ++ ++ mWorkerHandler.post( ++ mService.new EnqueueNotificationRunnable(mUserId, record, false, ++ mPostNotificationTrackerFactory.newTracker(null))); ++ waitForIdle(); ++ ++ assertThat(mService.mNotificationList).hasSize(1); ++ assertThat(mService.mNotificationList.get(0).getNotification().getAllowlistToken()) ++ .isEqualTo(NotificationManagerService.ALLOWLIST_TOKEN); ++ } ++ ++ @Test ++ public void enqueueNotification_rejectsOtherToken() throws RemoteException { ++ Notification sent = new Notification.Builder(mContext, TEST_CHANNEL_ID) ++ .setContentIntent(createPendingIntent("content")) ++ .build(); ++ sent.overrideAllowlistToken(new Binder()); ++ Notification received = parcelAndUnparcel(sent, Notification.CREATOR); ++ assertThat(received.getAllowlistToken()).isEqualTo(sent.getAllowlistToken()); ++ assertThrows(SecurityException.class, () -> ++ mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "tag", 1, ++ parcelAndUnparcel(received, Notification.CREATOR), mUserId)); ++ waitForIdle(); ++ ++ assertThat(mService.mNotificationList).isEmpty(); ++ } ++ ++ @Test ++ public void enqueueNotification_customParcelingWithFakeInnerToken_hasCorrectTokenInIntents() ++ throws RemoteException { ++ Notification sentFromApp = new Notification.Builder(mContext, TEST_CHANNEL_ID) ++ .setContentIntent(createPendingIntent("content")) ++ .setPublicVersion(new Notification.Builder(mContext, TEST_CHANNEL_ID) ++ .setContentIntent(createPendingIntent("public")) ++ .build()) ++ .build(); ++ sentFromApp.publicVersion.overrideAllowlistToken(new Binder()); ++ ++ // Instead of using the normal parceling, assume the caller parcels it by hand, including a ++ // null token in the outer notification (as would be expected, and as is verified by ++ // enqueue) but trying to sneak in a different one in the inner notification, hoping it gets ++ // propagated to the PendingIntents. ++ Parcel parcelSentFromApp = Parcel.obtain(); ++ writeNotificationToParcelCustom(parcelSentFromApp, sentFromApp, new ArraySet<>( ++ Lists.newArrayList(sentFromApp.contentIntent, ++ sentFromApp.publicVersion.contentIntent))); ++ ++ // Use the unparceling as received in enqueueNotificationWithTag() ++ parcelSentFromApp.setDataPosition(0); ++ Notification receivedByNms = new Notification(parcelSentFromApp); ++ ++ // Verify that all the pendingIntents have the correct token. ++ assertThat(receivedByNms.contentIntent.getWhitelistToken()).isEqualTo( ++ NotificationManagerService.ALLOWLIST_TOKEN); ++ assertThat(receivedByNms.publicVersion.contentIntent.getWhitelistToken()).isEqualTo( ++ NotificationManagerService.ALLOWLIST_TOKEN); ++ } ++ ++ /** ++ * Replicates the behavior of {@link Notification#writeToParcel} but excluding the ++ * "always use the same allowlist token as the root notification" parts. ++ */ ++ private static void writeNotificationToParcelCustom(Parcel parcel, Notification notif, ++ ArraySet allPendingIntents) { ++ int flags = 0; ++ parcel.writeInt(1); // version? ++ ++ parcel.writeStrongBinder(notif.getAllowlistToken()); ++ parcel.writeLong(notif.when); ++ parcel.writeLong(1234L); // notif.creationTime is private ++ if (notif.getSmallIcon() != null) { ++ parcel.writeInt(1); ++ notif.getSmallIcon().writeToParcel(parcel, 0); ++ } else { ++ parcel.writeInt(0); ++ } ++ parcel.writeInt(notif.number); ++ if (notif.contentIntent != null) { ++ parcel.writeInt(1); ++ notif.contentIntent.writeToParcel(parcel, 0); ++ } else { ++ parcel.writeInt(0); ++ } ++ if (notif.deleteIntent != null) { ++ parcel.writeInt(1); ++ notif.deleteIntent.writeToParcel(parcel, 0); ++ } else { ++ parcel.writeInt(0); ++ } ++ if (notif.tickerText != null) { ++ parcel.writeInt(1); ++ TextUtils.writeToParcel(notif.tickerText, parcel, flags); ++ } else { ++ parcel.writeInt(0); ++ } ++ if (notif.tickerView != null) { ++ parcel.writeInt(1); ++ notif.tickerView.writeToParcel(parcel, 0); ++ } else { ++ parcel.writeInt(0); ++ } ++ if (notif.contentView != null) { ++ parcel.writeInt(1); ++ notif.contentView.writeToParcel(parcel, 0); ++ } else { ++ parcel.writeInt(0); ++ } ++ if (notif.getLargeIcon() != null) { ++ parcel.writeInt(1); ++ notif.getLargeIcon().writeToParcel(parcel, 0); ++ } else { ++ parcel.writeInt(0); ++ } ++ ++ parcel.writeInt(notif.defaults); ++ parcel.writeInt(notif.flags); ++ ++ if (notif.sound != null) { ++ parcel.writeInt(1); ++ notif.sound.writeToParcel(parcel, 0); ++ } else { ++ parcel.writeInt(0); ++ } ++ parcel.writeInt(notif.audioStreamType); ++ ++ if (notif.audioAttributes != null) { ++ parcel.writeInt(1); ++ notif.audioAttributes.writeToParcel(parcel, 0); ++ } else { ++ parcel.writeInt(0); ++ } ++ ++ parcel.writeLongArray(notif.vibrate); ++ parcel.writeInt(notif.ledARGB); ++ parcel.writeInt(notif.ledOnMS); ++ parcel.writeInt(notif.ledOffMS); ++ parcel.writeInt(notif.iconLeve); ++ if (notif.fullScreenIntent != null) { ++ parcel.writeInt(1); ++ notif.fullScreenIntent.writeToParcel(parcel, 0); ++ } else { ++ parcel.writeInt(0); ++ } ++ ++ parcel.writeInt(notif.priority); ++ ++ parcel.writeString8(notif.category); ++ ++ parcel.writeString8(notif.getGroup()); ++ ++ parcel.writeString8(notif.getSortKey()); ++ ++ parcel.writeBundle(notif.extras); // null ok ++ parcel.writeTypedArray(notif.actions, 0); // null ok ++ ++ if (notif.bigContentView != null) { ++ parcel.writeInt(1); ++ notif.bigContentView.writeToParcel(parcel, 0); ++ } else { ++ parcel.writeInt(0); ++ } ++ ++ if (notif.headsUpContentView != null) { ++ parcel.writeInt(1); ++ notif.headsUpContentView.writeToParcel(parcel, 0); ++ } else { ++ parcel.writeInt(0); ++ } ++ ++ parcel.writeInt(notif.visibility); ++ ++ if (notif.publicVersion != null) { ++ parcel.writeInt(1); ++ writeNotificationToParcelCustom(parcel, notif.publicVersion, new ArraySet<>()); ++ } else { ++ parcel.writeInt(0); ++ } ++ ++ parcel.writeInt(notif.color); ++ ++ if (notif.getChannelId() != null) { ++ parcel.writeInt(1); ++ parcel.writeString8(notif.getChannelId()); ++ } else { ++ parcel.writeInt(0); ++ } ++ parcel.writeLong(notif.getTimeoutAfter()); ++ ++ if (notif.getShortcutId() != null) { ++ parcel.writeInt(1); ++ parcel.writeString8(notif.getShortcutId()); ++ } else { ++ parcel.writeInt(0); ++ } ++ ++ if (notif.getLocusId() != null) { ++ parcel.writeInt(1); ++ notif.getLocusId().writeToParcel(parcel, 0); ++ } else { ++ parcel.writeInt(0); ++ } ++ ++ parcel.writeInt(notif.getBadgeIconType()); ++ ++ if (notif.getSettingsText() != null) { ++ parcel.writeInt(1); ++ TextUtils.writeToParcel(notif.getSettingsText(), parcel, flags); ++ } else { ++ parcel.writeInt(0); ++ } ++ ++ parcel.writeInt(notif.getGroupAlertBehavior()); ++ ++ if (notif.getBubbleMetadata() != null) { ++ parcel.writeInt(1); ++ notif.getBubbleMetadata().writeToParcel(parcel, 0); ++ } else { ++ parcel.writeInt(0); ++ } ++ ++ parcel.writeBoolean(notif.getAllowSystemGeneratedContextualActions()); ++ ++ parcel.writeInt(Notification.FOREGROUND_SERVICE_DEFAULT); // no getter for mFgsDeferBehavior ++ ++ // mUsesStandardHeader is not written because it should be recomputed in listeners ++ ++ parcel.writeArraySet(allPendingIntents); ++ } ++ ++ @Test ++ @SuppressWarnings("unchecked") ++ public void getActiveNotifications_doesNotLeakAllowlistToken() throws RemoteException { ++ Notification sentFromApp = new Notification.Builder(mContext, TEST_CHANNEL_ID) ++ .setContentIntent(createPendingIntent("content")) ++ .setPublicVersion(new Notification.Builder(mContext, TEST_CHANNEL_ID) ++ .setContentIntent(createPendingIntent("public")) ++ .build()) ++ .extend(new Notification.WearableExtender() ++ .addPage(new Notification.Builder(mContext, TEST_CHANNEL_ID) ++ .setContentIntent(createPendingIntent("wearPage")) ++ .build())) ++ .build(); ++ // Binder transition: app -> NMS ++ Notification receivedByNms = parcelAndUnparcel(sentFromApp, Notification.CREATOR); ++ assertThat(receivedByNms.getAllowlistToken()).isEqualTo( ++ NotificationManagerService.ALLOWLIST_TOKEN); ++ mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "tag", 1, ++ parcelAndUnparcel(receivedByNms, Notification.CREATOR), mUserId); ++ waitForIdle(); ++ assertThat(mService.mNotificationList).hasSize(1); ++ Notification posted = mService.mNotificationList.get(0).getNotification(); ++ assertThat(posted.getAllowlistToken()).isEqualTo( ++ NotificationManagerService.ALLOWLIST_TOKEN); ++ assertThat(posted.contentIntent.getWhitelistToken()).isEqualTo( ++ NotificationManagerService.ALLOWLIST_TOKEN); ++ ++ ParceledListSlice listSentFromNms = ++ mBinderService.getAppActiveNotifications(mPkg, mUserId); ++ // Binder transition: NMS -> app. App doesn't have the allowlist token so clear it ++ // (having a different one would produce the same effect; the relevant thing is to not let ++ // out ALLOWLIST_TOKEN). ++ // Note: for other tests, this is restored by constructing TestableNMS in setup(). ++ Notification.processAllowlistToken = null; ++ ParceledListSlice listReceivedByApp = parcelAndUnparcel( ++ listSentFromNms, ParceledListSlice.CREATOR); ++ Notification gottenBackByApp = listReceivedByApp.getList().get(0).getNotification(); ++ ++ assertThat(gottenBackByApp.getAllowlistToken()).isNull(); ++ assertThat(gottenBackByApp.contentIntent.getWhitelistToken()).isNull(); ++ assertThat(gottenBackByApp.publicVersion.getAllowlistToken()).isNull(); ++ assertThat(gottenBackByApp.publicVersion.contentIntent.getWhitelistToken()).isNull(); ++ assertThat(new Notification.WearableExtender(gottenBackByApp).getPages() ++ .get(0).getAllowlistToken()).isNull(); ++ assertThat(new Notification.WearableExtender(gottenBackByApp).getPages() ++ .get(0).contentIntent.getWhitelistToken()).isNull(); ++ } ++ + @Test + public void enqueue_updatesEnqueueRate() throws Exception { + Notification n = generateNotificationRecord(null).getNotification(); +-- +2.46.0 + diff --git a/aosp_diff/preliminary/frameworks/base/85_0085-Update-checkKeyIntent.bulletin.patch b/aosp_diff/preliminary/frameworks/base/85_0085-Update-checkKeyIntent.bulletin.patch new file mode 100644 index 0000000000..48f19e195f --- /dev/null +++ b/aosp_diff/preliminary/frameworks/base/85_0085-Update-checkKeyIntent.bulletin.patch @@ -0,0 +1,54 @@ +From 985bdc676ac5ea4f35be4b56b74f723afe5b2af3 Mon Sep 17 00:00:00 2001 +From: Dmitry Dementyev +Date: Tue, 1 Oct 2024 14:57:44 -0700 +Subject: [PATCH] Update checkKeyIntent + +1) Explicityly set component after target activity check. +2) Update Intent subclass check. + +Bug: 360846772 +Test: manual +Flag: EXEMPT bugfix +(cherry picked from commit cde345a7ee06db716e613e12a2c218ce248ad1c4) +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:58f3d5b2226cc9c6dc9fcca1427eb6574dcc0eb8) +Merged-In: Ied7961c73299681aa5b523cf3f00fd905893116f +Change-Id: Ied7961c73299681aa5b523cf3f00fd905893116f +--- + .../android/server/accounts/AccountManagerService.java | 9 ++++++--- + 1 file changed, 6 insertions(+), 3 deletions(-) + +diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java +index 6179b1533475..71ed5dbe8ab6 100644 +--- a/services/core/java/com/android/server/accounts/AccountManagerService.java ++++ b/services/core/java/com/android/server/accounts/AccountManagerService.java +@@ -4978,6 +4978,8 @@ public class AccountManagerService + Log.e(TAG, String.format(tmpl, activityName, pkgName, mAccountType)); + return false; + } ++ intent.setComponent(targetActivityInfo.getComponentName()); ++ bundle.putParcelable(AccountManager.KEY_INTENT, intent); + return true; + } finally { + Binder.restoreCallingIdentity(bid); +@@ -4999,14 +5001,15 @@ public class AccountManagerService + Bundle simulateBundle = p.readBundle(); + p.recycle(); + Intent intent = bundle.getParcelable(AccountManager.KEY_INTENT, Intent.class); +- if (intent != null && intent.getClass() != Intent.class) { +- return false; +- } + Intent simulateIntent = simulateBundle.getParcelable(AccountManager.KEY_INTENT, + Intent.class); + if (intent == null) { + return (simulateIntent == null); + } ++ if (intent.getClass() != Intent.class || simulateIntent.getClass() != Intent.class) { ++ return false; ++ } ++ + if (!intent.filterEquals(simulateIntent)) { + return false; + } +-- +2.46.1.824.gd892dcdcdd-goog + diff --git a/aosp_diff/preliminary/frameworks/base/86_0086-Always-show-all-approved-apps.bulletin.patch b/aosp_diff/preliminary/frameworks/base/86_0086-Always-show-all-approved-apps.bulletin.patch new file mode 100644 index 0000000000..126091acd1 --- /dev/null +++ b/aosp_diff/preliminary/frameworks/base/86_0086-Always-show-all-approved-apps.bulletin.patch @@ -0,0 +1,201 @@ +From 8516dfb89f99fd119fa12045e5c88633ce7478b8 Mon Sep 17 00:00:00 2001 +From: Julia Reynolds +Date: Mon, 7 Oct 2024 11:10:31 -0400 +Subject: [PATCH] Always show all approved apps + +Regardless of what the current criteria is in order to be approved, +show everything that's currently approved, since the criteria might +have been more lax when it was approved + +Test: manual +Test: ServiceListingTest +Flag: EXEMPT bug fix +Bug: 365738306 +(cherry picked from commit 234c5e843ca427b1dd47e91e3969f3309dd787bf) +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:63af7d0f3ad9546734c4aeb6163dcecb0c79a5c3) +Merged-In: I6c19d3dbff6ecabc74729a7f021f293e26601944 +Change-Id: I6c19d3dbff6ecabc74729a7f021f293e26601944 +--- + .../applications/ServiceListing.java | 32 ++++++--- + .../applications/ServiceListingTest.java | 66 ++++++++++++++++++- + 2 files changed, 88 insertions(+), 10 deletions(-) + +diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ServiceListing.java b/packages/SettingsLib/src/com/android/settingslib/applications/ServiceListing.java +index c8bcabff1094..261c722e517c 100644 +--- a/packages/SettingsLib/src/com/android/settingslib/applications/ServiceListing.java ++++ b/packages/SettingsLib/src/com/android/settingslib/applications/ServiceListing.java +@@ -138,23 +138,37 @@ public class ServiceListing { + } + + final PackageManager pmWrapper = mContext.getPackageManager(); ++ // Add requesting apps, with full validation + List installedServices = pmWrapper.queryIntentServicesAsUser( + new Intent(mIntentAction), flags, user); + for (ResolveInfo resolveInfo : installedServices) { + ServiceInfo info = resolveInfo.serviceInfo; + +- if (!mPermission.equals(info.permission)) { +- Slog.w(mTag, "Skipping " + mNoun + " service " +- + info.packageName + "/" + info.name +- + ": it does not require the permission " +- + mPermission); +- continue; ++ if (!mEnabledServices.contains(info.getComponentName())) { ++ if (!mPermission.equals(info.permission)) { ++ Slog.w(mTag, "Skipping " + mNoun + " service " ++ + info.packageName + "/" + info.name ++ + ": it does not require the permission " ++ + mPermission); ++ continue; ++ } ++ if (mValidator != null && !mValidator.test(info)) { ++ continue; ++ } ++ mServices.add(info); + } +- if (mValidator != null && !mValidator.test(info)) { +- continue; ++ } ++ ++ // Add all apps with access, in case prior approval was granted without full validation ++ for (ComponentName cn : mEnabledServices) { ++ List enabledServices = pmWrapper.queryIntentServicesAsUser( ++ new Intent().setComponent(cn), flags, user); ++ for (ResolveInfo resolveInfo : enabledServices) { ++ ServiceInfo info = resolveInfo.serviceInfo; ++ mServices.add(info); + } +- mServices.add(info); + } ++ + for (Callback callback : mCallbacks) { + callback.onServicesReloaded(mServices); + } +diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ServiceListingTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ServiceListingTest.java +index 7ff0988c494d..feef559dfe26 100644 +--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ServiceListingTest.java ++++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ServiceListingTest.java +@@ -21,6 +21,7 @@ import static com.google.common.truth.Truth.assertThat; + import static org.mockito.ArgumentMatchers.any; + import static org.mockito.ArgumentMatchers.anyInt; + import static org.mockito.ArgumentMatchers.anyList; ++import static org.mockito.ArgumentMatchers.argThat; + import static org.mockito.Mockito.mock; + import static org.mockito.Mockito.spy; + import static org.mockito.Mockito.times; +@@ -29,6 +30,7 @@ import static org.mockito.Mockito.when; + + import android.content.ComponentName; + import android.content.Context; ++import android.content.Intent; + import android.content.pm.PackageManager; + import android.content.pm.ResolveInfo; + import android.content.pm.ServiceInfo; +@@ -42,6 +44,7 @@ import org.junit.Before; + import org.junit.Test; + import org.junit.runner.RunWith; + import org.mockito.ArgumentCaptor; ++import org.mockito.ArgumentMatcher; + import org.robolectric.RobolectricTestRunner; + import org.robolectric.RuntimeEnvironment; + +@@ -72,19 +75,26 @@ public class ServiceListingTest { + .build(); + } + ++ private ArgumentMatcher filterEquals(Intent intent) { ++ return (test) -> { ++ return intent.filterEquals(test); ++ }; ++ } ++ + @Test + public void testValidator() { + ServiceInfo s1 = new ServiceInfo(); + s1.permission = "testPermission"; + s1.packageName = "pkg"; ++ s1.name = "Service1"; + ServiceInfo s2 = new ServiceInfo(); + s2.permission = "testPermission"; + s2.packageName = "pkg2"; ++ s2.name = "service2"; + ResolveInfo r1 = new ResolveInfo(); + r1.serviceInfo = s1; + ResolveInfo r2 = new ResolveInfo(); + r2.serviceInfo = s2; +- + when(mPm.queryIntentServicesAsUser(any(), anyInt(), anyInt())).thenReturn( + ImmutableList.of(r1, r2)); + +@@ -118,9 +128,11 @@ public class ServiceListingTest { + ServiceInfo s1 = new ServiceInfo(); + s1.permission = "testPermission"; + s1.packageName = "pkg"; ++ s1.name = "Service1"; + ServiceInfo s2 = new ServiceInfo(); + s2.permission = "testPermission"; + s2.packageName = "pkg2"; ++ s2.name = "service2"; + ResolveInfo r1 = new ResolveInfo(); + r1.serviceInfo = s1; + ResolveInfo r2 = new ResolveInfo(); +@@ -193,4 +205,56 @@ public class ServiceListingTest { + assertThat(Settings.Secure.getString(RuntimeEnvironment.application.getContentResolver(), + TEST_SETTING)).contains(testComponent2.flattenToString()); + } ++ ++ @Test ++ public void testHasPermissionWithoutMeetingCurrentRegs() { ++ ServiceInfo s1 = new ServiceInfo(); ++ s1.permission = "testPermission"; ++ s1.packageName = "pkg"; ++ s1.name = "Service1"; ++ ServiceInfo s2 = new ServiceInfo(); ++ s2.permission = "testPermission"; ++ s2.packageName = "pkg2"; ++ s2.name = "service2"; ++ ResolveInfo r1 = new ResolveInfo(); ++ r1.serviceInfo = s1; ++ ResolveInfo r2 = new ResolveInfo(); ++ r2.serviceInfo = s2; ++ ++ ComponentName approvedComponent = new ComponentName(s2.packageName, s2.name); ++ ++ Settings.Secure.putString( ++ mContext.getContentResolver(), TEST_SETTING, approvedComponent.flattenToString()); ++ ++ when(mPm.queryIntentServicesAsUser(argThat( ++ filterEquals(new Intent(TEST_INTENT))), anyInt(), anyInt())) ++ .thenReturn(ImmutableList.of(r1)); ++ when(mPm.queryIntentServicesAsUser(argThat( ++ filterEquals(new Intent().setComponent(approvedComponent))), ++ anyInt(), anyInt())) ++ .thenReturn(ImmutableList.of(r2)); ++ ++ mServiceListing = new ServiceListing.Builder(mContext) ++ .setTag("testTag") ++ .setSetting(TEST_SETTING) ++ .setNoun("testNoun") ++ .setIntentAction(TEST_INTENT) ++ .setValidator(info -> { ++ if (info.packageName.equals("pkg")) { ++ return true; ++ } ++ return false; ++ }) ++ .setPermission("testPermission") ++ .build(); ++ ServiceListing.Callback callback = mock(ServiceListing.Callback.class); ++ mServiceListing.addCallback(callback); ++ mServiceListing.reload(); ++ ++ verify(mPm, times(2)).queryIntentServicesAsUser(any(), anyInt(), anyInt()); ++ ArgumentCaptor> captor = ArgumentCaptor.forClass(List.class); ++ verify(callback, times(1)).onServicesReloaded(captor.capture()); ++ ++ assertThat(captor.getValue()).containsExactlyElementsIn(ImmutableList.of(s2, s1)); ++ } + } +-- +2.46.1.824.gd892dcdcdd-goog + diff --git a/aosp_diff/preliminary/frameworks/base/86_0086-Pass-SafeActivityOptions-with-actual-caller-for-startActivityInT.bulletin.patch b/aosp_diff/preliminary/frameworks/base/86_0086-Pass-SafeActivityOptions-with-actual-caller-for-startActivityInT.bulletin.patch new file mode 100644 index 0000000000..e30ff14e4c --- /dev/null +++ b/aosp_diff/preliminary/frameworks/base/86_0086-Pass-SafeActivityOptions-with-actual-caller-for-startActivityInT.bulletin.patch @@ -0,0 +1,71 @@ +From 5eb2aa58018c3e3b42c4574e76e9aa845ab31bff Mon Sep 17 00:00:00 2001 +From: Chris Li +Date: Wed, 9 Oct 2024 01:50:57 +0000 +Subject: [PATCH] Pass SafeActivityOptions with actual caller for + startActivityInTF + +We clearCallingUid before apply the WCT, but SafeActivityOptions will +query the Binder Uid when construct. Update to pass in the actual +caller. + +Flag: EXEMPT bug fix +Bug: 369103643 +Test: atest WmTests:WindowOrganizerTests# + testStartActivityInTaskFragment_checkCallerPermission +(cherry picked from commit 20c568e77eae5d469cd5e594b644d8645d830dbd) +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:b9fcaac0c8ce28a6a5aa8ddd10cbb4e3d91e9734) +Merged-In: I873ae576de0bc4a7402c2f522b45853bce48a0c5 +Change-Id: I873ae576de0bc4a7402c2f522b45853bce48a0c5 +--- + .../java/com/android/server/wm/ActivityStartController.java | 5 ++--- + .../com/android/server/wm/WindowOrganizerController.java | 4 +++- + 2 files changed, 5 insertions(+), 4 deletions(-) + +diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java +index a6e50405e7d9..4de02f7f812f 100644 +--- a/services/core/java/com/android/server/wm/ActivityStartController.java ++++ b/services/core/java/com/android/server/wm/ActivityStartController.java +@@ -43,7 +43,6 @@ import android.content.pm.ApplicationInfo; + import android.content.pm.PackageManager; + import android.content.pm.ResolveInfo; + import android.os.Binder; +-import android.os.Bundle; + import android.os.IBinder; + import android.os.Trace; + import android.os.UserHandle; +@@ -529,14 +528,14 @@ public class ActivityStartController { + * Starts an activity in the TaskFragment. + * @param taskFragment TaskFragment {@link TaskFragment} to start the activity in. + * @param activityIntent intent to start the activity. +- * @param activityOptions ActivityOptions to start the activity with. ++ * @param activityOptions SafeActivityOptions to start the activity with. + * @param resultTo the caller activity + * @param callingUid the caller uid + * @param callingPid the caller pid + * @return the start result. + */ + int startActivityInTaskFragment(@NonNull TaskFragment taskFragment, +- @NonNull Intent activityIntent, @Nullable Bundle activityOptions, ++ @NonNull Intent activityIntent, @Nullable SafeActivityOptions activityOptions, + @Nullable IBinder resultTo, int callingUid, int callingPid, + @Nullable IBinder errorCallbackToken) { + final ActivityRecord caller = +diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java +index 027ab97693fd..7b59d6fbd820 100644 +--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java ++++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java +@@ -1189,8 +1189,10 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub + final IBinder callerActivityToken = operation.getActivityToken(); + final Intent activityIntent = operation.getActivityIntent(); + final Bundle activityOptions = operation.getBundle(); ++ final SafeActivityOptions safeOptions = ++ SafeActivityOptions.fromBundle(activityOptions, caller.mPid, caller.mUid); + final int result = mService.getActivityStartController() +- .startActivityInTaskFragment(taskFragment, activityIntent, activityOptions, ++ .startActivityInTaskFragment(taskFragment, activityIntent, safeOptions, + callerActivityToken, caller.mUid, caller.mPid, + errorCallbackToken); + if (!isStartResultSuccessful(result)) { +-- +2.46.1.824.gd892dcdcdd-goog + diff --git a/aosp_diff/preliminary/frameworks/libs/net/0001-Handle-v4-mapped-v6-address-in-Struct-parsing.bulletin.patch b/aosp_diff/preliminary/frameworks/libs/net/0001-Handle-v4-mapped-v6-address-in-Struct-parsing.bulletin.patch new file mode 100644 index 0000000000..38b4556ffc --- /dev/null +++ b/aosp_diff/preliminary/frameworks/libs/net/0001-Handle-v4-mapped-v6-address-in-Struct-parsing.bulletin.patch @@ -0,0 +1,47 @@ +From e72c61380c52a4450970556e5936c5ec03fd66fb Mon Sep 17 00:00:00 2001 +From: Motomu Utsumi +Date: Wed, 31 Jul 2024 18:13:04 +0900 +Subject: [PATCH] Handle v4-mapped v6 address in Struct parsing + +Cherry-pick of aosp/2807798 to backport VPN security fix to non-mainline +U devices. +This CL removes test changes since u branches use module prebuilt by +default. +Having Merged-In to prevent automerger to udc-mainlie-prod where +frameworks/libs/net does not exist. + +testV4MappedV6Address fails without change in Struct.java + +Bug: 193031925 +Test: TH +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:80500a3de46b212c7ed0e9f9f9c87f698b101c17) +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:338282075d51ef6402ddde764db12b6bafebc0c4) +Merged-In: I2d0796b6deef1a2d9979f16ccc4afe9a04f028dc +Change-Id: I2d0796b6deef1a2d9979f16ccc4afe9a04f028dc +--- + common/device/com/android/net/module/util/Struct.java | 9 ++++++++- + 1 file changed, 8 insertions(+), 1 deletion(-) + +diff --git a/common/device/com/android/net/module/util/Struct.java b/common/device/com/android/net/module/util/Struct.java +index b638a46..9ae9abf 100644 +--- a/common/device/com/android/net/module/util/Struct.java ++++ b/common/device/com/android/net/module/util/Struct.java +@@ -414,7 +414,14 @@ public class Struct { + final byte[] address = new byte[isIpv6 ? 16 : 4]; + buf.get(address); + try { +- value = InetAddress.getByAddress(address); ++ if (isIpv6) { ++ // Using Inet6Address.getByAddress since InetAddress.getByAddress converts ++ // v4-mapped v6 address to v4 address internally and returns Inet4Address. ++ value = Inet6Address.getByAddress( ++ null /* host */, address, -1 /* scope_id */); ++ } else { ++ value = InetAddress.getByAddress(address); ++ } + } catch (UnknownHostException e) { + throw new IllegalArgumentException("illegal length of IP address", e); + } +-- +2.46.1.824.gd892dcdcdd-goog + diff --git a/aosp_diff/preliminary/frameworks/libs/net/0002-Add-util-method-to-generate-IPv4-mapped-IPv6-address-from-IPv4-a.bulletin.patch b/aosp_diff/preliminary/frameworks/libs/net/0002-Add-util-method-to-generate-IPv4-mapped-IPv6-address-from-IPv4-a.bulletin.patch new file mode 100644 index 0000000000..495643fd00 --- /dev/null +++ b/aosp_diff/preliminary/frameworks/libs/net/0002-Add-util-method-to-generate-IPv4-mapped-IPv6-address-from-IPv4-a.bulletin.patch @@ -0,0 +1,74 @@ +From b20d67cbfc24a54dfe1dc9854bac61fdf52f7913 Mon Sep 17 00:00:00 2001 +From: Motomu Utsumi +Date: Wed, 31 Jul 2024 18:41:33 +0900 +Subject: [PATCH] Add util method to generate IPv4-mapped IPv6 address from + IPv4 address + +Cherry-pick of aosp/2795709 to backport VPN security fix to non-mainline +devices. +Having Merged-In to prevent automerger to udc-mainlie-prod where +frameworks/libs/net does not exist. + +Bug: 193031925 +Test: TH +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:03eb6f09360de988687c06940b6f13be7e650b34) +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:29e53bd862d7b4c30552f188065e1239e84c401e) +Merged-In: Iaa5d5f31904f8a122cb96f2c3f0e3499a2577664 +Change-Id: Iaa5d5f31904f8a122cb96f2c3f0e3499a2577664 +--- + .../net/module/util/InetAddressUtils.java | 26 +++++++++++++++++++ + 1 file changed, 26 insertions(+) + +diff --git a/common/framework/com/android/net/module/util/InetAddressUtils.java b/common/framework/com/android/net/module/util/InetAddressUtils.java +index 40fc59f..4b27a97 100644 +--- a/common/framework/com/android/net/module/util/InetAddressUtils.java ++++ b/common/framework/com/android/net/module/util/InetAddressUtils.java +@@ -21,6 +21,7 @@ import android.os.Parcel; + import android.util.Log; + + ++import java.net.Inet4Address; + import java.net.Inet6Address; + import java.net.InetAddress; + import java.net.UnknownHostException; +@@ -32,6 +33,7 @@ import java.net.UnknownHostException; + public class InetAddressUtils { + + private static final String TAG = InetAddressUtils.class.getSimpleName(); ++ private static final int INET4_ADDR_LENGTH = 4; + private static final int INET6_ADDR_LENGTH = 16; + + /** +@@ -93,5 +95,29 @@ public class InetAddressUtils { + } + } + ++ /** ++ * Create a v4-mapped v6 address from v4 address ++ * ++ * @param v4Addr Inet4Address which is converted to v4-mapped v6 address ++ * @return v4-mapped v6 address ++ */ ++ public static Inet6Address v4MappedV6Address(@NonNull final Inet4Address v4Addr) { ++ final byte[] v6AddrBytes = new byte[INET6_ADDR_LENGTH]; ++ v6AddrBytes[10] = (byte) 0xFF; ++ v6AddrBytes[11] = (byte) 0xFF; ++ System.arraycopy(v4Addr.getAddress(), 0 /* srcPos */, v6AddrBytes, 12 /* dstPos */, ++ INET4_ADDR_LENGTH); ++ try { ++ // Using Inet6Address.getByAddress since InetAddress.getByAddress converts v4-mapped v6 ++ // address to v4 address internally and returns Inet4Address ++ return Inet6Address.getByAddress(null /* host */, v6AddrBytes, -1 /* scope_id */); ++ } catch (UnknownHostException impossible) { ++ // getByAddress throws UnknownHostException when the argument is the invalid length ++ // but INET6_ADDR_LENGTH(16) is the valid length. ++ Log.wtf(TAG, "Failed to generate v4-mapped v6 address from " + v4Addr, impossible); ++ return null; ++ } ++ } ++ + private InetAddressUtils() {} + } +-- +2.46.1.824.gd892dcdcdd-goog + diff --git a/aosp_diff/preliminary/frameworks/native/08_0008-binder-fix-FD-handling-in-continueWrite.bulletin.patch b/aosp_diff/preliminary/frameworks/native/08_0008-binder-fix-FD-handling-in-continueWrite.bulletin.patch new file mode 100644 index 0000000000..94c596ee7e --- /dev/null +++ b/aosp_diff/preliminary/frameworks/native/08_0008-binder-fix-FD-handling-in-continueWrite.bulletin.patch @@ -0,0 +1,396 @@ +From 8d76a855a1ba7f2f347c211e05f4066c9b7a82bc Mon Sep 17 00:00:00 2001 +From: Frederick Mayle +Date: Mon, 14 Oct 2024 17:12:30 -0700 +Subject: [PATCH] binder: fix FD handling in continueWrite + +Only close FDs within the truncated part of the parcel. + +This change also fixes a bug where a parcel truncated into the middle of +an object would not properly free that object. That could have resulted +in an OOB access in `Parcel::truncateRpcObjects`, so more bounds +checking is added. + +The new tests show how to reproduce the bug by appending to or partially +truncating Parcels owned by the kernel. Two cases are disabled because +of a bug in the Parcel fdsan code (b/370824489). + +Cherry-pick notes: Add truncateFileDescriptors method instead of +modifying closeFileDescriptors to avoid API change errors. Tweaked the +test to support older C++ and android-base libs. + +Flag: EXEMPT bugfix +Ignore-AOSP-First: security fix +Bug: 239222407, 359179312 +Test: atest binderLibTest +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:e77c9487166599cce197597038011f0879770ab4) +Merged-In: Iadf7e2e98e3eb97c56ec2fed2b49d1e6492af9a3 +Change-Id: Iadf7e2e98e3eb97c56ec2fed2b49d1e6492af9a3 +--- + libs/binder/Parcel.cpp | 66 ++++++++++-- + libs/binder/include/binder/Parcel.h | 3 + + libs/binder/tests/binderLibTest.cpp | 162 ++++++++++++++++++++++++++++ + 3 files changed, 222 insertions(+), 9 deletions(-) + +diff --git a/libs/binder/Parcel.cpp b/libs/binder/Parcel.cpp +index 0aca163eab..15dad8e521 100644 +--- a/libs/binder/Parcel.cpp ++++ b/libs/binder/Parcel.cpp +@@ -2505,13 +2505,17 @@ const flat_binder_object* Parcel::readObject(bool nullMetaData) const + #endif // BINDER_WITH_KERNEL_IPC + + void Parcel::closeFileDescriptors() { ++ truncateFileDescriptors(0); ++} ++ ++void Parcel::truncateFileDescriptors(size_t newObjectsSize) { + if (auto* kernelFields = maybeKernelFields()) { + #ifdef BINDER_WITH_KERNEL_IPC + size_t i = kernelFields->mObjectsSize; + if (i > 0) { + // ALOGI("Closing file descriptors for %zu objects...", i); + } +- while (i > 0) { ++ while (i > newObjectsSize) { + i--; + const flat_binder_object* flat = + reinterpret_cast(mData + kernelFields->mObjects[i]); +@@ -2521,6 +2525,7 @@ void Parcel::closeFileDescriptors() { + } + } + #else // BINDER_WITH_KERNEL_IPC ++ (void)newObjectsSize; + LOG_ALWAYS_FATAL("Binder kernel driver disabled at build time"); + #endif // BINDER_WITH_KERNEL_IPC + } else if (auto* rpcFields = maybeRpcFields()) { +@@ -2870,13 +2875,38 @@ status_t Parcel::continueWrite(size_t desired) + objectsSize = 0; + } else { + if (kernelFields) { ++#ifdef BINDER_WITH_KERNEL_IPC ++ validateReadData(mDataSize); // hack to sort the objects + while (objectsSize > 0) { +- if (kernelFields->mObjects[objectsSize - 1] < desired) break; ++ if (kernelFields->mObjects[objectsSize - 1] + sizeof(flat_binder_object) <= ++ desired) ++ break; + objectsSize--; + } ++#endif // BINDER_WITH_KERNEL_IPC + } else { + while (objectsSize > 0) { +- if (rpcFields->mObjectPositions[objectsSize - 1] < desired) break; ++ // Object size varies by type. ++ uint32_t pos = rpcFields->mObjectPositions[objectsSize - 1]; ++ size_t size = sizeof(RpcFields::ObjectType); ++ uint32_t minObjectEnd; ++ if (__builtin_add_overflow(pos, sizeof(RpcFields::ObjectType), &minObjectEnd) || ++ minObjectEnd > mDataSize) { ++ return BAD_VALUE; ++ } ++ const auto type = *reinterpret_cast(mData + pos); ++ switch (type) { ++ case RpcFields::TYPE_BINDER_NULL: ++ break; ++ case RpcFields::TYPE_BINDER: ++ size += sizeof(uint64_t); // address ++ break; ++ case RpcFields::TYPE_NATIVE_FILE_DESCRIPTOR: ++ size += sizeof(int32_t); // fd index ++ break; ++ } ++ ++ if (pos + size <= desired) break; + objectsSize--; + } + } +@@ -2925,15 +2955,24 @@ status_t Parcel::continueWrite(size_t desired) + if (mData) { + memcpy(data, mData, mDataSize < desired ? mDataSize : desired); + } ++#ifdef BINDER_WITH_KERNEL_IPC + if (objects && kernelFields && kernelFields->mObjects) { + memcpy(objects, kernelFields->mObjects, objectsSize * sizeof(binder_size_t)); ++ // All FDs are owned when `mOwner`, even when `cookie == 0`. When ++ // we switch to `!mOwner`, we need to explicitly mark the FDs as ++ // owned. ++ for (size_t i = 0; i < objectsSize; i++) { ++ flat_binder_object* flat = reinterpret_cast(data + objects[i]); ++ if (flat->hdr.type == BINDER_TYPE_FD) { ++ flat->cookie = 1; ++ } ++ } + } + // ALOGI("Freeing data ref of %p (pid=%d)", this, getpid()); + if (kernelFields) { +- // TODO(b/239222407): This seems wrong. We should only free FDs when +- // they are in a truncated section of the parcel. +- closeFileDescriptors(); ++ truncateFileDescriptors(objectsSize); + } ++#endif // BINDER_WITH_KERNEL_IPC + mOwner(mData, mDataSize, kernelFields ? kernelFields->mObjects : nullptr, + kernelFields ? kernelFields->mObjectsSize : 0); + mOwner = nullptr; +@@ -3060,11 +3099,19 @@ status_t Parcel::truncateRpcObjects(size_t newObjectsSize) { + } + while (rpcFields->mObjectPositions.size() > newObjectsSize) { + uint32_t pos = rpcFields->mObjectPositions.back(); +- rpcFields->mObjectPositions.pop_back(); ++ uint32_t minObjectEnd; ++ if (__builtin_add_overflow(pos, sizeof(RpcFields::ObjectType), &minObjectEnd) || ++ minObjectEnd > mDataSize) { ++ return BAD_VALUE; ++ } + const auto type = *reinterpret_cast(mData + pos); + if (type == RpcFields::TYPE_NATIVE_FILE_DESCRIPTOR) { +- const auto fdIndex = +- *reinterpret_cast(mData + pos + sizeof(RpcFields::ObjectType)); ++ uint32_t objectEnd; ++ if (__builtin_add_overflow(minObjectEnd, sizeof(int32_t), &objectEnd) || ++ objectEnd > mDataSize) { ++ return BAD_VALUE; ++ } ++ const auto fdIndex = *reinterpret_cast(mData + minObjectEnd); + if (rpcFields->mFds == nullptr || fdIndex < 0 || + static_cast(fdIndex) >= rpcFields->mFds->size()) { + ALOGE("RPC Parcel contains invalid file descriptor index. index=%d fd_count=%zu", +@@ -3074,6 +3121,7 @@ status_t Parcel::truncateRpcObjects(size_t newObjectsSize) { + // In practice, this always removes the last element. + rpcFields->mFds->erase(rpcFields->mFds->begin() + fdIndex); + } ++ rpcFields->mObjectPositions.pop_back(); + } + return OK; + } +diff --git a/libs/binder/include/binder/Parcel.h b/libs/binder/include/binder/Parcel.h +index 162cd406dc..73c06bf0e7 100644 +--- a/libs/binder/include/binder/Parcel.h ++++ b/libs/binder/include/binder/Parcel.h +@@ -602,6 +602,9 @@ public: + void print(std::ostream& to, uint32_t flags = 0) const; + + private: ++ // Close all file descriptors in the parcel at object positions >= newObjectsSize. ++ __attribute__((__visibility__("hidden"))) void truncateFileDescriptors(size_t newObjectsSize); ++ + // `objects` and `objectsSize` always 0 for RPC Parcels. + typedef void (*release_func)(const uint8_t* data, size_t dataSize, const binder_size_t* objects, + size_t objectsSize); +diff --git a/libs/binder/tests/binderLibTest.cpp b/libs/binder/tests/binderLibTest.cpp +index 8974ad745d..fe01bb292f 100644 +--- a/libs/binder/tests/binderLibTest.cpp ++++ b/libs/binder/tests/binderLibTest.cpp +@@ -27,6 +27,7 @@ + #include + #include + ++#include + #include + #include + #include +@@ -43,6 +44,7 @@ + + #include + #include ++#include + #include + #include + #include +@@ -57,6 +59,7 @@ using namespace std::string_literals; + using namespace std::chrono_literals; + using android::base::testing::HasValue; + using android::base::testing::Ok; ++using android::base::unique_fd; + using testing::ExplainMatchResult; + using testing::Matcher; + using testing::Not; +@@ -106,6 +109,8 @@ enum BinderLibTestTranscationCode { + BINDER_LIB_TEST_LINK_DEATH_TRANSACTION, + BINDER_LIB_TEST_WRITE_FILE_TRANSACTION, + BINDER_LIB_TEST_WRITE_PARCEL_FILE_DESCRIPTOR_TRANSACTION, ++ BINDER_LIB_TEST_GET_FILE_DESCRIPTORS_OWNED_TRANSACTION, ++ BINDER_LIB_TEST_GET_FILE_DESCRIPTORS_UNOWNED_TRANSACTION, + BINDER_LIB_TEST_EXIT_TRANSACTION, + BINDER_LIB_TEST_DELAYED_EXIT_TRANSACTION, + BINDER_LIB_TEST_GET_PTR_SIZE_TRANSACTION, +@@ -445,6 +450,35 @@ class TestDeathRecipient : public IBinder::DeathRecipient, public BinderLibTestE + }; + }; + ++ssize_t countFds() { ++ DIR* dir = opendir("/proc/self/fd/"); ++ if (dir == nullptr) return -1; ++ ssize_t ret = 0; ++ dirent* ent; ++ while ((ent = readdir(dir)) != nullptr) ret++; ++ closedir(dir); ++ return ret; ++} ++ ++struct FdLeakDetector { ++ int startCount; ++ ++ FdLeakDetector() { ++ // This log statement is load bearing. We have to log something before ++ // counting FDs to make sure the logging system is initialized, otherwise ++ // the sockets it opens will look like a leak. ++ ALOGW("FdLeakDetector counting FDs."); ++ startCount = countFds(); ++ } ++ ~FdLeakDetector() { ++ int endCount = countFds(); ++ if (startCount != endCount) { ++ ADD_FAILURE() << "fd count changed (" << startCount << " -> " << endCount ++ << ") fd leak?"; ++ } ++ } ++}; ++ + TEST_F(BinderLibTest, CannotUseBinderAfterFork) { + // EXPECT_DEATH works by forking the process + EXPECT_DEATH({ ProcessState::self(); }, "libbinder ProcessState can not be used after fork"); +@@ -872,6 +906,100 @@ TEST_F(BinderLibTest, PassParcelFileDescriptor) { + EXPECT_EQ(0, read(read_end.get(), readbuf.data(), datasize)); + } + ++TEST_F(BinderLibTest, RecvOwnedFileDescriptors) { ++ FdLeakDetector fd_leak_detector; ++ ++ Parcel data; ++ Parcel reply; ++ EXPECT_EQ(NO_ERROR, ++ m_server->transact(BINDER_LIB_TEST_GET_FILE_DESCRIPTORS_OWNED_TRANSACTION, data, ++ &reply)); ++ unique_fd a, b; ++ EXPECT_EQ(OK, reply.readUniqueFileDescriptor(&a)); ++ EXPECT_EQ(OK, reply.readUniqueFileDescriptor(&b)); ++} ++ ++// Used to trigger fdsan error (b/239222407). ++TEST_F(BinderLibTest, RecvOwnedFileDescriptorsAndWriteInt) { ++ GTEST_SKIP() << "triggers fdsan false positive: b/370824489"; ++ ++ FdLeakDetector fd_leak_detector; ++ ++ Parcel data; ++ Parcel reply; ++ EXPECT_EQ(NO_ERROR, ++ m_server->transact(BINDER_LIB_TEST_GET_FILE_DESCRIPTORS_OWNED_TRANSACTION, data, ++ &reply)); ++ reply.setDataPosition(reply.dataSize()); ++ reply.writeInt32(0); ++ reply.setDataPosition(0); ++ unique_fd a, b; ++ EXPECT_EQ(OK, reply.readUniqueFileDescriptor(&a)); ++ EXPECT_EQ(OK, reply.readUniqueFileDescriptor(&b)); ++} ++ ++// Used to trigger fdsan error (b/239222407). ++TEST_F(BinderLibTest, RecvOwnedFileDescriptorsAndTruncate) { ++ GTEST_SKIP() << "triggers fdsan false positive: b/370824489"; ++ ++ FdLeakDetector fd_leak_detector; ++ ++ Parcel data; ++ Parcel reply; ++ EXPECT_EQ(NO_ERROR, ++ m_server->transact(BINDER_LIB_TEST_GET_FILE_DESCRIPTORS_OWNED_TRANSACTION, data, ++ &reply)); ++ reply.setDataSize(reply.dataSize() - sizeof(flat_binder_object)); ++ unique_fd a, b; ++ EXPECT_EQ(OK, reply.readUniqueFileDescriptor(&a)); ++ EXPECT_EQ(BAD_TYPE, reply.readUniqueFileDescriptor(&b)); ++} ++ ++TEST_F(BinderLibTest, RecvUnownedFileDescriptors) { ++ FdLeakDetector fd_leak_detector; ++ ++ Parcel data; ++ Parcel reply; ++ EXPECT_EQ(NO_ERROR, ++ m_server->transact(BINDER_LIB_TEST_GET_FILE_DESCRIPTORS_UNOWNED_TRANSACTION, data, ++ &reply)); ++ unique_fd a, b; ++ EXPECT_EQ(OK, reply.readUniqueFileDescriptor(&a)); ++ EXPECT_EQ(OK, reply.readUniqueFileDescriptor(&b)); ++} ++ ++// Used to trigger fdsan error (b/239222407). ++TEST_F(BinderLibTest, RecvUnownedFileDescriptorsAndWriteInt) { ++ FdLeakDetector fd_leak_detector; ++ ++ Parcel data; ++ Parcel reply; ++ EXPECT_EQ(NO_ERROR, ++ m_server->transact(BINDER_LIB_TEST_GET_FILE_DESCRIPTORS_UNOWNED_TRANSACTION, data, ++ &reply)); ++ reply.setDataPosition(reply.dataSize()); ++ reply.writeInt32(0); ++ reply.setDataPosition(0); ++ unique_fd a, b; ++ EXPECT_EQ(OK, reply.readUniqueFileDescriptor(&a)); ++ EXPECT_EQ(OK, reply.readUniqueFileDescriptor(&b)); ++} ++ ++// Used to trigger fdsan error (b/239222407). ++TEST_F(BinderLibTest, RecvUnownedFileDescriptorsAndTruncate) { ++ FdLeakDetector fd_leak_detector; ++ ++ Parcel data; ++ Parcel reply; ++ EXPECT_EQ(NO_ERROR, ++ m_server->transact(BINDER_LIB_TEST_GET_FILE_DESCRIPTORS_UNOWNED_TRANSACTION, data, ++ &reply)); ++ reply.setDataSize(reply.dataSize() - sizeof(flat_binder_object)); ++ unique_fd a, b; ++ EXPECT_EQ(OK, reply.readUniqueFileDescriptor(&a)); ++ EXPECT_EQ(BAD_TYPE, reply.readUniqueFileDescriptor(&b)); ++} ++ + TEST_F(BinderLibTest, PromoteLocal) { + sp strong = new BBinder(); + wp weak = strong; +@@ -1800,6 +1928,40 @@ public: + if (ret != size) return UNKNOWN_ERROR; + return NO_ERROR; + } ++ case BINDER_LIB_TEST_GET_FILE_DESCRIPTORS_OWNED_TRANSACTION: { ++ unique_fd fd1(memfd_create("memfd1", MFD_CLOEXEC)); ++ if (!fd1.ok()) { ++ PLOG(ERROR) << "memfd_create failed"; ++ return UNKNOWN_ERROR; ++ } ++ unique_fd fd2(memfd_create("memfd2", MFD_CLOEXEC)); ++ if (!fd2.ok()) { ++ PLOG(ERROR) << "memfd_create failed"; ++ return UNKNOWN_ERROR; ++ } ++ status_t ret; ++ ret = reply->writeFileDescriptor(fd1.release(), true); ++ if (ret != NO_ERROR) { ++ return ret; ++ } ++ ret = reply->writeFileDescriptor(fd2.release(), true); ++ if (ret != NO_ERROR) { ++ return ret; ++ } ++ return NO_ERROR; ++ } ++ case BINDER_LIB_TEST_GET_FILE_DESCRIPTORS_UNOWNED_TRANSACTION: { ++ status_t ret; ++ ret = reply->writeFileDescriptor(STDOUT_FILENO, false); ++ if (ret != NO_ERROR) { ++ return ret; ++ } ++ ret = reply->writeFileDescriptor(STDERR_FILENO, false); ++ if (ret != NO_ERROR) { ++ return ret; ++ } ++ return NO_ERROR; ++ } + case BINDER_LIB_TEST_DELAYED_EXIT_TRANSACTION: + alarm(10); + return NO_ERROR; +-- +2.46.1.824.gd892dcdcdd-goog + diff --git a/aosp_diff/preliminary/frameworks/native/09_0009-libbinder-Parcel-grow-rejects-large-data-pos.bulletin.patch b/aosp_diff/preliminary/frameworks/native/09_0009-libbinder-Parcel-grow-rejects-large-data-pos.bulletin.patch new file mode 100644 index 0000000000..b3c811f918 --- /dev/null +++ b/aosp_diff/preliminary/frameworks/native/09_0009-libbinder-Parcel-grow-rejects-large-data-pos.bulletin.patch @@ -0,0 +1,41 @@ +From 01a1e34929996a8fb9791dec6313627fee4ab1b3 Mon Sep 17 00:00:00 2001 +From: Steven Moreland +Date: Wed, 2 Oct 2024 01:00:23 +0000 +Subject: [PATCH] libbinder: Parcel: grow rejects large data pos + +This is unexpected behavior so throw an error. +Allocating this much memory may cause OOM or +other issues. + +Bug: 370831157 +Test: fuzzer +(cherry picked from commit 608524d462278c2c9f6716cd94f126c85e9f2e91) +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:93856b3c6df5a34717dddddfab23d945753f7de8) +Merged-In: Iea0884ca61b08e52e6a6e9c66693e427cb5536f4 +Change-Id: Iea0884ca61b08e52e6a6e9c66693e427cb5536f4 +--- + libs/binder/Parcel.cpp | 8 ++++++++ + 1 file changed, 8 insertions(+) + +diff --git a/libs/binder/Parcel.cpp b/libs/binder/Parcel.cpp +index 15dad8e521..fececf894f 100644 +--- a/libs/binder/Parcel.cpp ++++ b/libs/binder/Parcel.cpp +@@ -2775,6 +2775,14 @@ status_t Parcel::growData(size_t len) + return BAD_VALUE; + } + ++ if (mDataPos > mDataSize) { ++ // b/370831157 - this case used to abort. We also don't expect mDataPos < mDataSize, but ++ // this would only waste a bit of memory, so it's okay. ++ ALOGE("growData only expected at the end of a Parcel. pos: %zu, size: %zu, capacity: %zu", ++ mDataPos, len, mDataCapacity); ++ return BAD_VALUE; ++ } ++ + if (len > SIZE_MAX - mDataSize) return NO_MEMORY; // overflow + if (mDataSize + len > SIZE_MAX / 3) return NO_MEMORY; // overflow + size_t newSize = ((mDataSize+len)*3)/2; +-- +2.46.1.824.gd892dcdcdd-goog + diff --git a/aosp_diff/preliminary/frameworks/native/10_0010-libbinder-Parcel-validate-read-data-before-write.bulletin.patch b/aosp_diff/preliminary/frameworks/native/10_0010-libbinder-Parcel-validate-read-data-before-write.bulletin.patch new file mode 100644 index 0000000000..b4b67e9f46 --- /dev/null +++ b/aosp_diff/preliminary/frameworks/native/10_0010-libbinder-Parcel-validate-read-data-before-write.bulletin.patch @@ -0,0 +1,59 @@ +From f3c7aac0e3277f7ebabaab94f34b5c9156412cc9 Mon Sep 17 00:00:00 2001 +From: Steven Moreland +Date: Wed, 2 Oct 2024 00:37:59 +0000 +Subject: [PATCH] libbinder: Parcel: validate read data before write + +This is slow, but it's required to prevent memory +corruption. + +Ignore-AOSP-First: security +Bug: 370840874 +Test: fuzzer +(cherry picked from commit c54dad65317f851ce9d016bd90ec6a7a04da09fc) +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:640d942d6a9a26a0beca87d3abdf2ee1048985b9) +Merged-In: Ibc5566ade0389221690dc90324f93394cf7fc9a5 +Change-Id: Ibc5566ade0389221690dc90324f93394cf7fc9a5 +--- + libs/binder/Parcel.cpp | 12 ++++++++++++ + 1 file changed, 12 insertions(+) + +diff --git a/libs/binder/Parcel.cpp b/libs/binder/Parcel.cpp +index fececf894f..2be2aa5b1f 100644 +--- a/libs/binder/Parcel.cpp ++++ b/libs/binder/Parcel.cpp +@@ -1090,6 +1090,10 @@ restart_write: + //printf("Writing %ld bytes, padded to %ld\n", len, padded); + uint8_t* const data = mData+mDataPos; + ++ if (status_t status = validateReadData(mDataPos + padded); status != OK) { ++ return nullptr; // drops status ++ } ++ + // Need to pad at end? + if (padded != len) { + #if BYTE_ORDER == BIG_ENDIAN +@@ -1648,6 +1652,10 @@ status_t Parcel::writeObject(const flat_binder_object& val, bool nullMetaData) + const bool enoughObjects = kernelFields->mObjectsSize < kernelFields->mObjectsCapacity; + if (enoughData && enoughObjects) { + restart_write: ++ if (status_t status = validateReadData(mDataPos + sizeof(val)); status != OK) { ++ return status; ++ } ++ + *reinterpret_cast(mData+mDataPos) = val; + + // remember if it's a file descriptor +@@ -1889,6 +1897,10 @@ status_t Parcel::writeAligned(T val) { + + if ((mDataPos+sizeof(val)) <= mDataCapacity) { + restart_write: ++ if (status_t status = validateReadData(mDataPos + sizeof(val)); status != OK) { ++ return status; ++ } ++ + memcpy(mData + mDataPos, &val, sizeof(val)); + return finishWrite(sizeof(val)); + } +-- +2.46.1.824.gd892dcdcdd-goog + diff --git a/aosp_diff/preliminary/packages/apps/DocumentsUI/0001-Prevent-clickjacking-attack-in-DocsUi-.bulletin.patch b/aosp_diff/preliminary/packages/apps/DocumentsUI/0001-Prevent-clickjacking-attack-in-DocsUi-.bulletin.patch new file mode 100644 index 0000000000..988d4f72eb --- /dev/null +++ b/aosp_diff/preliminary/packages/apps/DocumentsUI/0001-Prevent-clickjacking-attack-in-DocsUi-.bulletin.patch @@ -0,0 +1,84 @@ +From 6a1dd25117d5406d3befe404335202623778736a Mon Sep 17 00:00:00 2001 +From: Aditya Singh +Date: Mon, 9 Sep 2024 15:40:13 +0000 +Subject: [PATCH] Prevent clickjacking attack in DocsUi. + +* Added permission `HIDE_OVERLAY_WINDOWS` in the Manifest. +* Set the flag to hide overlay windows to true in BaseActivity and + ConfirmFragment. + +Bug: 233605527 +Test: Manually, see http://b/233605527#comment4 +Flag: EXEMPT bugfix +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:4f610dc4830093b42934090fcf5bc129b639c693) +Merged-In: I511730856be58cad3e13fa50bfac1e1ee2f5fee0 +Change-Id: I511730856be58cad3e13fa50bfac1e1ee2f5fee0 +--- + AndroidManifest.xml | 1 + + src/com/android/documentsui/BaseActivity.java | 5 +++++ + src/com/android/documentsui/picker/ConfirmFragment.java | 7 ++++++- + 3 files changed, 12 insertions(+), 1 deletion(-) + +diff --git a/AndroidManifest.xml b/AndroidManifest.xml +index ef86ab779..f536d8184 100644 +--- a/AndroidManifest.xml ++++ b/AndroidManifest.xml +@@ -33,6 +33,7 @@ + + + ++ + + + +diff --git a/src/com/android/documentsui/BaseActivity.java b/src/com/android/documentsui/BaseActivity.java +index c6cbc1936..5af331439 100644 +--- a/src/com/android/documentsui/BaseActivity.java ++++ b/src/com/android/documentsui/BaseActivity.java +@@ -74,6 +74,7 @@ import com.android.documentsui.roots.ProvidersCache; + import com.android.documentsui.sidebar.RootsFragment; + import com.android.documentsui.sorting.SortController; + import com.android.documentsui.sorting.SortModel; ++import com.android.modules.utils.build.SdkLevel; + + import com.android.documentsui.util.VersionUtils; + import com.google.android.material.appbar.AppBarLayout; +@@ -135,6 +136,10 @@ public abstract class BaseActivity + // Record the time when onCreate is invoked for metric. + mStartTime = new Date().getTime(); + ++ if (SdkLevel.isAtLeastS()) { ++ getWindow().setHideOverlayWindows(true); ++ } ++ + // ToDo Create tool to check resource version before applyStyle for the theme + // If version code is not match, we should reset overlay package to default, + // in case Activity continueusly encounter resource not found exception +diff --git a/src/com/android/documentsui/picker/ConfirmFragment.java b/src/com/android/documentsui/picker/ConfirmFragment.java +index 94015e930..e1af281bc 100644 +--- a/src/com/android/documentsui/picker/ConfirmFragment.java ++++ b/src/com/android/documentsui/picker/ConfirmFragment.java +@@ -32,6 +32,7 @@ import com.android.documentsui.BaseActivity; + import com.android.documentsui.R; + import com.android.documentsui.base.DocumentInfo; + import com.android.documentsui.base.Shared; ++import com.android.modules.utils.build.SdkLevel; + + import com.google.android.material.dialog.MaterialAlertDialogBuilder; + +@@ -102,7 +103,11 @@ public class ConfirmFragment extends DialogFragment { + builder.setNegativeButton(android.R.string.cancel, + (DialogInterface dialog, int id) -> pickResult.increaseActionCount()); + +- return builder.create(); ++ Dialog dialog = builder.create(); ++ if (SdkLevel.isAtLeastS()) { ++ dialog.getWindow().setHideOverlayWindows(true); ++ } ++ return dialog; + } + + @Override +-- +2.46.1.824.gd892dcdcdd-goog + diff --git a/aosp_diff/preliminary/packages/apps/Settings/21_0021--CDM-NLS-Check-if-the-NLS-service-has-an-intent-filter.bulletin.patch b/aosp_diff/preliminary/packages/apps/Settings/21_0021--CDM-NLS-Check-if-the-NLS-service-has-an-intent-filter.bulletin.patch new file mode 100644 index 0000000000..4a089939fb --- /dev/null +++ b/aosp_diff/preliminary/packages/apps/Settings/21_0021--CDM-NLS-Check-if-the-NLS-service-has-an-intent-filter.bulletin.patch @@ -0,0 +1,147 @@ +From b0eec6fdfd5b4f9ba81750da468d26440904142e Mon Sep 17 00:00:00 2001 +From: Guojing Yuan +Date: Tue, 1 Oct 2024 21:59:31 +0000 +Subject: [PATCH] [CDM][NLS] Check if the NLS service has an intent-filter + +Bug: 363248394 +Test: CTS +Flag: EXEMPT bugfix +(cherry picked from commit 7ae59a42eb13f643d842525208619037c074371a) +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:1b323fe2c30f18a414e2249f69121f4dbb380fa3) +Merged-In: Ib79c219cde8d73a218ceb7911f4552d43e384d8e +Change-Id: Ib79c219cde8d73a218ceb7911f4552d43e384d8e +--- + ...otificationAccessConfirmationActivity.java | 50 +++++++++++-------- + ...icationAccessConfirmationActivityTest.java | 9 ++-- + 2 files changed, 33 insertions(+), 26 deletions(-) + +diff --git a/src/com/android/settings/notification/NotificationAccessConfirmationActivity.java b/src/com/android/settings/notification/NotificationAccessConfirmationActivity.java +index 9ea8c58024..74b8102ee2 100644 +--- a/src/com/android/settings/notification/NotificationAccessConfirmationActivity.java ++++ b/src/com/android/settings/notification/NotificationAccessConfirmationActivity.java +@@ -31,13 +31,15 @@ import android.app.admin.DevicePolicyManager; + import android.content.ComponentName; + import android.content.Context; + import android.content.DialogInterface; ++import android.content.Intent; + import android.content.pm.ApplicationInfo; + import android.content.pm.PackageItemInfo; + import android.content.pm.PackageManager; +-import android.content.pm.ServiceInfo; ++import android.content.pm.ResolveInfo; + import android.os.Bundle; + import android.os.UserHandle; + import android.os.UserManager; ++import android.service.notification.NotificationListenerService; + import android.text.TextUtils; + import android.util.Slog; + import android.view.WindowManager; +@@ -48,6 +50,8 @@ import com.android.internal.app.AlertActivity; + import com.android.internal.app.AlertController; + import com.android.settings.R; + ++import java.util.List; ++ + /** @hide */ + public class NotificationAccessConfirmationActivity extends Activity + implements DialogInterface { +@@ -112,6 +116,31 @@ public class NotificationAccessConfirmationActivity extends Activity + return; + } + ++ // Check NLS service info. ++ String requiredPermission = Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE; ++ Intent NLSIntent = new Intent(NotificationListenerService.SERVICE_INTERFACE); ++ List matchedServiceList = getPackageManager().queryIntentServicesAsUser( ++ NLSIntent, /* flags */ 0, mUserId); ++ boolean hasNLSIntentFilter = false; ++ for (ResolveInfo service : matchedServiceList) { ++ if (service.serviceInfo.packageName.equals(mComponentName.getPackageName())) { ++ if (!requiredPermission.equals(service.serviceInfo.permission)) { ++ Slog.e(LOG_TAG, "Service " + mComponentName + " lacks permission " ++ + requiredPermission); ++ finish(); ++ return; ++ } ++ hasNLSIntentFilter = true; ++ break; ++ } ++ } ++ if (!hasNLSIntentFilter) { ++ Slog.e(LOG_TAG, "Service " + mComponentName + " lacks an intent-filter action " ++ + "for android.service.notification.NotificationListenerService."); ++ finish(); ++ return; ++ } ++ + AlertController.AlertParams p = new AlertController.AlertParams(this); + p.mTitle = getString( + R.string.notification_listener_security_warning_title, +@@ -146,19 +175,6 @@ public class NotificationAccessConfirmationActivity extends Activity + } + + private void onAllow() { +- String requiredPermission = Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE; +- try { +- ServiceInfo serviceInfo = getPackageManager().getServiceInfo(mComponentName, 0); +- if (!requiredPermission.equals(serviceInfo.permission)) { +- Slog.e(LOG_TAG, +- "Service " + mComponentName + " lacks permission " + requiredPermission); +- return; +- } +- } catch (PackageManager.NameNotFoundException e) { +- Slog.e(LOG_TAG, "Failed to get service info for " + mComponentName, e); +- return; +- } +- + mNm.setNotificationListenerAccessGranted(mComponentName, true); + + finish(); +@@ -169,12 +185,6 @@ public class NotificationAccessConfirmationActivity extends Activity + return AlertActivity.dispatchPopulateAccessibilityEvent(this, event); + } + +- @Override +- public void onBackPressed() { +- // Suppress finishing the activity on back button press, +- // consistently with the permission dialog behavior +- } +- + @Override + public void cancel() { + finish(); +diff --git a/tests/robotests/src/com/android/settings/notification/NotificationAccessConfirmationActivityTest.java b/tests/robotests/src/com/android/settings/notification/NotificationAccessConfirmationActivityTest.java +index 86631ffb2d..788f853e19 100644 +--- a/tests/robotests/src/com/android/settings/notification/NotificationAccessConfirmationActivityTest.java ++++ b/tests/robotests/src/com/android/settings/notification/NotificationAccessConfirmationActivityTest.java +@@ -30,8 +30,6 @@ import android.content.pm.ApplicationInfo; + import android.content.pm.PackageInfo; + import android.widget.TextView; + +-import com.android.settings.R; +- + import com.google.common.base.Strings; + + import org.junit.Test; +@@ -44,15 +42,14 @@ import org.robolectric.RuntimeEnvironment; + public class NotificationAccessConfirmationActivityTest { + + @Test +- public void start_showsDialog() { ++ public void start_withMissingIntentFilter_finishes() { + ComponentName cn = new ComponentName("com.example", "com.example.SomeService"); + installPackage(cn.getPackageName(), "X"); + + NotificationAccessConfirmationActivity activity = startActivityWithIntent(cn); + +- assertThat(activity.isFinishing()).isFalse(); +- assertThat(getDialogText(activity)).isEqualTo( +- activity.getString(R.string.notification_listener_security_warning_summary, "X")); ++ assertThat(getDialogText(activity)).isNull(); ++ assertThat(activity.isFinishing()).isTrue(); + } + + @Test +-- +2.46.1.824.gd892dcdcdd-goog + diff --git a/aosp_diff/preliminary/packages/apps/Settings/22_0022-Disable-factory-reset-in-DSU-mode.bulletin.patch b/aosp_diff/preliminary/packages/apps/Settings/22_0022-Disable-factory-reset-in-DSU-mode.bulletin.patch new file mode 100644 index 0000000000..82e3bce8ae --- /dev/null +++ b/aosp_diff/preliminary/packages/apps/Settings/22_0022-Disable-factory-reset-in-DSU-mode.bulletin.patch @@ -0,0 +1,64 @@ +From 69b3831009d1f1d167e4b91ea47334345f263199 Mon Sep 17 00:00:00 2001 +From: t +Date: Thu, 2 Nov 2023 08:06:59 +0000 +Subject: [PATCH] Disable factory reset in DSU mode + +Bug: 302317901 +Bug: 316578327 +Test: build +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:5f8f7f93f3b5b1726b2d49a51f25b2bb2f4c0ee9) +Merged-In: I485eb6ac7beec0893d91ca5fe8ad88ecd96a5cbe +Change-Id: I485eb6ac7beec0893d91ca5fe8ad88ecd96a5cbe +--- + src/com/android/settings/MainClear.java | 16 ++++++++++++++++ + 1 file changed, 16 insertions(+) + +diff --git a/src/com/android/settings/MainClear.java b/src/com/android/settings/MainClear.java +index f706c78540..07888c6b67 100644 +--- a/src/com/android/settings/MainClear.java ++++ b/src/com/android/settings/MainClear.java +@@ -26,11 +26,13 @@ import android.accounts.AccountManager; + import android.accounts.AuthenticatorDescription; + import android.app.ActionBar; + import android.app.Activity; ++import android.app.AlertDialog; + import android.app.admin.DevicePolicyManager; + import android.app.settings.SettingsEnums; + import android.content.ComponentName; + import android.content.ContentResolver; + import android.content.Context; ++import android.content.DialogInterface; + import android.content.Intent; + import android.content.pm.PackageManager; + import android.content.pm.ResolveInfo; +@@ -43,6 +45,7 @@ import android.os.Environment; + import android.os.SystemProperties; + import android.os.UserHandle; + import android.os.UserManager; ++import android.os.image.DynamicSystemManager; + import android.provider.Settings; + import android.telephony.euicc.EuiccManager; + import android.text.TextUtils; +@@ -266,6 +269,19 @@ public class MainClear extends InstrumentedFragment implements OnGlobalLayoutLis + return; + } + ++ final DynamicSystemManager dsuManager = (DynamicSystemManager) ++ getActivity().getSystemService(Context.DYNAMIC_SYSTEM_SERVICE); ++ if (dsuManager.isInUse()) { ++ AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); ++ builder.setTitle(R.string.dsu_is_running); ++ builder.setPositiveButton(R.string.okay, new DialogInterface.OnClickListener() { ++ public void onClick(DialogInterface dialog, int id) {} ++ }); ++ AlertDialog dsuAlertdialog = builder.create(); ++ dsuAlertdialog.show(); ++ return; ++ } ++ + if (runKeyguardConfirmation(KEYGUARD_REQUEST)) { + return; + } +-- +2.46.1.824.gd892dcdcdd-goog + diff --git a/aosp_diff/preliminary/packages/modules/Bluetooth/0018-RESTRICT-AUTOMERGE-Disallow-unexpected-incoming-HID-connections.bulletin.patch b/aosp_diff/preliminary/packages/modules/Bluetooth/0018-RESTRICT-AUTOMERGE-Disallow-unexpected-incoming-HID-connections.bulletin.patch new file mode 100644 index 0000000000..3c87426868 --- /dev/null +++ b/aosp_diff/preliminary/packages/modules/Bluetooth/0018-RESTRICT-AUTOMERGE-Disallow-unexpected-incoming-HID-connections.bulletin.patch @@ -0,0 +1,463 @@ +From 9529bb85c051596d8cc34ddac5fd5318e9b6d009 Mon Sep 17 00:00:00 2001 +From: Himanshu Rawat +Date: Fri, 5 Apr 2024 17:33:53 +0000 +Subject: [PATCH] RESTRICT AUTOMERGE Disallow unexpected incoming HID + connections + +HID profile accepted any new incoming HID connection. Even when the +connection policy disabled HID connection, remote devices could initiate +HID connection. +This change ensures that incoming HID connection are accepted only if +application was interested in that HID connection. +This vulnerarbility no longer exists on the main because of feature +request b/324093729. + +Test: mmm packages/modules/Bluetooth +Test: Manual | Pair and connect a HID device, disable HID connection +from Bluetooth device setting, attempt to connect from the HID device. +Bug: 308429049 +Ignore-AOSP-First: security +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:bdd92020a9c14c3f541b39624c5b1e0af599acc5) +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:358b66af175f423523c5d90bb8aea4b3eb084172) +Merged-In: Iba2ac3502bf1e6e4ac1f60ed64b1b074facd880b +Change-Id: Iba2ac3502bf1e6e4ac1f60ed64b1b074facd880b +--- + .../jni/com_android_bluetooth_hid_host.cpp | 8 +- + .../android/bluetooth/hid/HidHostService.java | 11 +-- + system/btif/include/btif_hh.h | 4 +- + system/btif/include/btif_storage.h | 23 ++++++ + system/btif/src/btif_hh.cc | 81 +++++++++++++++++-- + system/btif/src/btif_profile_storage.cc | 50 +++++++++++- + system/gd/rust/linux/stack/src/bluetooth.rs | 4 +- + .../gd/rust/topshim/src/profiles/hid_host.rs | 2 +- + system/include/hardware/bt_hh.h | 2 +- + 9 files changed, 166 insertions(+), 19 deletions(-) + +diff --git a/android/app/jni/com_android_bluetooth_hid_host.cpp b/android/app/jni/com_android_bluetooth_hid_host.cpp +index 7a164233bc..18ba315129 100644 +--- a/android/app/jni/com_android_bluetooth_hid_host.cpp ++++ b/android/app/jni/com_android_bluetooth_hid_host.cpp +@@ -282,7 +282,8 @@ static jboolean connectHidNative(JNIEnv* env, jobject object, + } + + static jboolean disconnectHidNative(JNIEnv* env, jobject object, +- jbyteArray address) { ++ jbyteArray address, ++ jboolean reconnect_allowed) { + jbyte* addr; + jboolean ret = JNI_TRUE; + if (!sBluetoothHidInterface) return JNI_FALSE; +@@ -293,7 +294,8 @@ static jboolean disconnectHidNative(JNIEnv* env, jobject object, + return JNI_FALSE; + } + +- bt_status_t status = sBluetoothHidInterface->disconnect((RawAddress*)addr); ++ bt_status_t status = ++ sBluetoothHidInterface->disconnect((RawAddress*)addr, reconnect_allowed); + if (status != BT_STATUS_SUCCESS) { + ALOGE("Failed disconnect hid channel, status: %d", status); + ret = JNI_FALSE; +@@ -509,7 +511,7 @@ static JNINativeMethod sMethods[] = { + {"initializeNative", "()V", (void*)initializeNative}, + {"cleanupNative", "()V", (void*)cleanupNative}, + {"connectHidNative", "([B)Z", (void*)connectHidNative}, +- {"disconnectHidNative", "([B)Z", (void*)disconnectHidNative}, ++ {"disconnectHidNative", "([BZ)Z", (void*)disconnectHidNative}, + {"getProtocolModeNative", "([B)Z", (void*)getProtocolModeNative}, + {"virtualUnPlugNative", "([B)Z", (void*)virtualUnPlugNative}, + {"setProtocolModeNative", "([BB)Z", (void*)setProtocolModeNative}, +diff --git a/android/app/src/com/android/bluetooth/hid/HidHostService.java b/android/app/src/com/android/bluetooth/hid/HidHostService.java +index dfd7dd2002..eae47c1881 100644 +--- a/android/app/src/com/android/bluetooth/hid/HidHostService.java ++++ b/android/app/src/com/android/bluetooth/hid/HidHostService.java +@@ -190,7 +190,10 @@ public class HidHostService extends ProfileService { + break; + case MESSAGE_DISCONNECT: { + BluetoothDevice device = (BluetoothDevice) msg.obj; +- if (!disconnectHidNative(getByteAddress(device))) { ++ int connectionPolicy = getConnectionPolicy(device); ++ boolean reconnectAllowed = ++ connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED; ++ if (!disconnectHidNative(getByteAddress(device), reconnectAllowed)) { + broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTING); + broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED); + break; +@@ -326,9 +329,7 @@ public class HidHostService extends ProfileService { + } + }; + +- /** +- * Handlers for incoming service calls +- */ ++ /** Handlers for incoming service calls */ + @VisibleForTesting + static class BluetoothHidHostBinder extends IBluetoothHidHost.Stub + implements IProfileServiceBinder { +@@ -1032,7 +1033,7 @@ public class HidHostService extends ProfileService { + + private native boolean connectHidNative(byte[] btAddress); + +- private native boolean disconnectHidNative(byte[] btAddress); ++ private native boolean disconnectHidNative(byte[] btAddress, boolean reconnect_allowed); + + private native boolean getProtocolModeNative(byte[] btAddress); + +diff --git a/system/btif/include/btif_hh.h b/system/btif/include/btif_hh.h +index d524687149..6ee8487712 100644 +--- a/system/btif/include/btif_hh.h ++++ b/system/btif/include/btif_hh.h +@@ -111,6 +111,7 @@ typedef struct { + uint8_t dev_handle; + RawAddress bd_addr; + tBTA_HH_ATTR_MASK attr_mask; ++ bool reconnect_allowed; + } btif_hh_added_device_t; + + /** +@@ -134,7 +135,8 @@ extern btif_hh_cb_t btif_hh_cb; + + btif_hh_device_t* btif_hh_find_connected_dev_by_handle(uint8_t handle); + void btif_hh_remove_device(RawAddress bd_addr); +-bool btif_hh_add_added_dev(const RawAddress& bda, tBTA_HH_ATTR_MASK attr_mask); ++bool btif_hh_add_added_dev(const RawAddress& bda, tBTA_HH_ATTR_MASK attr_mask, ++ bool reconnect_allowed); + bt_status_t btif_hh_virtual_unplug(const RawAddress* bd_addr); + void btif_hh_disconnect(RawAddress* bd_addr); + void btif_hh_setreport(btif_hh_device_t* p_dev, bthh_report_type_t r_type, +diff --git a/system/btif/include/btif_storage.h b/system/btif/include/btif_storage.h +index a8bf67eac1..392d99e4a3 100644 +--- a/system/btif/include/btif_storage.h ++++ b/system/btif/include/btif_storage.h +@@ -216,6 +216,29 @@ void btif_storage_load_le_devices(void); + ******************************************************************************/ + bt_status_t btif_storage_load_bonded_devices(void); + ++/******************************************************************************* ++ * ++ * Function btif_storage_set_hid_connection_policy ++ * ++ * Description Stores connection policy info in nvram ++ * ++ * Returns BT_STATUS_SUCCESS ++ * ++ ******************************************************************************/ ++bt_status_t btif_storage_set_hid_connection_policy(const RawAddress& addr, ++ bool reconnect_allowed); ++/******************************************************************************* ++ * ++ * Function btif_storage_get_hid_connection_policy ++ * ++ * Description get connection policy info from nvram ++ * ++ * Returns BT_STATUS_SUCCESS ++ * ++ ******************************************************************************/ ++bt_status_t btif_storage_get_hid_connection_policy(const RawAddress& addr, ++ bool* reconnect_allowed); ++ + /******************************************************************************* + * + * Function btif_storage_add_hid_device_info +diff --git a/system/btif/src/btif_hh.cc b/system/btif/src/btif_hh.cc +index fea75ef0cb..e9d157b3ee 100644 +--- a/system/btif/src/btif_hh.cc ++++ b/system/btif/src/btif_hh.cc +@@ -309,6 +309,24 @@ btif_hh_device_t* btif_hh_find_connected_dev_by_handle(uint8_t handle) { + return NULL; + } + ++/******************************************************************************* ++ * ++ * Function btif_hh_find_added_dev ++ * ++ * Description Return the added device pointer of the specified address ++ * ++ * Returns Added device entry ++ ******************************************************************************/ ++btif_hh_added_device_t* btif_hh_find_added_dev(const RawAddress& addr) { ++ for (int i = 0; i < BTIF_HH_MAX_ADDED_DEV; i++) { ++ btif_hh_added_device_t* added_dev = &btif_hh_cb.added_devices[i]; ++ if (added_dev->bd_addr == addr) { ++ return added_dev; ++ } ++ } ++ return nullptr; ++} ++ + /******************************************************************************* + * + * Function btif_hh_find_dev_by_bda +@@ -398,9 +416,38 @@ static void hh_connect_complete(uint8_t handle, RawAddress& bda, + HAL_CBACK(bt_hh_callbacks, connection_state_cb, &bda, state); + } + ++static bool hh_connection_allowed(const RawAddress& bda) { ++ /* Accept connection only if reconnection is allowed for the known device, or ++ * outgoing connection was requested */ ++ btif_hh_added_device_t* added_dev = btif_hh_find_added_dev(bda); ++ if (added_dev != nullptr && added_dev->reconnect_allowed) { ++ LOG_VERBOSE("Connection allowed %s", ADDRESS_TO_LOGGABLE_CSTR(bda)); ++ return true; ++ } else if (btif_hh_cb.pending_conn_address == bda) { ++ LOG_VERBOSE("Device connection was pending for: %s, status: %s", ++ ADDRESS_TO_LOGGABLE_CSTR(bda), ++ btif_hh_status_text(btif_hh_cb.status).c_str()); ++ return true; ++ } ++ ++ return false; ++} ++ + static void hh_open_handler(tBTA_HH_CONN& conn) { + LOG_DEBUG("status = %d, handle = %d", conn.status, conn.handle); + ++ if (!hh_connection_allowed(conn.bda)) { ++ LOG_WARN("Reject unexpected incoming HID Connection, device: %s", ++ ADDRESS_TO_LOGGABLE_CSTR(conn.bda)); ++ btif_hh_device_t* p_dev = btif_hh_find_connected_dev_by_handle(conn.handle); ++ if (p_dev != nullptr) { ++ p_dev->dev_status = BTHH_CONN_STATE_DISCONNECTED; ++ } ++ ++ hh_connect_complete(conn.handle, conn.bda, BTIF_HH_DEV_DISCONNECTED); ++ return; ++ } ++ + HAL_CBACK(bt_hh_callbacks, connection_state_cb, (RawAddress*)&conn.bda, + BTHH_CONN_STATE_CONNECTING); + btif_hh_cb.pending_conn_address = RawAddress::kEmpty; +@@ -454,7 +501,8 @@ static void hh_open_handler(tBTA_HH_CONN& conn) { + * + * Returns true if add successfully, otherwise false. + ******************************************************************************/ +-bool btif_hh_add_added_dev(const RawAddress& bda, tBTA_HH_ATTR_MASK attr_mask) { ++bool btif_hh_add_added_dev(const RawAddress& bda, tBTA_HH_ATTR_MASK attr_mask, ++ bool reconnect_allowed) { + int i; + for (i = 0; i < BTIF_HH_MAX_ADDED_DEV; i++) { + if (btif_hh_cb.added_devices[i].bd_addr == bda) { +@@ -464,10 +512,12 @@ bool btif_hh_add_added_dev(const RawAddress& bda, tBTA_HH_ATTR_MASK attr_mask) { + } + for (i = 0; i < BTIF_HH_MAX_ADDED_DEV; i++) { + if (btif_hh_cb.added_devices[i].bd_addr.IsEmpty()) { +- LOG(WARNING) << " Added device " << ADDRESS_TO_LOGGABLE_STR(bda); ++ LOG(WARNING) << " Added device " << ADDRESS_TO_LOGGABLE_STR(bda) ++ << " reconnection allowed: " << reconnect_allowed; + btif_hh_cb.added_devices[i].bd_addr = bda; + btif_hh_cb.added_devices[i].dev_handle = BTA_HH_INVALID_HANDLE; + btif_hh_cb.added_devices[i].attr_mask = attr_mask; ++ btif_hh_cb.added_devices[i].reconnect_allowed = reconnect_allowed; + return true; + } + } +@@ -1025,7 +1075,7 @@ static void btif_hh_upstreams_evt(uint16_t event, char* p_param) { + p_data->dscp_info.version, + p_data->dscp_info.ctry_code, len, + p_data->dscp_info.descriptor.dsc_list); +- if (btif_hh_add_added_dev(p_dev->bd_addr, p_dev->attr_mask)) { ++ if (btif_hh_add_added_dev(p_dev->bd_addr, p_dev->attr_mask, true)) { + tBTA_HH_DEV_DSCP_INFO dscp_info; + bt_status_t ret; + btif_hh_copy_hid_info(&dscp_info, &p_data->dscp_info); +@@ -1042,6 +1092,8 @@ static void btif_hh_upstreams_evt(uint16_t event, char* p_param) { + p_data->dscp_info.ssr_min_tout, len, + p_data->dscp_info.descriptor.dsc_list); + ++ btif_storage_set_hid_connection_policy(p_dev->bd_addr, true); ++ + ASSERTC(ret == BT_STATUS_SUCCESS, "storing hid info failed", ret); + BTIF_TRACE_WARNING("BTA_HH_GET_DSCP_EVT: Called add device"); + +@@ -1341,12 +1393,20 @@ static bt_status_t connect(RawAddress* bd_addr) { + return BT_STATUS_NOT_READY; + } + ++ /* If the device was already added, ensure that reconnections are allowed */ ++ btif_hh_added_device_t* added_dev = btif_hh_find_added_dev(*bd_addr); ++ if (added_dev != nullptr && !added_dev->reconnect_allowed) { ++ added_dev->reconnect_allowed = true; ++ btif_storage_set_hid_connection_policy(*bd_addr, true); ++ } ++ + p_dev = btif_hh_find_connected_dev_by_bda(*bd_addr); + if (p_dev) { + if (p_dev->dev_status == BTHH_CONN_STATE_CONNECTED || + p_dev->dev_status == BTHH_CONN_STATE_CONNECTING) { + BTIF_TRACE_ERROR("%s: Error, device %s already connected.", __func__, + ADDRESS_TO_LOGGABLE_CSTR(*bd_addr)); ++ + return BT_STATUS_DONE; + } else if (p_dev->dev_status == BTHH_CONN_STATE_DISCONNECTING) { + BTIF_TRACE_ERROR("%s: Error, device %s is busy with (dis)connecting.", +@@ -1368,7 +1428,7 @@ static bt_status_t connect(RawAddress* bd_addr) { + * Returns bt_status_t + * + ******************************************************************************/ +-static bt_status_t disconnect(RawAddress* bd_addr) { ++static bt_status_t disconnect(RawAddress* bd_addr, bool reconnect_allowed) { + CHECK_BTHH_INIT(); + BTIF_TRACE_EVENT("BTHH: %s", __func__); + btif_hh_device_t* p_dev; +@@ -1380,6 +1440,16 @@ static bt_status_t disconnect(RawAddress* bd_addr) { + return BT_STATUS_UNHANDLED; + } + ++ if (!reconnect_allowed) { ++ LOG_INFO("Incoming reconnections disabled for device %s", ++ ADDRESS_TO_LOGGABLE_CSTR(*bd_addr)); ++ btif_hh_added_device_t* added_dev = btif_hh_find_added_dev(*bd_addr); ++ if (added_dev != nullptr && added_dev->reconnect_allowed) { ++ added_dev->reconnect_allowed = false; ++ btif_storage_set_hid_connection_policy(added_dev->bd_addr, false); ++ } ++ } ++ + p_dev = btif_hh_find_connected_dev_by_bda(*bd_addr); + if (!p_dev) { + BTIF_TRACE_ERROR("%s: Error, device %s not opened.", __func__, +@@ -1524,9 +1594,10 @@ static bt_status_t set_info(RawAddress* bd_addr, bthh_hid_info_t hid_info) { + (uint8_t*)osi_malloc(dscp_info.descriptor.dl_len); + memcpy(dscp_info.descriptor.dsc_list, &(hid_info.dsc_list), hid_info.dl_len); + +- if (btif_hh_add_added_dev(*bd_addr, hid_info.attr_mask)) { ++ if (btif_hh_add_added_dev(*bd_addr, hid_info.attr_mask, true)) { + BTA_HhAddDev(*bd_addr, hid_info.attr_mask, hid_info.sub_class, + hid_info.app_id, dscp_info); ++ btif_storage_set_hid_connection_policy(*bd_addr, true); + } + + osi_free_and_reset((void**)&dscp_info.descriptor.dsc_list); +diff --git a/system/btif/src/btif_profile_storage.cc b/system/btif/src/btif_profile_storage.cc +index eb423ba06d..92ad612c9f 100644 +--- a/system/btif/src/btif_profile_storage.cc ++++ b/system/btif/src/btif_profile_storage.cc +@@ -80,6 +80,7 @@ using bluetooth::groups::DeviceGroups; + #define BTIF_STORAGE_LEAUDIO_SOURCE_SUPPORTED_CONTEXT_TYPE \ + "SourceSupportedContextType" + #define BTIF_STORAGE_DEVICE_GROUP_BIN "DeviceGroupBin" ++#define BTIF_STORAGE_KEY_HID_RECONNECT_ALLOWED "HidReConnectAllowed" + + #define STORAGE_HID_ATRR_MASK_SIZE (4) + #define STORAGE_HID_SUB_CLASS_SIZE (2) +@@ -104,6 +105,49 @@ using bluetooth::groups::DeviceGroups; + STORAGE_HID_VERSION_SIZE + 1 + STORAGE_HID_CTRY_CODE_SIZE + 1 + \ + STORAGE_HID_DESC_LEN_SIZE + 1 + STORAGE_HID_DESC_MAX_SIZE + 1) + ++/******************************************************************************* ++ * ++ * Function btif_storage_set_hid_connection_policy ++ * ++ * Description Stores connection policy info in nvram ++ * ++ * Returns BT_STATUS_SUCCESS ++ * ++ ******************************************************************************/ ++bt_status_t btif_storage_set_hid_connection_policy(const RawAddress& addr, ++ bool reconnect_allowed) { ++ std::string bdstr = addr.ToString(); ++ ++ if (btif_config_set_int(bdstr, BTIF_STORAGE_KEY_HID_RECONNECT_ALLOWED, ++ reconnect_allowed)) { ++ return BT_STATUS_SUCCESS; ++ } else { ++ return BT_STATUS_FAIL; ++ } ++} ++ ++/******************************************************************************* ++ * ++ * Function btif_storage_get_hid_connection_policy ++ * ++ * Description get connection policy info from nvram ++ * ++ * Returns BT_STATUS_SUCCESS ++ * ++ ******************************************************************************/ ++bt_status_t btif_storage_get_hid_connection_policy(const RawAddress& addr, ++ bool* reconnect_allowed) { ++ std::string bdstr = addr.ToString(); ++ ++ // For backward compatibility, assume that the reconnection is allowed in the ++ // absence of the key ++ int value = 1; ++ btif_config_get_int(bdstr, BTIF_STORAGE_KEY_HID_RECONNECT_ALLOWED, &value); ++ *reconnect_allowed = (value != 0); ++ ++ return BT_STATUS_SUCCESS; ++} ++ + /******************************************************************************* + * + * Function btif_storage_add_hid_device_info +@@ -198,8 +242,11 @@ bt_status_t btif_storage_load_bonded_hid_info(void) { + (uint8_t*)dscp_info.descriptor.dsc_list, &len); + } + ++ bool reconnect_allowed = false; ++ btif_storage_get_hid_connection_policy(bd_addr, &reconnect_allowed); ++ + // add extracted information to BTA HH +- if (btif_hh_add_added_dev(bd_addr, attr_mask)) { ++ if (btif_hh_add_added_dev(bd_addr, attr_mask, reconnect_allowed)) { + BTA_HhAddDev(bd_addr, attr_mask, sub_class, app_id, dscp_info); + } + } +@@ -231,6 +278,7 @@ bt_status_t btif_storage_remove_hid_info(const RawAddress& remote_bd_addr) { + btif_config_remove(bdstr, "HidSSRMaxLatency"); + btif_config_remove(bdstr, "HidSSRMinTimeout"); + btif_config_remove(bdstr, "HidDescriptor"); ++ btif_config_remove(bdstr, BTIF_STORAGE_KEY_HID_RECONNECT_ALLOWED); + return BT_STATUS_SUCCESS; + } + +diff --git a/system/gd/rust/linux/stack/src/bluetooth.rs b/system/gd/rust/linux/stack/src/bluetooth.rs +index 910d04af78..bec54c5f44 100644 +--- a/system/gd/rust/linux/stack/src/bluetooth.rs ++++ b/system/gd/rust/linux/stack/src/bluetooth.rs +@@ -2446,7 +2446,7 @@ impl IBluetooth for Bluetooth { + if UuidHelper::is_profile_supported(&p) { + match p { + Profile::Hid | Profile::Hogp => { +- self.hh.as_ref().unwrap().disconnect(&mut addr.unwrap()); ++ self.hh.as_ref().unwrap().disconnect(&mut addr.unwrap(), true); + } + + Profile::A2dpSink +@@ -2574,7 +2574,7 @@ impl BtifHHCallbacks for Bluetooth { + "[{}]: Rejecting a unbonded device's attempt to connect to HID/HOG profiles", + DisplayAddress(&address) + ); +- self.hh.as_ref().unwrap().disconnect(&mut address); ++ self.hh.as_ref().unwrap().disconnect(&mut address, true); + } + } + +diff --git a/system/gd/rust/topshim/src/profiles/hid_host.rs b/system/gd/rust/topshim/src/profiles/hid_host.rs +index ce60d93354..76c5c685c4 100644 +--- a/system/gd/rust/topshim/src/profiles/hid_host.rs ++++ b/system/gd/rust/topshim/src/profiles/hid_host.rs +@@ -238,7 +238,7 @@ impl HidHost { + #[profile_enabled_or(BtStatus::NotReady)] + pub fn disconnect(&self, addr: &mut RawAddress) -> BtStatus { + let addr_ptr = LTCheckedPtrMut::from_ref(addr); +- BtStatus::from(ccall!(self, disconnect, addr_ptr.into())) ++ BtStatus::from(ccall!(self, disconnect, addr_ptr.into(), true)) + } + + #[profile_enabled_or(BtStatus::NotReady)] +diff --git a/system/include/hardware/bt_hh.h b/system/include/hardware/bt_hh.h +index f6e6e83abd..0700ed4a20 100644 +--- a/system/include/hardware/bt_hh.h ++++ b/system/include/hardware/bt_hh.h +@@ -179,7 +179,7 @@ typedef struct { + bt_status_t (*connect)(RawAddress* bd_addr); + + /** dis-connect from hid device */ +- bt_status_t (*disconnect)(RawAddress* bd_addr); ++ bt_status_t (*disconnect)(RawAddress* bd_addr, bool reconnect_allowed); + + /** Virtual UnPlug (VUP) the specified HID device */ + bt_status_t (*virtual_unplug)(RawAddress* bd_addr); +-- +2.46.1.824.gd892dcdcdd-goog + diff --git a/aosp_diff/preliminary/packages/modules/Bluetooth/0019-Resolve-incomplete-fix-for-SMP-authentication-bypass.bulletin.patch b/aosp_diff/preliminary/packages/modules/Bluetooth/0019-Resolve-incomplete-fix-for-SMP-authentication-bypass.bulletin.patch new file mode 100644 index 0000000000..e8e9328ecc --- /dev/null +++ b/aosp_diff/preliminary/packages/modules/Bluetooth/0019-Resolve-incomplete-fix-for-SMP-authentication-bypass.bulletin.patch @@ -0,0 +1,49 @@ +From 6ad3d749f7f632787f29710ce23736e10d2969bf Mon Sep 17 00:00:00 2001 +From: Brian Delwiche +Date: Mon, 14 Oct 2024 22:50:55 +0000 +Subject: [PATCH] Resolve incomplete fix for SMP authentication bypass + +Fix for b/25992313 was landed correctly on main, but in older branches +SMP contains identical functions smp_proc_init and smp_proc_rand, both +of which exhibit the problem, and only the former of which was patched. +This allows the problem to still appear on branches from sc-dev to +udc-dev. + +Add the logic to smp_proc_rand. + +Bug: 251514170 +Test: m com.android.btservices +Tag: #security +Ignore-AOSP-First: security +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:16a745956dbaff2d37d0e7afdf662314ea379f29) +Merged-In: I51e99c18a322a29632a6cac09ddb2b07bea482fc +Change-Id: I51e99c18a322a29632a6cac09ddb2b07bea482fc +--- + system/stack/smp/smp_act.cc | 11 +++++++++++ + 1 file changed, 11 insertions(+) + +diff --git a/system/stack/smp/smp_act.cc b/system/stack/smp/smp_act.cc +index a5b1581070..e7ce939380 100644 +--- a/system/stack/smp/smp_act.cc ++++ b/system/stack/smp/smp_act.cc +@@ -721,6 +721,17 @@ void smp_proc_rand(tSMP_CB* p_cb, tSMP_INT_DATA* p_data) { + return; + } + ++ if (!((p_cb->loc_auth_req & SMP_SC_SUPPORT_BIT) && ++ (p_cb->peer_auth_req & SMP_SC_SUPPORT_BIT)) && ++ !(p_cb->flags & SMP_PAIR_FLAGS_CMD_CONFIRM_SENT)) { ++ // in legacy pairing, the peer should send its rand after ++ // we send our confirm ++ tSMP_INT_DATA smp_int_data{}; ++ smp_int_data.status = SMP_INVALID_PARAMETERS; ++ smp_sm_event(p_cb, SMP_AUTH_CMPL_EVT, &smp_int_data); ++ return; ++ } ++ + /* save the SRand for comparison */ + STREAM_TO_ARRAY(p_cb->rrand.data(), p, OCTET16_LEN); + } +-- +2.46.1.824.gd892dcdcdd-goog + diff --git a/aosp_diff/preliminary/packages/modules/Connectivity/0001-netd-bpf-implement-ingress-discard-based-on-dstip-ifindex-.bulletin.patch b/aosp_diff/preliminary/packages/modules/Connectivity/0001-netd-bpf-implement-ingress-discard-based-on-dstip-ifindex-.bulletin.patch new file mode 100644 index 0000000000..25736d3fd5 --- /dev/null +++ b/aosp_diff/preliminary/packages/modules/Connectivity/0001-netd-bpf-implement-ingress-discard-based-on-dstip-ifindex-.bulletin.patch @@ -0,0 +1,124 @@ +From 4e80ab9ca5b9e7db8185cdde240ab0f01a2c0611 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Maciej=20=C5=BBenczykowski?= +Date: Tue, 29 Aug 2023 18:39:28 +0000 +Subject: [PATCH] netd bpf - implement ingress discard based on {dstip,ifindex} +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Cherry-pick of aosp/2731354 to backport VPN security fix to non-mainline +U devices. +This CL removes bpf_existence_test.cpp change in aosp/2731354 since u +branches use module prebuilt as default. + +Test: TreeHugger +Bug: 193031925 +Signed-off-by: Maciej Żenczykowski +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:e1a82cc1cc04ed49f022825010316cde7758c2fb) +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:807a915d2113781a9a42f47c6244729c91aa0d4b) +Merged-In: If30536c444d9a661a162b935b1dd28df2d88f9b7 +Change-Id: If30536c444d9a661a162b935b1dd28df2d88f9b7 +--- + bpf_progs/netd.c | 32 ++++++++++++++++++++++++++++++++ + bpf_progs/netd.h | 14 ++++++++++++++ + 2 files changed, 46 insertions(+) + +diff --git a/bpf_progs/netd.c b/bpf_progs/netd.c +index 39dff7f30b..da2046efb0 100644 +--- a/bpf_progs/netd.c ++++ b/bpf_progs/netd.c +@@ -92,6 +92,8 @@ DEFINE_BPF_MAP_RO_NETD(stats_map_B, HASH, StatsKey, StatsValue, STATS_MAP_SIZE) + DEFINE_BPF_MAP_NO_NETD(iface_stats_map, HASH, uint32_t, StatsValue, IFACE_STATS_MAP_SIZE) + DEFINE_BPF_MAP_NO_NETD(uid_owner_map, HASH, uint32_t, UidOwnerValue, UID_OWNER_MAP_SIZE) + DEFINE_BPF_MAP_RW_NETD(uid_permission_map, HASH, uint32_t, uint8_t, UID_OWNER_MAP_SIZE) ++DEFINE_BPF_MAP_NO_NETD(ingress_discard_map, HASH, IngressDiscardKey, IngressDiscardValue, ++ INGRESS_DISCARD_MAP_SIZE) + + /* never actually used from ebpf */ + DEFINE_BPF_MAP_NO_NETD(iface_index_name_map, HASH, uint32_t, IfaceValue, IFACE_INDEX_NAME_MAP_SIZE) +@@ -343,6 +345,35 @@ static __always_inline inline BpfConfig getConfig(uint32_t configKey) { + return *config; + } + ++static __always_inline inline bool ingress_should_discard(struct __sk_buff* skb, ++ const unsigned kver) { ++ // Require 4.19, since earlier kernels don't have bpf_skb_load_bytes_relative() which ++ // provides relative to L3 header reads. Without that we could fetch the wrong bytes. ++ // Additionally earlier bpf verifiers are much harder to please. ++ if (kver < KVER(4, 19, 0)) return false; ++ ++ IngressDiscardKey k = {}; ++ if (skb->protocol == htons(ETH_P_IP)) { ++ k.daddr.s6_addr32[2] = htonl(0xFFFF); ++ (void)bpf_skb_load_bytes_net(skb, IP4_OFFSET(daddr), &k.daddr.s6_addr32[3], 4, kver); ++ } else if (skb->protocol == htons(ETH_P_IPV6)) { ++ (void)bpf_skb_load_bytes_net(skb, IP6_OFFSET(daddr), &k.daddr, sizeof(k.daddr), kver); ++ } else { ++ return false; // non IPv4/IPv6, so no IP to match on ++ } ++ ++ // we didn't check for load success, because destination bytes will be zeroed if ++ // bpf_skb_load_bytes_net() fails, instead we rely on daddr of '::' and '::ffff:0.0.0.0' ++ // never being present in the map itself ++ ++ IngressDiscardValue* v = bpf_ingress_discard_map_lookup_elem(&k); ++ if (!v) return false; // lookup failure -> no protection in place -> allow ++ // if (skb->ifindex == 1) return false; // allow 'lo', but can't happen - see callsite ++ if (skb->ifindex == v->iif[0]) return false; // allowed interface ++ if (skb->ifindex == v->iif[1]) return false; // allowed interface ++ return true; // disallowed interface ++} ++ + // DROP_IF_SET is set of rules that DROP if rule is globally enabled, and per-uid bit is set + #define DROP_IF_SET (STANDBY_MATCH | OEM_DENY_1_MATCH | OEM_DENY_2_MATCH | OEM_DENY_3_MATCH) + // DROP_IF_UNSET is set of rules that should DROP if globally enabled, and per-uid bit is NOT set +@@ -368,6 +399,7 @@ static __always_inline inline int bpf_owner_match(struct __sk_buff* skb, uint32_ + if (enabledRules & (DROP_IF_SET | DROP_IF_UNSET) & (uidRules ^ DROP_IF_UNSET)) return DROP; + + if (!egress && skb->ifindex != 1) { ++ if (ingress_should_discard(skb, kver)) return DROP; + if (uidRules & IIF_MATCH) { + if (allowed_iif && skb->ifindex != allowed_iif) { + // Drops packets not coming from lo nor the allowed interface +diff --git a/bpf_progs/netd.h b/bpf_progs/netd.h +index be604f9a1a..c85e40e7e0 100644 +--- a/bpf_progs/netd.h ++++ b/bpf_progs/netd.h +@@ -121,6 +121,7 @@ static const int IFACE_INDEX_NAME_MAP_SIZE = 1000; + static const int IFACE_STATS_MAP_SIZE = 1000; + static const int CONFIGURATION_MAP_SIZE = 2; + static const int UID_OWNER_MAP_SIZE = 4000; ++static const int INGRESS_DISCARD_MAP_SIZE = 100; + static const int PACKET_TRACE_BUF_SIZE = 32 * 1024; + + #ifdef __cplusplus +@@ -165,6 +166,7 @@ ASSERT_STRING_EQUAL(XT_BPF_DENYLIST_PROG_PATH, BPF_NETD_PATH "prog_netd_skfilte + #define CONFIGURATION_MAP_PATH BPF_NETD_PATH "map_netd_configuration_map" + #define UID_OWNER_MAP_PATH BPF_NETD_PATH "map_netd_uid_owner_map" + #define UID_PERMISSION_MAP_PATH BPF_NETD_PATH "map_netd_uid_permission_map" ++#define INGRESS_DISCARD_MAP_PATH BPF_NETD_PATH "map_netd_ingress_discard_map" + #define PACKET_TRACE_RINGBUF_PATH BPF_NETD_PATH "map_netd_packet_trace_ringbuf" + #define PACKET_TRACE_ENABLED_MAP_PATH BPF_NETD_PATH "map_netd_packet_trace_enabled_map" + +@@ -213,6 +215,18 @@ typedef struct { + } UidOwnerValue; + STRUCT_SIZE(UidOwnerValue, 2 * 4); // 8 + ++typedef struct { ++ // The destination ip of the incoming packet. IPv4 uses IPv4-mapped IPv6 address format. ++ struct in6_addr daddr; ++} IngressDiscardKey; ++STRUCT_SIZE(IngressDiscardKey, 16); // 16 ++ ++typedef struct { ++ // Allowed interface indexes. Use same value multiple times if you just want to match 1 value. ++ uint32_t iif[2]; ++} IngressDiscardValue; ++STRUCT_SIZE(IngressDiscardValue, 2 * 4); // 8 ++ + // Entry in the configuration map that stores which UID rules are enabled. + #define UID_RULES_CONFIGURATION_KEY 0 + // Entry in the configuration map that stores which stats map is currently in use. +-- +2.46.1.824.gd892dcdcdd-goog + diff --git a/aosp_diff/preliminary/packages/modules/Connectivity/0002-Add-java-class-for-Ingress-discard-bpf-map-key-value.bulletin.patch b/aosp_diff/preliminary/packages/modules/Connectivity/0002-Add-java-class-for-Ingress-discard-bpf-map-key-value.bulletin.patch new file mode 100644 index 0000000000..817ad4e911 --- /dev/null +++ b/aosp_diff/preliminary/packages/modules/Connectivity/0002-Add-java-class-for-Ingress-discard-bpf-map-key-value.bulletin.patch @@ -0,0 +1,102 @@ +From 9eb0da27c4ecff37f7b6d2328b8dee6ba766ad1a Mon Sep 17 00:00:00 2001 +From: Motomu Utsumi +Date: Mon, 23 Oct 2023 16:53:51 +0900 +Subject: [PATCH] Add java class for Ingress discard bpf map key value + +Cherry-pick of aosp/2795708 to backport VPN security fix to non-mainline +U devices. + +Bug: 193031925 +Test: TH +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:9024892db8de73eea86967880b0cfa4470db079f) +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:a25019c5917a6b1a5070bff2d897963cbecb1b5d) +Merged-In: Ib403099df5cefa3cbad52e3fd568b1a5047adcd4 +Change-Id: Ib403099df5cefa3cbad52e3fd568b1a5047adcd4 +--- + .../module/util/bpf/IngressDiscardKey.java | 32 +++++++++++++++++ + .../module/util/bpf/IngressDiscardValue.java | 34 +++++++++++++++++++ + 2 files changed, 66 insertions(+) + create mode 100644 common/src/com/android/net/module/util/bpf/IngressDiscardKey.java + create mode 100644 common/src/com/android/net/module/util/bpf/IngressDiscardValue.java + +diff --git a/common/src/com/android/net/module/util/bpf/IngressDiscardKey.java b/common/src/com/android/net/module/util/bpf/IngressDiscardKey.java +new file mode 100644 +index 0000000000..eabcf3cce7 +--- /dev/null ++++ b/common/src/com/android/net/module/util/bpf/IngressDiscardKey.java +@@ -0,0 +1,32 @@ ++/* ++ * Copyright (C) 2023 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 com.android.net.module.util.bpf; ++ ++import com.android.net.module.util.Struct; ++ ++import java.net.Inet6Address; ++ ++/** Key type for ingress discard map */ ++public class IngressDiscardKey extends Struct { ++ // The destination ip of the incoming packet. IPv4 uses IPv4-mapped IPv6 address. ++ @Field(order = 0, type = Type.Ipv6Address) ++ public final Inet6Address dstAddr; ++ ++ public IngressDiscardKey(final Inet6Address dstAddr) { ++ this.dstAddr = dstAddr; ++ } ++} +diff --git a/common/src/com/android/net/module/util/bpf/IngressDiscardValue.java b/common/src/com/android/net/module/util/bpf/IngressDiscardValue.java +new file mode 100644 +index 0000000000..7df3620f41 +--- /dev/null ++++ b/common/src/com/android/net/module/util/bpf/IngressDiscardValue.java +@@ -0,0 +1,34 @@ ++/* ++ * Copyright (C) 2023 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 com.android.net.module.util.bpf; ++ ++import com.android.net.module.util.Struct; ++ ++/** Value type for ingress discard map */ ++public class IngressDiscardValue extends Struct { ++ // Allowed interface indexes. ++ // Use the same value for iif1 and iif2 if there is only a single allowed interface index. ++ @Field(order = 0, type = Type.S32) ++ public final int iif1; ++ @Field(order = 1, type = Type.S32) ++ public final int iif2; ++ ++ public IngressDiscardValue(final int iif1, final int iif2) { ++ this.iif1 = iif1; ++ this.iif2 = iif2; ++ } ++} +-- +2.46.1.824.gd892dcdcdd-goog + diff --git a/aosp_diff/preliminary/packages/modules/Connectivity/0003-Add-methods-for-updating-ingressDiscardRule-bpf-map-to-BpfNetMap.bulletin.patch b/aosp_diff/preliminary/packages/modules/Connectivity/0003-Add-methods-for-updating-ingressDiscardRule-bpf-map-to-BpfNetMap.bulletin.patch new file mode 100644 index 0000000000..29840305b0 --- /dev/null +++ b/aosp_diff/preliminary/packages/modules/Connectivity/0003-Add-methods-for-updating-ingressDiscardRule-bpf-map-to-BpfNetMap.bulletin.patch @@ -0,0 +1,266 @@ +From de7b97573ac92be4cff6ca71a1837b2fe0dcbbab Mon Sep 17 00:00:00 2001 +From: Motomu Utsumi +Date: Wed, 31 Jul 2024 18:49:49 +0900 +Subject: [PATCH] Add methods for updating ingressDiscardRule bpf map to + BpfNetMaps + +Cherry-pick of aosp/2795710 to backport VPN security fix to non-mainline +U devices with minor conflicts resolution. +Since U branches use module prebuilt and new tests in +BpfNetMapsTest.java relies on InetAddressUtils change in this topic, +this CL does not add tests for setIngressDiscardRule and +removeIngressDiscardRule. + +Bug: 193031925 +Test: NetworkStaticLibsTests +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:b01647922d2b393fd705bf7fd68fbe10ccdffdc4) +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:63e35f76ce93bc4d4ecfe7802f6fba84cb99d0f0) +Merged-In: Ie7057eb0273023767489ea72ea1faf9598724ef9 +Change-Id: Ie7057eb0273023767489ea72ea1faf9598724ef9 +--- + .../module/util/bpf/IngressDiscardKey.java | 13 +++ + .../src/com/android/server/BpfNetMaps.java | 86 +++++++++++++++++++ + .../com/android/server/BpfNetMapsTest.java | 6 ++ + 3 files changed, 105 insertions(+) + +diff --git a/common/src/com/android/net/module/util/bpf/IngressDiscardKey.java b/common/src/com/android/net/module/util/bpf/IngressDiscardKey.java +index eabcf3cce7..9fefb521d9 100644 +--- a/common/src/com/android/net/module/util/bpf/IngressDiscardKey.java ++++ b/common/src/com/android/net/module/util/bpf/IngressDiscardKey.java +@@ -16,9 +16,12 @@ + + package com.android.net.module.util.bpf; + ++import com.android.net.module.util.InetAddressUtils; + import com.android.net.module.util.Struct; + ++import java.net.Inet4Address; + import java.net.Inet6Address; ++import java.net.InetAddress; + + /** Key type for ingress discard map */ + public class IngressDiscardKey extends Struct { +@@ -29,4 +32,14 @@ public class IngressDiscardKey extends Struct { + public IngressDiscardKey(final Inet6Address dstAddr) { + this.dstAddr = dstAddr; + } ++ ++ private static Inet6Address getInet6Address(final InetAddress addr) { ++ return (addr instanceof Inet4Address) ++ ? InetAddressUtils.v4MappedV6Address((Inet4Address) addr) ++ : (Inet6Address) addr; ++ } ++ ++ public IngressDiscardKey(final InetAddress dstAddr) { ++ this(getInet6Address(dstAddr)); ++ } + } +diff --git a/service/src/com/android/server/BpfNetMaps.java b/service/src/com/android/server/BpfNetMaps.java +index ec168dd0ab..98c73d04a7 100644 +--- a/service/src/com/android/server/BpfNetMaps.java ++++ b/service/src/com/android/server/BpfNetMaps.java +@@ -40,6 +40,7 @@ import static com.android.server.ConnectivityStatsLog.NETWORK_BPF_MAP_INFO; + import android.app.StatsManager; + import android.content.Context; + import android.net.INetd; ++import android.os.Build; + import android.os.RemoteException; + import android.os.ServiceSpecificException; + import android.provider.DeviceConfig; +@@ -51,6 +52,8 @@ import android.util.Log; + import android.util.Pair; + import android.util.StatsEvent; + ++import androidx.annotation.RequiresApi; ++ + import com.android.internal.annotations.VisibleForTesting; + import com.android.modules.utils.BackgroundThread; + import com.android.modules.utils.build.SdkLevel; +@@ -64,9 +67,12 @@ import com.android.net.module.util.Struct.U32; + import com.android.net.module.util.Struct.U8; + import com.android.net.module.util.bpf.CookieTagMapKey; + import com.android.net.module.util.bpf.CookieTagMapValue; ++import com.android.net.module.util.bpf.IngressDiscardKey; ++import com.android.net.module.util.bpf.IngressDiscardValue; + + import java.io.FileDescriptor; + import java.io.IOException; ++import java.net.InetAddress; + import java.util.Arrays; + import java.util.List; + import java.util.Set; +@@ -113,6 +119,8 @@ public class BpfNetMaps { + "/sys/fs/bpf/netd_shared/map_netd_uid_permission_map"; + private static final String COOKIE_TAG_MAP_PATH = + "/sys/fs/bpf/netd_shared/map_netd_cookie_tag_map"; ++ public static final String INGRESS_DISCARD_MAP_PATH = ++ "/sys/fs/bpf/netd_shared/map_netd_ingress_discard_map"; + private static final S32 UID_RULES_CONFIGURATION_KEY = new S32(0); + private static final S32 CURRENT_STATS_MAP_CONFIGURATION_KEY = new S32(1); + private static final long UID_RULES_DEFAULT_CONFIGURATION = 0; +@@ -124,6 +132,7 @@ public class BpfNetMaps { + private static IBpfMap sUidOwnerMap = null; + private static IBpfMap sUidPermissionMap = null; + private static IBpfMap sCookieTagMap = null; ++ private static IBpfMap sIngressDiscardMap = null; + + // LINT.IfChange(match_type) + @VisibleForTesting public static final long NO_MATCH = 0; +@@ -201,6 +210,15 @@ public class BpfNetMaps { + sCookieTagMap = cookieTagMap; + } + ++ /** ++ * Set ingressDiscardMap for test. ++ */ ++ @VisibleForTesting ++ public static void setIngressDiscardMapForTest( ++ IBpfMap ingressDiscardMap) { ++ sIngressDiscardMap = ingressDiscardMap; ++ } ++ + private static IBpfMap getConfigurationMap() { + try { + return new BpfMap<>( +@@ -237,6 +255,15 @@ public class BpfNetMaps { + } + } + ++ private static IBpfMap getIngressDiscardMap() { ++ try { ++ return new BpfMap<>(INGRESS_DISCARD_MAP_PATH, BpfMap.BPF_F_RDWR, ++ IngressDiscardKey.class, IngressDiscardValue.class); ++ } catch (ErrnoException e) { ++ throw new IllegalStateException("Cannot open ingress discard map", e); ++ } ++ } ++ + private static void initBpfMaps() { + if (sConfigurationMap == null) { + sConfigurationMap = getConfigurationMap(); +@@ -270,6 +297,15 @@ public class BpfNetMaps { + if (sCookieTagMap == null) { + sCookieTagMap = getCookieTagMap(); + } ++ ++ if (sIngressDiscardMap == null) { ++ sIngressDiscardMap = getIngressDiscardMap(); ++ } ++ try { ++ sIngressDiscardMap.clear(); ++ } catch (ErrnoException e) { ++ throw new IllegalStateException("Failed to initialize ingress discard map", e); ++ } + } + + /** +@@ -307,6 +343,13 @@ public class BpfNetMaps { + return Os.if_nametoindex(ifName); + } + ++ /** ++ * Get interface name ++ */ ++ public String getIfName(final int ifIndex) { ++ return Os.if_indextoname(ifIndex); ++ } ++ + /** + * Call synchronize_rcu() + */ +@@ -987,6 +1030,45 @@ public class BpfNetMaps { + } + } + ++ /** ++ * Set ingress discard rule ++ * ++ * @param address target address to set the ingress discard rule ++ * @param iface allowed interface ++ */ ++ @RequiresApi(Build.VERSION_CODES.TIRAMISU) ++ public void setIngressDiscardRule(final InetAddress address, final String iface) { ++ throwIfPreT("setIngressDiscardRule is not available on pre-T devices"); ++ final int ifIndex = mDeps.getIfIndex(iface); ++ if (ifIndex == 0) { ++ Log.e(TAG, "Failed to get if index, skip setting ingress discard rule for " + address ++ + "(" + iface + ")"); ++ return; ++ } ++ try { ++ sIngressDiscardMap.updateEntry(new IngressDiscardKey(address), ++ new IngressDiscardValue(ifIndex, ifIndex)); ++ } catch (ErrnoException e) { ++ Log.e(TAG, "Failed to set ingress discard rule for " + address + "(" ++ + iface + "), " + e); ++ } ++ } ++ ++ /** ++ * Remove ingress discard rule ++ * ++ * @param address target address to remove the ingress discard rule ++ */ ++ @RequiresApi(Build.VERSION_CODES.TIRAMISU) ++ public void removeIngressDiscardRule(final InetAddress address) { ++ throwIfPreT("removeIngressDiscardRule is not available on pre-T devices"); ++ try { ++ sIngressDiscardMap.deleteEntry(new IngressDiscardKey(address)); ++ } catch (ErrnoException e) { ++ Log.e(TAG, "Failed to remove ingress discard rule for " + address + ", " + e); ++ } ++ } ++ + /** Register callback for statsd to pull atom. */ + public void setPullAtomCallback(final Context context) { + throwIfPreT("setPullAtomCallback is not available on pre-T devices"); +@@ -1136,6 +1218,10 @@ public class BpfNetMaps { + }); + BpfDump.dumpMap(sUidPermissionMap, pw, "sUidPermissionMap", + (uid, permission) -> uid.val + " " + permissionToString(permission.val)); ++ BpfDump.dumpMap(sIngressDiscardMap, pw, "sIngressDiscardMap", ++ (key, value) -> "[" + key.dstAddr + "]: " ++ + value.iif1 + "(" + mDeps.getIfName(value.iif1) + "), " ++ + value.iif2 + "(" + mDeps.getIfName(value.iif2) + ")"); + pw.decreaseIndent(); + } + } +diff --git a/tests/unit/java/com/android/server/BpfNetMapsTest.java b/tests/unit/java/com/android/server/BpfNetMapsTest.java +index 19fa41df32..319af501d8 100644 +--- a/tests/unit/java/com/android/server/BpfNetMapsTest.java ++++ b/tests/unit/java/com/android/server/BpfNetMapsTest.java +@@ -78,6 +78,8 @@ import com.android.net.module.util.Struct.U32; + import com.android.net.module.util.Struct.U8; + import com.android.net.module.util.bpf.CookieTagMapKey; + import com.android.net.module.util.bpf.CookieTagMapValue; ++import com.android.net.module.util.bpf.IngressDiscardKey; ++import com.android.net.module.util.bpf.IngressDiscardValue; + import com.android.testutils.DevSdkIgnoreRule; + import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter; + import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo; +@@ -139,11 +141,14 @@ public final class BpfNetMapsTest { + private final IBpfMap mUidPermissionMap = new TestBpfMap<>(S32.class, U8.class); + private final IBpfMap mCookieTagMap = + spy(new TestBpfMap<>(CookieTagMapKey.class, CookieTagMapValue.class)); ++ private final IBpfMap mIngressDiscardMap = ++ new TestBpfMap<>(IngressDiscardKey.class, IngressDiscardValue.class); + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + doReturn(TEST_IF_INDEX).when(mDeps).getIfIndex(TEST_IF_NAME); ++ doReturn(TEST_IF_NAME).when(mDeps).getIfName(TEST_IF_INDEX); + doReturn(0).when(mDeps).synchronizeKernelRCU(); + BpfNetMaps.setEnableJavaBpfMapForTest(true /* enable */); + BpfNetMaps.setConfigurationMapForTest(mConfigurationMap); +@@ -153,6 +158,7 @@ public final class BpfNetMapsTest { + BpfNetMaps.setUidOwnerMapForTest(mUidOwnerMap); + BpfNetMaps.setUidPermissionMapForTest(mUidPermissionMap); + BpfNetMaps.setCookieTagMapForTest(mCookieTagMap); ++ BpfNetMaps.setIngressDiscardMapForTest(mIngressDiscardMap); + mBpfNetMaps = new BpfNetMaps(mContext, mNetd, mDeps); + } + +-- +2.46.1.824.gd892dcdcdd-goog + diff --git a/aosp_diff/preliminary/packages/modules/Connectivity/0004-Drop-packets-to-VPN-address-ingressing-via-non-VPN-interface.bulletin.patch b/aosp_diff/preliminary/packages/modules/Connectivity/0004-Drop-packets-to-VPN-address-ingressing-via-non-VPN-interface.bulletin.patch new file mode 100644 index 0000000000..63b50c7683 --- /dev/null +++ b/aosp_diff/preliminary/packages/modules/Connectivity/0004-Drop-packets-to-VPN-address-ingressing-via-non-VPN-interface.bulletin.patch @@ -0,0 +1,156 @@ +From 1a0dd8ccde71c2252132d60e5b897fa2f569cc76 Mon Sep 17 00:00:00 2001 +From: Motomu Utsumi +Date: Wed, 31 Jul 2024 18:59:17 +0900 +Subject: [PATCH] Drop packets to VPN address ingressing via non-VPN interface + +Cherry-pick of aosp/2795711 to backport VPN security fix to non-mainline +U devices. +Since isTetheringFeatureNotChickenedOut is not available on U branch, +this feature is enabled on T+ devices without kill switch. +Also, this CL removes test changes since CSTest utilities are not +available on u branches. + +When there are addresses that are used by a single VPN interface, +ConnectivityService sets ingress discard rules to drop packets to this +address from the non-Vpn interfaces + +Bug: 193031925 +Test: TH +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:d493a3aa7dcca3219b139616c9de3c6ee8181f86) +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:1027bc813ea6a5b97bc0f55401e01f5eec91e94a) +Merged-In: I5933d42f3fd257139fb803ede1391e10d9d1211b +Change-Id: I5933d42f3fd257139fb803ede1391e10d9d1211b +--- + .../android/server/ConnectivityService.java | 88 +++++++++++++++++++ + 1 file changed, 88 insertions(+) + +diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java +index 120486c1df..0882870770 100755 +--- a/service/src/com/android/server/ConnectivityService.java ++++ b/service/src/com/android/server/ConnectivityService.java +@@ -936,6 +936,9 @@ public class ConnectivityService extends IConnectivityManager.Stub + private final Map + mSelfCertifiedCapabilityCache = new HashMap<>(); + ++ // Flag to drop packets to VPN addresses ingressing via non-VPN interfaces. ++ private final boolean mIngressToVpnAddressFiltering; ++ + /** + * Implements support for the legacy "one network per network type" model. + * +@@ -1798,6 +1801,7 @@ public class ConnectivityService extends IConnectivityManager.Stub + activityManager.registerUidFrozenStateChangedCallback( + (Runnable r) -> r.run(), frozenStateChangedCallback); + } ++ mIngressToVpnAddressFiltering = mDeps.isAtLeastT(); + } + + /** +@@ -4776,6 +4780,7 @@ public class ConnectivityService extends IConnectivityManager.Stub + if (mDscpPolicyTracker != null) { + mDscpPolicyTracker.removeAllDscpPolicies(nai, false); + } ++ updateIngressToVpnAddressFiltering(null, nai.linkProperties, nai); + try { + mNetd.networkDestroy(nai.network.getNetId()); + } catch (RemoteException | ServiceSpecificException e) { +@@ -8014,6 +8019,8 @@ public class ConnectivityService extends IConnectivityManager.Stub + // new interface (the interface name -> index map becomes initialized) + updateVpnFiltering(newLp, oldLp, networkAgent); + ++ updateIngressToVpnAddressFiltering(newLp, oldLp, networkAgent); ++ + updateMtu(newLp, oldLp); + // TODO - figure out what to do for clat + // for (LinkProperties lp : newLp.getStackedLinks()) { +@@ -8337,6 +8344,87 @@ public class ConnectivityService extends IConnectivityManager.Stub + } + } + ++ /** ++ * Returns ingress discard rules to drop packets to VPN addresses ingressing via non-VPN ++ * interfaces. ++ * Ingress discard rule is added to the address iff ++ * 1. The address is not a link local address ++ * 2. The address is used by a single VPN interface and not used by any other ++ * interfaces even non-VPN ones ++ * This method can be called during network disconnects, when nai has already been removed from ++ * mNetworkAgentInfos. ++ * ++ * @param nai This method generates rules assuming lp of this nai is the lp at the second ++ * argument. ++ * @param lp This method generates rules assuming lp of nai at the first argument is this lp. ++ * Caller passes old lp to generate old rules and new lp to generate new rules. ++ * @return ingress discard rules. Set of pairs of addresses and interface names ++ */ ++ private Set> generateIngressDiscardRules( ++ @NonNull final NetworkAgentInfo nai, @Nullable final LinkProperties lp) { ++ Set nais = new ArraySet<>(mNetworkAgentInfos); ++ nais.add(nai); ++ // Determine how many networks each IP address is currently configured on. ++ // Ingress rules are added only for IP addresses that are configured on single interface. ++ final Map addressOwnerCounts = new ArrayMap<>(); ++ for (final NetworkAgentInfo agent : nais) { ++ if (agent.isDestroyed()) { ++ continue; ++ } ++ final LinkProperties agentLp = (nai == agent) ? lp : agent.linkProperties; ++ if (agentLp == null) { ++ continue; ++ } ++ for (final InetAddress addr: agentLp.getAllAddresses()) { ++ addressOwnerCounts.put(addr, addressOwnerCounts.getOrDefault(addr, 0) + 1); ++ } ++ } ++ ++ // Iterates all networks instead of only generating rule for nai that was passed in since ++ // lp of the nai change could cause/resolve address collision and result in affecting rule ++ // for different network. ++ final Set> ingressDiscardRules = new ArraySet<>(); ++ for (final NetworkAgentInfo agent : nais) { ++ if (!agent.isVPN() || agent.isDestroyed()) { ++ continue; ++ } ++ final LinkProperties agentLp = (nai == agent) ? lp : agent.linkProperties; ++ if (agentLp == null || agentLp.getInterfaceName() == null) { ++ continue; ++ } ++ ++ for (final InetAddress addr: agentLp.getAllAddresses()) { ++ if (addressOwnerCounts.get(addr) == 1 && !addr.isLinkLocalAddress()) { ++ ingressDiscardRules.add(new Pair<>(addr, agentLp.getInterfaceName())); ++ } ++ } ++ } ++ return ingressDiscardRules; ++ } ++ ++ private void updateIngressToVpnAddressFiltering(@Nullable LinkProperties newLp, ++ @Nullable LinkProperties oldLp, @NonNull NetworkAgentInfo nai) { ++ // Having isAtleastT to avoid NewApi linter error (b/303382209) ++ if (!mIngressToVpnAddressFiltering || !mDeps.isAtLeastT()) { ++ return; ++ } ++ final CompareOrUpdateResult> ruleDiff = ++ new CompareOrUpdateResult<>( ++ generateIngressDiscardRules(nai, oldLp), ++ generateIngressDiscardRules(nai, newLp), ++ (rule) -> rule.first); ++ for (Pair rule: ruleDiff.removed) { ++ mBpfNetMaps.removeIngressDiscardRule(rule.first); ++ } ++ for (Pair rule: ruleDiff.added) { ++ mBpfNetMaps.setIngressDiscardRule(rule.first, rule.second); ++ } ++ // setIngressDiscardRule overrides the existing rule ++ for (Pair rule: ruleDiff.updated) { ++ mBpfNetMaps.setIngressDiscardRule(rule.first, rule.second); ++ } ++ } ++ + private void updateWakeOnLan(@NonNull LinkProperties lp) { + if (mWolSupportedInterfaces == null) { + mWolSupportedInterfaces = new ArraySet<>(mResources.get().getStringArray( +-- +2.46.1.824.gd892dcdcdd-goog + diff --git a/aosp_diff/preliminary/packages/modules/Connectivity/0005-Skip-adding-ingress-discard-rule-to-legacy-VPN.bulletin.patch b/aosp_diff/preliminary/packages/modules/Connectivity/0005-Skip-adding-ingress-discard-rule-to-legacy-VPN.bulletin.patch new file mode 100644 index 0000000000..292ac12845 --- /dev/null +++ b/aosp_diff/preliminary/packages/modules/Connectivity/0005-Skip-adding-ingress-discard-rule-to-legacy-VPN.bulletin.patch @@ -0,0 +1,50 @@ +From c16addaec604d724cf5296f89e606d558128d0cc Mon Sep 17 00:00:00 2001 +From: Motomu Utsumi +Date: Thu, 1 Aug 2024 21:27:12 +0900 +Subject: [PATCH] Skip adding ingress discard rule to legacy VPN + +Cherry-pick of aosp/3201971 to backport VPN security fix to non-mainline +U devices. + +Some legacy VPNs need to receive packets to VPN address via +non-VPN interface. + +Bug: 193031925 +Test: TH +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:5441470a6a04f36369ec79c3eff3a72fc47ca9e3) +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:717bb36e5963c2dc4c315b7d58f0c7b3d85fcf31) +Merged-In: If4f6b095a719a0abcb6254c522beac5d45110d4d +Change-Id: If4f6b095a719a0abcb6254c522beac5d45110d4d +--- + service/src/com/android/server/ConnectivityService.java | 7 +++++-- + 1 file changed, 5 insertions(+), 2 deletions(-) + +diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java +index 0882870770..523062c845 100755 +--- a/service/src/com/android/server/ConnectivityService.java ++++ b/service/src/com/android/server/ConnectivityService.java +@@ -8349,8 +8349,10 @@ public class ConnectivityService extends IConnectivityManager.Stub + * interfaces. + * Ingress discard rule is added to the address iff + * 1. The address is not a link local address +- * 2. The address is used by a single VPN interface and not used by any other ++ * 2. The address is used by a single non-Legacy VPN interface and not used by any other + * interfaces even non-VPN ones ++ * Ingress discard rule is not be added to Legacy VPN since some Legacy VPNs need to receive ++ * packet to VPN address via non-VPN interface. + * This method can be called during network disconnects, when nai has already been removed from + * mNetworkAgentInfos. + * +@@ -8385,7 +8387,8 @@ public class ConnectivityService extends IConnectivityManager.Stub + // for different network. + final Set> ingressDiscardRules = new ArraySet<>(); + for (final NetworkAgentInfo agent : nais) { +- if (!agent.isVPN() || agent.isDestroyed()) { ++ if (!agent.isVPN() || agent.isDestroyed() ++ || getVpnType(agent) == VpnManager.TYPE_VPN_LEGACY) { + continue; + } + final LinkProperties agentLp = (nai == agent) ? lp : agent.linkProperties; +-- +2.46.1.824.gd892dcdcdd-goog + diff --git a/aosp_diff/preliminary/packages/modules/Connectivity/0006-Skip-adding-ingress-discard-rule-to-OEM-VPN.bulletin.patch b/aosp_diff/preliminary/packages/modules/Connectivity/0006-Skip-adding-ingress-discard-rule-to-OEM-VPN.bulletin.patch new file mode 100644 index 0000000000..e043f21853 --- /dev/null +++ b/aosp_diff/preliminary/packages/modules/Connectivity/0006-Skip-adding-ingress-discard-rule-to-OEM-VPN.bulletin.patch @@ -0,0 +1,55 @@ +From d5d5e5749316d31a63b9b8b132e273d29364401b Mon Sep 17 00:00:00 2001 +From: Motomu Utsumi +Date: Tue, 6 Aug 2024 19:00:22 +0900 +Subject: [PATCH] Skip adding ingress discard rule to OEM VPN + +Cherry-pick of aosp/3208090 to backport VPN security fix to non-mainline +U devices. + +OEM VPNs might need to receive packets to VPN address via +non-VPN interface. + +Bug: 193031925 +Test: TH +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:df163f70fd3f456604019072b796eaeab71418ae) +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:595c611192c340588278b88686e095b13e883929) +Merged-In: I6c0080e8205410f4b6a389b793d56b63ebcc5e95 +Change-Id: I6c0080e8205410f4b6a389b793d56b63ebcc5e95 +--- + .../src/com/android/server/ConnectivityService.java | 12 +++++++----- + 1 file changed, 7 insertions(+), 5 deletions(-) + +diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java +index 523062c845..a12f3cd5e2 100755 +--- a/service/src/com/android/server/ConnectivityService.java ++++ b/service/src/com/android/server/ConnectivityService.java +@@ -8349,10 +8349,10 @@ public class ConnectivityService extends IConnectivityManager.Stub + * interfaces. + * Ingress discard rule is added to the address iff + * 1. The address is not a link local address +- * 2. The address is used by a single non-Legacy VPN interface and not used by any other +- * interfaces even non-VPN ones +- * Ingress discard rule is not be added to Legacy VPN since some Legacy VPNs need to receive +- * packet to VPN address via non-VPN interface. ++ * 2. The address is used by a single interface of VPN whose VPN type is not TYPE_VPN_LEGACY ++ * or TYPE_VPN_OEM and the address is not used by any other interfaces even non-VPN ones ++ * Ingress discard rule is not be added to TYPE_VPN_LEGACY or TYPE_VPN_OEM VPN since these VPNs ++ * might need to receive packet to VPN address via non-VPN interface. + * This method can be called during network disconnects, when nai has already been removed from + * mNetworkAgentInfos. + * +@@ -8387,8 +8387,10 @@ public class ConnectivityService extends IConnectivityManager.Stub + // for different network. + final Set> ingressDiscardRules = new ArraySet<>(); + for (final NetworkAgentInfo agent : nais) { ++ final int vpnType = getVpnType(agent); + if (!agent.isVPN() || agent.isDestroyed() +- || getVpnType(agent) == VpnManager.TYPE_VPN_LEGACY) { ++ || vpnType == VpnManager.TYPE_VPN_LEGACY ++ || vpnType == VpnManager.TYPE_VPN_OEM) { + continue; + } + final LinkProperties agentLp = (nai == agent) ? lp : agent.linkProperties; +-- +2.46.1.824.gd892dcdcdd-goog + diff --git a/aosp_diff/preliminary/packages/modules/Permission/0002-Fix-Dynamic-Permission-group-auto-grant-behaivor.bulletin.patch b/aosp_diff/preliminary/packages/modules/Permission/0002-Fix-Dynamic-Permission-group-auto-grant-behaivor.bulletin.patch new file mode 100644 index 0000000000..616871e94f --- /dev/null +++ b/aosp_diff/preliminary/packages/modules/Permission/0002-Fix-Dynamic-Permission-group-auto-grant-behaivor.bulletin.patch @@ -0,0 +1,763 @@ +From ea7b26fe98c142d0a87d61c3a1c82335ea29c6d2 Mon Sep 17 00:00:00 2001 +From: Yi-an Chen +Date: Thu, 8 Aug 2024 01:15:57 +0000 +Subject: [PATCH] Fix Dynamic Permission group auto grant behaivor + +Fix the Dynamic Permission group auto grant behaivor so that a +permission group is only considered granted when (1) all permissions +were auto-granted or (2) a platform permission in the same group is +granted. + +Bug: 340480881 +Test: DynamicPermissionsTest +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:0fd4565dafbc4aff5c91d966bd7823e1fc4d961d) +Merged-In: I37b550f0c3933bc790c2917a14e917efbcccc4e8 +Change-Id: I37b550f0c3933bc790c2917a14e917efbcccc4e8 +--- + .../permission/data/LightPermInfoLiveData.kt | 2 +- + .../permission/data/PermGroupLiveData.kt | 40 +++++++++-------- + .../model/livedatatypes/LightAppPermGroup.kt | 37 +++++++++++----- + .../model/livedatatypes/LightPackageInfo.kt | 4 +- + .../model/livedatatypes/LightPermInfo.kt | 11 +++-- + .../model/livedatatypes/LightPermission.kt | 17 +++++--- + .../service/AutoRevokePermissions.kt | 2 +- + .../RuntimePermissionsUpgradeController.kt | 4 +- + .../handheld/ReviewPermissionsFragment.java | 4 +- + .../ui/model/AppPermissionViewModel.kt | 18 ++++---- + .../ui/model/GrantPermissionsViewModel.kt | 43 ++++++++++++------- + .../ui/model/ReviewPermissionsViewModel.kt | 2 +- + .../permission/utils/KotlinUtils.kt | 30 ++++++------- + .../permission/utils/SafetyNetLogger.java | 2 +- + .../model/ReviewPermissionsViewModelTest.kt | 2 +- + .../permission/utils/GrantRevokeTests.kt | 6 ++- + 16 files changed, 135 insertions(+), 89 deletions(-) + +diff --git a/PermissionController/src/com/android/permissioncontroller/permission/data/LightPermInfoLiveData.kt b/PermissionController/src/com/android/permissioncontroller/permission/data/LightPermInfoLiveData.kt +index 6f33cb199..5c559c0db 100644 +--- a/PermissionController/src/com/android/permissioncontroller/permission/data/LightPermInfoLiveData.kt ++++ b/PermissionController/src/com/android/permissioncontroller/permission/data/LightPermInfoLiveData.kt +@@ -68,7 +68,7 @@ class LightPermInfoLiveData private constructor( + } + + val newValue = try { +- LightPermInfo(app.packageManager.getPermissionInfo(permissionName, 0)) ++ LightPermInfo(app.packageManager.getPermissionInfo(permissionName, 0), null) + } catch (e: PackageManager.NameNotFoundException) { + Log.w(LOG_TAG, "Permission \"$permissionName\" not found") + invalidateSingle(permissionName) +diff --git a/PermissionController/src/com/android/permissioncontroller/permission/data/PermGroupLiveData.kt b/PermissionController/src/com/android/permissioncontroller/permission/data/PermGroupLiveData.kt +index 78f2f72c6..948815646 100644 +--- a/PermissionController/src/com/android/permissioncontroller/permission/data/PermGroupLiveData.kt ++++ b/PermissionController/src/com/android/permissioncontroller/permission/data/PermGroupLiveData.kt +@@ -17,6 +17,7 @@ + package com.android.permissioncontroller.permission.data + + import android.app.Application ++import android.content.pm.ApplicationInfo + import android.content.pm.PackageItemInfo + import android.content.pm.PackageManager + import android.content.pm.PermissionGroupInfo +@@ -68,32 +69,31 @@ class PermGroupLiveData private constructor( + */ + override fun onUpdate() { + val permissionInfos = mutableMapOf() +- + groupInfo = Utils.getGroupInfo(groupName, context) ?: run { + Log.e(LOG_TAG, "Invalid permission group $groupName") + invalidateSingle(groupName) + value = null + return + } +- ++ val permInfos = mutableListOf() + when (groupInfo) { + is PermissionGroupInfo -> { +- val permInfos = try { +- Utils.getInstalledRuntimePermissionInfosForGroup(context.packageManager, +- groupName) ++ try { ++ permInfos.addAll( ++ Utils.getInstalledRuntimePermissionInfosForGroup( ++ context.packageManager, ++ groupName ++ ) ++ ) + } catch (e: PackageManager.NameNotFoundException) { + Log.e(LOG_TAG, "Invalid permission group $groupName") + invalidateSingle(groupName) + value = null + return + } +- +- for (permInfo in permInfos) { +- permissionInfos[permInfo.name] = LightPermInfo(permInfo) +- } + } + is PermissionInfo -> { +- permissionInfos[groupInfo.name] = LightPermInfo(groupInfo as PermissionInfo) ++ permInfos.add(groupInfo as PermissionInfo) + } + else -> { + value = null +@@ -101,19 +101,25 @@ class PermGroupLiveData private constructor( + } + } + +- val permGroup = PermGroup(LightPermGroupInfo(groupInfo), permissionInfos) +- +- value = permGroup +- +- val packageNames = permissionInfos.values.map { permInfo -> permInfo.packageName } +- .toMutableSet() ++ val packageNames = permInfos.map { permInfo -> permInfo.packageName }.toMutableSet() + packageNames.add(groupInfo.packageName) +- + // TODO ntmyren: What if the package isn't installed for the system user? + val getLiveData = { packageName: String -> + LightPackageInfoLiveData[packageName, UserHandle.SYSTEM] + } + setSourcesToDifference(packageNames, packageLiveDatas, getLiveData) ++ if (!packageLiveDatas.all { it.value.isInitialized }) { ++ return ++ } ++ for (permInfo in permInfos) { ++ val lightPackageInfo = packageLiveDatas[permInfo.packageName]?.value ++ val isSystem = ++ lightPackageInfo?.let { it.appFlags and ApplicationInfo.FLAG_SYSTEM != 0 } ++ permissionInfos[permInfo.name] = LightPermInfo(permInfo, isSystem) ++ } ++ ++ val permGroup = PermGroup(LightPermGroupInfo(groupInfo), permissionInfos) ++ value = permGroup + } + + override fun onInactive() { +diff --git a/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/LightAppPermGroup.kt b/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/LightAppPermGroup.kt +index 3c87f0b7a..e98f01e47 100644 +--- a/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/LightAppPermGroup.kt ++++ b/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/LightAppPermGroup.kt +@@ -20,6 +20,7 @@ import android.Manifest + import android.Manifest.permission.ACCESS_COARSE_LOCATION + import android.os.Build + import android.os.UserHandle ++import com.android.permissioncontroller.permission.utils.Utils + + /** + * A lightweight version of the AppPermissionGroup data structure. Represents information about a +@@ -82,11 +83,13 @@ data class LightAppPermGroup( + if (name !in backgroundPermNames) name else null + } + ++ val isPlatformPermissionGroup = permGroupInfo.packageName == Utils.OS_PKG ++ + val foreground = AppPermSubGroup(permissions.filter { it.key in foregroundPermNames }, +- packageInfo, specialLocationGrant) ++ packageInfo, isPlatformPermissionGroup, specialLocationGrant) + + val background = AppPermSubGroup(permissions.filter { it.key in backgroundPermNames }, +- packageInfo, specialLocationGrant) ++ packageInfo, isPlatformPermissionGroup, specialLocationGrant) + + /** + * Whether or not this App Permission Group has a permission which has a background mode +@@ -127,7 +130,7 @@ data class LightAppPermGroup( + */ + val isOneTime = (permGroupName != Manifest.permission_group.LOCATION && + permissions.any { it.value.isOneTime } && +- permissions.none { !it.value.isOneTime && it.value.isGrantedIncludingAppOp }) || ++ permissions.none { !it.value.isOneTime && it.value.isGranted }) || + (permGroupName == Manifest.permission_group.LOCATION && + permissions[ACCESS_COARSE_LOCATION]?.isOneTime == true) + +@@ -182,17 +185,23 @@ data class LightAppPermGroup( + * + * @param permissions The permissions contained within this subgroup, a subset of those contained + * in the full group ++ * @param isPlatformPermissionGroup Whether this is a platform permission group + * @param specialLocationGrant Whether this is a special location package + */ + data class AppPermSubGroup internal constructor( + private val permissions: Map, + private val packageInfo: LightPackageInfo, ++ private val isPlatformPermissionGroup: Boolean, + private val specialLocationGrant: Boolean? + ) { +- /** +- * Whether any of this App Permission SubGroup's permissions are granted +- */ +- val isGranted = specialLocationGrant ?: permissions.any { it.value.isGrantedIncludingAppOp } ++ /** Whether any of this App Permission SubGroup's permissions are granted */ ++ val isGranted = ++ specialLocationGrant ++ ?: permissions.any { ++ val mayGrantByPlatformOrSystem = ++ !isPlatformPermissionGroup || it.value.isPlatformOrSystem ++ it.value.isGranted && mayGrantByPlatformOrSystem ++ } + + /** + * Whether this App Permission SubGroup should be treated as granted. This means either: +@@ -201,9 +210,15 @@ data class LightAppPermGroup( + * 2) All permissions were auto-granted (all permissions are all granted and all + * RevokeWhenRequested.) + */ +- val isGrantedExcludingRWROrAllRWR = specialLocationGrant ?: (permissions +- .any { it.value.isGrantedIncludingAppOp && !it.value.isRevokeWhenRequested } || +- permissions.all { it.value.isGrantedIncludingAppOp && it.value.isRevokeWhenRequested }) ++ val allowFullGroupGrant = ++ specialLocationGrant ++ ?: (permissions.any { ++ val mayGrantByPlatformOrSystem = ++ !isPlatformPermissionGroup || it.value.isPlatformOrSystem ++ it.value.allowFullGroupGrant && mayGrantByPlatformOrSystem ++ } || permissions.all { ++ it.value.isGranted && it.value.isRevokeWhenRequested ++ }) + + /** + * Whether any of this App Permission SubGroup's permissions are granted by default +@@ -215,7 +230,7 @@ data class LightAppPermGroup( + * none of the granted permissions are not one-time. + */ + val isOneTime = permissions.any { it.value.isOneTime } && +- permissions.none { it.value.isGrantedIncludingAppOp && !it.value.isOneTime } ++ permissions.none { it.value.isGranted && !it.value.isOneTime } + + /** + * Whether any of this App Permission Subgroup's foreground permissions are fixed by policy +diff --git a/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/LightPackageInfo.kt b/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/LightPackageInfo.kt +index 0f6b6c000..cb6c47c76 100644 +--- a/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/LightPackageInfo.kt ++++ b/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/LightPackageInfo.kt +@@ -58,7 +58,9 @@ data class LightPackageInfo( + pI: PackageInfo + ) : this( + pI.packageName, +- pI.permissions?.map { perm -> LightPermInfo(perm) } ?: emptyList(), ++ pI.permissions?.map { perm -> ++ LightPermInfo(perm, pI.applicationInfo!!.flags and ApplicationInfo.FLAG_SYSTEM != 0) ++ } ?: emptyList(), + pI.requestedPermissions?.toList() ?: emptyList(), + pI.requestedPermissionsFlags?.toList() ?: emptyList(), + pI.applicationInfo.uid, +diff --git a/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/LightPermInfo.kt b/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/LightPermInfo.kt +index 3954b7472..582742da4 100644 +--- a/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/LightPermInfo.kt ++++ b/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/LightPermInfo.kt +@@ -30,6 +30,7 @@ import android.content.pm.PermissionInfo + * @param protection The protection level of this permission + * @param protection Extra information about the protection of this permission + * @param flags The system flags of this permission ++ * @param isSystem Whether this permission is defined by a system app + */ + data class LightPermInfo( + val name: String, +@@ -38,11 +39,13 @@ data class LightPermInfo( + val backgroundPermission: String?, + val protection: Int, + val protectionFlags: Int, +- val flags: Int ++ val flags: Int, ++ val isSystem: Boolean? + ) { +- constructor (permInfo: PermissionInfo): this(permInfo.name, permInfo.packageName, +- permInfo.group, permInfo.backgroundPermission, permInfo.protection, +- permInfo.protectionFlags, permInfo.flags) ++ constructor (permInfo: PermissionInfo, isSystem: Boolean?) : this( ++ permInfo.name, permInfo.packageName, permInfo.group, permInfo.backgroundPermission, ++ permInfo.protection, permInfo.protectionFlags, permInfo.flags, isSystem ++ ) + + /** + * Gets the PermissionInfo for this permission from the system. +diff --git a/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/LightPermission.kt b/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/LightPermission.kt +index fd7d82dfc..0ee60e5bf 100644 +--- a/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/LightPermission.kt ++++ b/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/LightPermission.kt +@@ -28,7 +28,7 @@ import com.android.permissioncontroller.permission.utils.Utils + * + * @param pkgInfo The package requesting the permission + * @param permInfo The permissionInfo this represents +- * @param isGrantedIncludingAppOp Whether or not this permission is functionally granted. ++ * @param isGranted Whether or not this permission is functionally granted. + * A non-granted app op but granted permission is counted as not granted + * @param flags The PermissionController flags for this permission + * @param foregroundPerms The foreground permission names corresponding to this permission, if this +@@ -37,7 +37,7 @@ import com.android.permissioncontroller.permission.utils.Utils + data class LightPermission( + val pkgInfo: LightPackageInfo, + val permInfo: LightPermInfo, +- val isGrantedIncludingAppOp: Boolean, ++ val isGranted: Boolean, + val flags: Int, + val foregroundPerms: List? + ) { +@@ -97,9 +97,9 @@ data class LightPermission( + val isRevokeWhenRequested = flags and PackageManager.FLAG_PERMISSION_REVOKE_WHEN_REQUESTED != 0 + /** Whether this permission is user sensitive in its current grant state */ + val isUserSensitive = !isRuntimePlatformPermission(permInfo.name) || +- (isGrantedIncludingAppOp && ++ (isGranted && + (flags and PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED) != 0) || +- (!isGrantedIncludingAppOp && ++ (!isGranted && + (flags and PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED) != 0) + /** Whether the permission is restricted */ + val isRestricted = when { +@@ -120,10 +120,17 @@ data class LightPermission( + */ + val isSelectedLocationAccuracy = + flags and PackageManager.FLAG_PERMISSION_SELECTED_LOCATION_ACCURACY != 0 ++ /** Whether this permission is defined by platform or a system app */ ++ val isPlatformOrSystem = permInfo.packageName == Utils.OS_PKG || permInfo.isSystem == true ++ /** ++ * Whether this permission is granted including app op and does not hold the ++ * PackageManager.FLAG_PERMISSION_REVOKE_WHEN_REQUESTED flag. ++ */ ++ val allowFullGroupGrant = isGranted && !isRevokeWhenRequested + + override fun toString() = buildString { + append(name) +- if (isGrantedIncludingAppOp) append(", Granted") else append(", NotGranted") ++ if (isGranted) append(", Granted") else append(", NotGranted") + if (isPolicyFixed) append(", PolicyFixed") + if (isSystemFixed) append(", SystemFixed") + if (isUserFixed) append(", UserFixed") +diff --git a/PermissionController/src/com/android/permissioncontroller/permission/service/AutoRevokePermissions.kt b/PermissionController/src/com/android/permissioncontroller/permission/service/AutoRevokePermissions.kt +index ae9ccf19e..3845a73dc 100644 +--- a/PermissionController/src/com/android/permissioncontroller/permission/service/AutoRevokePermissions.kt ++++ b/PermissionController/src/com/android/permissioncontroller/permission/service/AutoRevokePermissions.kt +@@ -114,7 +114,7 @@ suspend fun revokeAppPermissions( + .getInitializedValue() ?: continue + val fixed = group.isBackgroundFixed || group.isForegroundFixed + val granted = group.permissions.any { (_, perm) -> +- perm.isGrantedIncludingAppOp && perm.name !in EXEMPT_PERMISSIONS ++ perm.isGranted && perm.name !in EXEMPT_PERMISSIONS + } + if (!fixed && granted && + !group.isGrantedByDefault && +diff --git a/PermissionController/src/com/android/permissioncontroller/permission/service/RuntimePermissionsUpgradeController.kt b/PermissionController/src/com/android/permissioncontroller/permission/service/RuntimePermissionsUpgradeController.kt +index 3405ab014..19b2b4803 100644 +--- a/PermissionController/src/com/android/permissioncontroller/permission/service/RuntimePermissionsUpgradeController.kt ++++ b/PermissionController/src/com/android/permissioncontroller/permission/service/RuntimePermissionsUpgradeController.kt +@@ -412,7 +412,7 @@ internal object RuntimePermissionsUpgradeController { + + val allPermissionsWithxemption = bgApp.allPermissions.toMutableMap() + allPermissionsWithxemption[permission.ACCESS_BACKGROUND_LOCATION] = +- LightPermission(perm.pkgInfo, perm.permInfo, perm.isGrantedIncludingAppOp, ++ LightPermission(perm.pkgInfo, perm.permInfo, perm.isGranted, + perm.flags or FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT, + perm.foregroundPerms) + +@@ -474,7 +474,7 @@ internal object RuntimePermissionsUpgradeController { + ?: continue + + if (!perm.isUserSet && !perm.isSystemFixed && !perm.isPolicyFixed && +- !perm.isGrantedIncludingAppOp) { ++ !perm.isGranted) { + grants.add(Grant(false, appPermGroup, + listOf(permission.ACCESS_MEDIA_LOCATION))) + } +diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/ReviewPermissionsFragment.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/ReviewPermissionsFragment.java +index 5e5c221ae..74719ef9f 100644 +--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/ReviewPermissionsFragment.java ++++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/ReviewPermissionsFragment.java +@@ -257,11 +257,11 @@ public final class ReviewPermissionsFragment extends PreferenceFragmentCompat + PermissionControllerStatsLog.write(REVIEW_PERMISSIONS_FRAGMENT_RESULT_REPORTED, + changeId, mViewModel.getPackageInfo().applicationInfo.uid, + group.getPackageName(), +- permission.getName(), permission.isGrantedIncludingAppOp()); ++ permission.getName(), permission.isGranted()); + Log.v(LOG_TAG, "Permission grant via permission review changeId=" + changeId + " uid=" + + mViewModel.getPackageInfo().applicationInfo.uid + " packageName=" + + group.getPackageName() + " permission=" +- + permission.getName() + " granted=" + permission.isGrantedIncludingAppOp()); ++ + permission.getName() + " granted=" + permission.isGranted()); + } + } + +diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/AppPermissionViewModel.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/AppPermissionViewModel.kt +index 99b40d8a7..169cc7222 100644 +--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/AppPermissionViewModel.kt ++++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/AppPermissionViewModel.kt +@@ -501,9 +501,9 @@ class AppPermissionViewModel( + // 2. Else if FINE or COARSE have the isSelectedLocationAccuracy flag set, then return + // true if FINE isSelectedLocationAccuracy is set. + // 3. Else, return default precision from device config. +- return if (fineLocation.isGrantedIncludingAppOp || +- coarseLocation.isGrantedIncludingAppOp) { +- fineLocation.isGrantedIncludingAppOp ++ return if (fineLocation.isGranted || ++ coarseLocation.isGranted) { ++ fineLocation.isGranted + } else if (fineLocation.isSelectedLocationAccuracy || + coarseLocation.isSelectedLocationAccuracy) { + fineLocation.isSelectedLocationAccuracy +@@ -1040,7 +1040,7 @@ class AppPermissionViewModel( + + private fun getIndividualPermissionDetailResId(group: LightAppPermGroup): Pair { + return when (val numRevoked = +- group.permissions.filter { !it.value.isGrantedIncludingAppOp }.size) { ++ group.permissions.filter { !it.value.isGranted }.size) { + 0 -> R.string.permission_revoked_none to numRevoked + group.permissions.size -> R.string.permission_revoked_all to numRevoked + else -> R.string.permission_revoked_count to numRevoked +@@ -1110,11 +1110,11 @@ class AppPermissionViewModel( + for ((permName, permission) in oldGroup.permissions) { + val newPermission = newGroup.permissions[permName] ?: continue + +- if (permission.isGrantedIncludingAppOp != newPermission.isGrantedIncludingAppOp || ++ if (permission.isGranted != newPermission.isGranted || + permission.flags != newPermission.flags) { + logAppPermissionFragmentActionReported(changeId, newPermission, buttonPressed) + PermissionDecisionStorageImpl.recordPermissionDecision(app.applicationContext, +- packageName, permGroupName, newPermission.isGrantedIncludingAppOp) ++ packageName, permGroupName, newPermission.isGranted) + PermissionChangeStorageImpl.recordPermissionChange(packageName) + } + } +@@ -1138,10 +1138,10 @@ class AppPermissionViewModel( + val uid = KotlinUtils.getPackageUid(app, packageName, user) ?: return + PermissionControllerStatsLog.write(APP_PERMISSION_FRAGMENT_ACTION_REPORTED, sessionId, + changeId, uid, packageName, permission.permInfo.name, +- permission.isGrantedIncludingAppOp, permission.flags, buttonPressed) ++ permission.isGranted, permission.flags, buttonPressed) + Log.v(LOG_TAG, "Permission changed via UI with sessionId=$sessionId changeId=" + + "$changeId uid=$uid packageName=$packageName permission=" + permission.permInfo.name + +- " isGranted=" + permission.isGrantedIncludingAppOp + " permissionFlags=" + ++ " isGranted=" + permission.isGranted + " permissionFlags=" + + permission.flags + " buttonPressed=$buttonPressed") + } + +@@ -1178,7 +1178,7 @@ class AppPermissionViewModel( + val partialPerms = getPartialStorageGrantPermissionsForGroup(group) + + return group.isGranted && group.permissions.values.all { +- it.name in partialPerms || (it.name !in partialPerms && !it.isGrantedIncludingAppOp) ++ it.name in partialPerms || (it.name !in partialPerms && !it.isGranted) + } + } + } +diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/GrantPermissionsViewModel.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/GrantPermissionsViewModel.kt +index 0680ffcd2..3891550f0 100644 +--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/GrantPermissionsViewModel.kt ++++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/GrantPermissionsViewModel.kt +@@ -301,7 +301,7 @@ class GrantPermissionsViewModel( + if (states.isNotEmpty()) { + for ((key, state) in states) { + val allAffectedGranted = state.affectedPermissions.all { perm -> +- appPermGroup.permissions[perm]?.isGrantedIncludingAppOp == true && ++ appPermGroup.permissions[perm]?.isGranted == true && + appPermGroup.permissions[perm]?.isRevokeWhenRequested == false + } + if (allAffectedGranted) { +@@ -340,7 +340,7 @@ class GrantPermissionsViewModel( + for (perm in fgState.affectedPermissions) { + minSdkForOrderedSplitPermissions = maxOf(minSdkForOrderedSplitPermissions, + splitPermissionTargetSdkMap.getOrDefault(perm, 0)) +- if (fgGroup.permissions[perm]?.isGrantedIncludingAppOp == false) { ++ if (fgGroup.permissions[perm]?.isGranted == false) { + // If any of the requested permissions is not granted, + // needFgPermissions = true + needFgPermissions = true +@@ -373,7 +373,7 @@ class GrantPermissionsViewModel( + // If the USER_SELECTED permission is user fixed and granted, or the app is only + // requesting USER_SELECTED, direct straight to photo picker + val userPerm = groupState.group.permissions[READ_MEDIA_VISUAL_USER_SELECTED] +- if ((userPerm?.isUserFixed == true && userPerm.isGrantedIncludingAppOp) || ++ if ((userPerm?.isUserFixed == true && userPerm.isGranted) || + groupState.affectedPermissions == listOf(READ_MEDIA_VISUAL_USER_SELECTED)) { + requestInfos.add(RequestInfo(groupInfo, openPhotoPicker = true)) + continue +@@ -524,7 +524,7 @@ class GrantPermissionsViewModel( + fgState.affectedPermissions.contains(ACCESS_FINE_LOCATION)) { + val coarseLocationPerm = + groupState.group.allPermissions[ACCESS_COARSE_LOCATION] +- if (coarseLocationPerm?.isGrantedIncludingAppOp == true) { ++ if (coarseLocationPerm?.isGranted == true) { + // Upgrade flow + locationVisibilities[DIALOG_WITH_FINE_LOCATION_ONLY] = true + message = RequestMessage.FG_FINE_LOCATION_MESSAGE +@@ -773,7 +773,7 @@ class GrantPermissionsViewModel( + return true + } + } else if (perm in getPartialStorageGrantPermissionsForGroup(group) && +- lightPermission.isGrantedIncludingAppOp) { ++ lightPermission.isGranted) { + // If a partial storage permission is granted as fixed, we should immediately show + // the photo picker + return true +@@ -825,7 +825,7 @@ class GrantPermissionsViewModel( + + // Do not attempt to grant background access if foreground access is not either already + // granted or requested +- if (isBackground && !group.foreground.isGrantedExcludingRWROrAllRWR && ++ if (isBackground && !group.foreground.allowFullGroupGrant && + !hasForegroundRequest) { + Log.w(LOG_TAG, "Cannot grant $perm as the matching foreground permission is not " + + "already granted.") +@@ -837,10 +837,10 @@ class GrantPermissionsViewModel( + return STATE_SKIPPED + } + +- if ((isBackground && group.background.isGrantedExcludingRWROrAllRWR || +- !isBackground && group.foreground.isGrantedExcludingRWROrAllRWR) && ++ if ((isBackground && group.background.allowFullGroupGrant || ++ !isBackground && group.foreground.allowFullGroupGrant) && + canAutoGrantWholeGroup(group)) { +- if (group.permissions[perm]?.isGrantedIncludingAppOp == false) { ++ if (group.permissions[perm]?.isGranted == false) { + if (isBackground) { + grantBackgroundRuntimePermissions(app, group, listOf(perm)) + } else { +@@ -869,7 +869,7 @@ class GrantPermissionsViewModel( + // If FINE location is not granted, do not grant it automatically when COARSE + // location is already granted. + if (group.permGroupName == LOCATION && +- group.allPermissions[ACCESS_FINE_LOCATION]?.isGrantedIncludingAppOp == false) { ++ group.allPermissions[ACCESS_FINE_LOCATION]?.isGranted == false) { + return false + } + // If READ_MEDIA_VISUAL_USER_SELECTED is the only permission in the group that is granted, +@@ -893,7 +893,7 @@ class GrantPermissionsViewModel( + + val partialPerms = getPartialStorageGrantPermissionsForGroup(group) + return group.isGranted && group.permissions.values.all { +- it.name in partialPerms || (it.name !in partialPerms && !it.isGrantedIncludingAppOp) ++ it.name in partialPerms || (it.name !in partialPerms && !it.isGranted) + } + } + +@@ -1114,28 +1114,39 @@ class GrantPermissionsViewModel( + } else { + PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_GRANTED + } ++ var affectedPermissions: List = groupState.affectedPermissions + if (groupState.isBackground) { +- grantBackgroundRuntimePermissions(app, groupState.group, +- groupState.affectedPermissions) ++ grantBackgroundRuntimePermissions(app, groupState.group, affectedPermissions) + } else { + if (affectedForegroundPermissions == null) { + grantForegroundRuntimePermissions(app, groupState.group, +- groupState.affectedPermissions, isOneTime) ++ affectedPermissions, isOneTime) + // This prevents weird flag state when app targetSDK switches from S+ to R- + if (groupState.affectedPermissions.contains(ACCESS_FINE_LOCATION)) { + KotlinUtils.setFlagsWhenLocationAccuracyChanged( + app, groupState.group, true) + } + } else { ++ affectedPermissions = affectedForegroundPermissions + val newGroup = grantForegroundRuntimePermissions(app, +- groupState.group, affectedForegroundPermissions, isOneTime) ++ groupState.group, affectedPermissions, isOneTime) + if (!isOneTime || newGroup.isOneTime) { + KotlinUtils.setFlagsWhenLocationAccuracyChanged(app, newGroup, + affectedForegroundPermissions.contains(ACCESS_FINE_LOCATION)) + } + } + } +- groupState.state = STATE_ALLOWED ++ val shouldDenyFullGroupGrant = ++ groupState.group.isPlatformPermissionGroup && ++ affectedPermissions.none { ++ groupState.group.permissions[it]?.isPlatformOrSystem == true ++ } ++ groupState.state = ++ if (shouldDenyFullGroupGrant) { ++ STATE_UNKNOWN ++ } else { ++ STATE_ALLOWED ++ } + } else { + if (groupState.isBackground) { + revokeBackgroundRuntimePermissions(app, groupState.group, +diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/ReviewPermissionsViewModel.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/ReviewPermissionsViewModel.kt +index 4e1fc1861..7431637a8 100644 +--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/ReviewPermissionsViewModel.kt ++++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/ReviewPermissionsViewModel.kt +@@ -135,7 +135,7 @@ class ReviewPermissionsViewModel( + val lightPerms = permGroup.allPermissions.values.toList() + val permissionCount = lightPerms.size + for (i in 0 until permissionCount) { +- if (!lightPerms[i].isGrantedIncludingAppOp) { ++ if (!lightPerms[i].isGranted) { + revokedCount++ + } + } +diff --git a/PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt b/PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt +index f9345ef58..fb188fca1 100644 +--- a/PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt ++++ b/PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt +@@ -694,7 +694,7 @@ object KotlinUtils { + group.userHandle, *flags) + } + newPerms[permName] = LightPermission(group.packageInfo, perm.permInfo, +- perm.isGrantedIncludingAppOp, perm.flags or flagsToSet, perm.foregroundPerms) ++ perm.isGranted, perm.flags or flagsToSet, perm.foregroundPerms) + } + return LightAppPermGroup(group.packageInfo, group.permGroupInfo, newPerms, + group.hasInstallToRuntimeSplit, group.specialLocationGrant) +@@ -790,7 +790,7 @@ object KotlinUtils { + val newGroup = LightAppPermGroup(group.packageInfo, group.permGroupInfo, newPerms, + group.hasInstallToRuntimeSplit, group.specialLocationGrant) + // If any permission in the group is one time granted, start one time permission session. +- if (newGroup.permissions.any { it.value.isOneTime && it.value.isGrantedIncludingAppOp }) { ++ if (newGroup.permissions.any { it.value.isOneTime && it.value.isGranted }) { + if (SdkLevel.isAtLeastT()) { + app.getSystemService(PermissionManager::class.java)!!.startOneTimePermissionSession( + group.packageName, Utils.getOneTimePermissionsTimeout(), +@@ -842,11 +842,11 @@ object KotlinUtils { + + var newFlags = perm.flags + var oldFlags = perm.flags +- var isGranted = perm.isGrantedIncludingAppOp ++ var isGranted = perm.isGranted + var shouldKill = false + + // Grant the permission if needed. +- if (!perm.isGrantedIncludingAppOp) { ++ if (!perm.isGranted) { + val affectsAppOp = permissionToOp(perm.name) != null || perm.isBackgroundPermission + + // TODO 195016052: investigate adding split permission handling +@@ -906,14 +906,14 @@ object KotlinUtils { + + // If we newly grant background access to the fine location, double-guess the user some + // time later if this was really the right choice. +- if (!perm.isGrantedIncludingAppOp && isGranted) { ++ if (!perm.isGranted && isGranted) { + var triggerLocationAccessCheck = false + if (perm.name == ACCESS_FINE_LOCATION) { + val bgPerm = group.permissions[perm.backgroundPermission] +- triggerLocationAccessCheck = bgPerm?.isGrantedIncludingAppOp == true ++ triggerLocationAccessCheck = bgPerm?.isGranted == true + } else if (perm.name == ACCESS_BACKGROUND_LOCATION) { + val fgPerm = group.permissions[ACCESS_FINE_LOCATION] +- triggerLocationAccessCheck = fgPerm?.isGrantedIncludingAppOp == true ++ triggerLocationAccessCheck = fgPerm?.isGranted == true + } + if (triggerLocationAccessCheck) { + // trigger location access check +@@ -1113,13 +1113,13 @@ object KotlinUtils { + + val user = UserHandle.getUserHandleForUid(group.packageInfo.uid) + var newFlags = perm.flags +- var isGranted = perm.isGrantedIncludingAppOp ++ var isGranted = perm.isGranted + val supportsRuntime = group.packageInfo.targetSdkVersion >= Build.VERSION_CODES.M + var shouldKill = false + + val affectsAppOp = permissionToOp(perm.name) != null || perm.isBackgroundPermission + +- if (perm.isGrantedIncludingAppOp || (perm.isCompatRevoked && forceRemoveRevokedCompat)) { ++ if (perm.isGranted || (perm.isCompatRevoked && forceRemoveRevokedCompat)) { + if (supportsRuntime && !isPermissionSplitFromNonRuntime(app, perm.name, + group.packageInfo.targetSdkVersion)) { + // Revoke the permission if needed. +@@ -1165,14 +1165,14 @@ object KotlinUtils { + + // If we revoke background access to the fine location, we trigger a check to remove + // notification warning about background location access +- if (perm.isGrantedIncludingAppOp && !isGranted) { ++ if (perm.isGranted && !isGranted) { + var cancelLocationAccessWarning = false + if (perm.name == ACCESS_FINE_LOCATION) { + val bgPerm = group.permissions[perm.backgroundPermission] +- cancelLocationAccessWarning = bgPerm?.isGrantedIncludingAppOp == true ++ cancelLocationAccessWarning = bgPerm?.isGranted == true + } else if (perm.name == ACCESS_BACKGROUND_LOCATION) { + val fgPerm = group.permissions[ACCESS_FINE_LOCATION] +- cancelLocationAccessWarning = fgPerm?.isGrantedIncludingAppOp == true ++ cancelLocationAccessWarning = fgPerm?.isGranted == true + } + if (cancelLocationAccessWarning) { + // cancel location access warning notification +@@ -1238,7 +1238,7 @@ object KotlinUtils { + val fgPerm = group.permissions[foregroundPermName] + val appOpName = permissionToOp(foregroundPermName) ?: continue + +- if (fgPerm != null && fgPerm.isGrantedIncludingAppOp) { ++ if (fgPerm != null && fgPerm.isGranted) { + wasChanged = setOpMode(appOpName, uid, packageName, MODE_ALLOWED, + appOpsManager) || wasChanged + } +@@ -1248,7 +1248,7 @@ object KotlinUtils { + if (perm.backgroundPermission != null) { + wasChanged = if (group.permissions.containsKey(perm.backgroundPermission)) { + val bgPerm = group.permissions[perm.backgroundPermission] +- val mode = if (bgPerm != null && bgPerm.isGrantedIncludingAppOp) MODE_ALLOWED ++ val mode = if (bgPerm != null && bgPerm.isGranted) MODE_ALLOWED + else MODE_FOREGROUND + + setOpMode(appOpName, uid, packageName, mode, appOpsManager) +@@ -1299,7 +1299,7 @@ object KotlinUtils { + if (perm.isBackgroundPermission && perm.foregroundPerms != null) { + for (foregroundPermName in perm.foregroundPerms) { + val fgPerm = group.permissions[foregroundPermName] +- if (fgPerm != null && fgPerm.isGrantedIncludingAppOp) { ++ if (fgPerm != null && fgPerm.isGranted) { + val appOpName = permissionToOp(foregroundPermName) ?: return false + wasChanged = wasChanged || setOpMode(appOpName, uid, packageName, + MODE_FOREGROUND, appOpsManager) +diff --git a/PermissionController/src/com/android/permissioncontroller/permission/utils/SafetyNetLogger.java b/PermissionController/src/com/android/permissioncontroller/permission/utils/SafetyNetLogger.java +index 828857cc6..c9b023c44 100644 +--- a/PermissionController/src/com/android/permissioncontroller/permission/utils/SafetyNetLogger.java ++++ b/PermissionController/src/com/android/permissioncontroller/permission/utils/SafetyNetLogger.java +@@ -95,7 +95,7 @@ public final class SafetyNetLogger { + } + + builder.append(permission.getName()).append('|'); +- builder.append(permission.isGrantedIncludingAppOp()).append('|'); ++ builder.append(permission.isGranted()).append('|'); + builder.append(permission.getFlags()); + } + } +diff --git a/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/permission/ui/model/ReviewPermissionsViewModelTest.kt b/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/permission/ui/model/ReviewPermissionsViewModelTest.kt +index 0f4216066..55aa40e50 100644 +--- a/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/permission/ui/model/ReviewPermissionsViewModelTest.kt ++++ b/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/permission/ui/model/ReviewPermissionsViewModelTest.kt +@@ -110,7 +110,7 @@ class ReviewPermissionsViewModelTest { + permissionsMap["mockedPermission1"] = permission2 + + whenever(permGroup.allPermissions).thenReturn(permissionsMap) +- whenever(permission1.isGrantedIncludingAppOp).thenReturn(true) ++ whenever(permission1.isGranted).thenReturn(true) + + val summary = model.getSummaryForIndividuallyControlledPermGroup(permGroup) + assertEquals( +diff --git a/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/permission/utils/GrantRevokeTests.kt b/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/permission/utils/GrantRevokeTests.kt +index be6518b23..c688273c6 100644 +--- a/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/permission/utils/GrantRevokeTests.kt ++++ b/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/permission/utils/GrantRevokeTests.kt +@@ -24,6 +24,7 @@ import android.app.AppOpsManager.MODE_FOREGROUND + import android.app.AppOpsManager.MODE_IGNORED + import android.app.AppOpsManager.permissionToOp + import android.app.Application ++import android.content.pm.ApplicationInfo + import android.content.pm.PackageManager + import android.content.pm.PackageManager.FLAG_PERMISSION_AUTO_REVOKED + import android.content.pm.PackageManager.FLAG_PERMISSION_ONE_TIME +@@ -180,7 +181,8 @@ class GrantRevokeTests { + permInfoProtectionFlags: Int = 0 + ): LightPermission { + val permInfo = LightPermInfo(permName, TEST_PACKAGE_NAME, PERM_GROUP_NAME, backgroundPerm, +- PermissionInfo.PROTECTION_DANGEROUS, permInfoProtectionFlags, 0) ++ PermissionInfo.PROTECTION_DANGEROUS, permInfoProtectionFlags, 0, ++ pkgInfo.appFlags and ApplicationInfo.FLAG_SYSTEM != 0) + return LightPermission(pkgInfo, permInfo, + pkgInfo.requestedPermissionsFlags[pkgInfo.requestedPermissions.indexOf(permName)] + == PERMISSION_GRANTED, flags, foregroundPerms) +@@ -251,7 +253,7 @@ class GrantRevokeTests { + val flags = state.second + + assertWithMessage("permission $permName grant state incorrect") +- .that(perms[permName]?.isGrantedIncludingAppOp).isEqualTo(granted) ++ .that(perms[permName]?.isGranted).isEqualTo(granted) + + val actualFlags = perms[permName]!!.flags + assertWithMessage("permission $permName flags incorrect, expected" + +-- +2.46.1.824.gd892dcdcdd-goog + diff --git a/aosp_diff/preliminary/packages/services/Telephony/04_0004-enforce-the-calling-package-is-the-default-dialer-for-VisualVoic.bulletin.patch b/aosp_diff/preliminary/packages/services/Telephony/04_0004-enforce-the-calling-package-is-the-default-dialer-for-VisualVoic.bulletin.patch new file mode 100644 index 0000000000..ec327d813b --- /dev/null +++ b/aosp_diff/preliminary/packages/services/Telephony/04_0004-enforce-the-calling-package-is-the-default-dialer-for-VisualVoic.bulletin.patch @@ -0,0 +1,37 @@ +From 095be0bb467e9bde25726e9edf987c9a2bc0d00b Mon Sep 17 00:00:00 2001 +From: Thomas Stuart +Date: Thu, 6 Jun 2024 22:34:33 +0000 +Subject: [PATCH] enforce the calling package is the default dialer for + VisualVoicemail + +In the docs for setVisualVoicemailSmsFilterSettings, they state that +the callingPackage needs to be the default dialer. However, server side, +there is no enforcement for this. Now, every client is verified to be +the default dialer, system dialer, or carrier visual voicemail app +before changing the settings for visual voicemail. + +Bug: 308932906 +Test: CTS +(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:60833ab6168a325da0e715b9116b3653a84b4c22) +Merged-In: I951d7783f5c425c03768efb7aee7a38e299e6536 +Change-Id: I951d7783f5c425c03768efb7aee7a38e299e6536 +--- + src/com/android/phone/PhoneInterfaceManager.java | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java +index 0107b1c78..ec15715e4 100644 +--- a/src/com/android/phone/PhoneInterfaceManager.java ++++ b/src/com/android/phone/PhoneInterfaceManager.java +@@ -4080,7 +4080,7 @@ public class PhoneInterfaceManager extends ITelephony.Stub { + public void enableVisualVoicemailSmsFilter(String callingPackage, int subId, + VisualVoicemailSmsFilterSettings settings) { + mAppOps.checkPackage(Binder.getCallingUid(), callingPackage); +- ++ enforceVisualVoicemailPackage(callingPackage, subId); + final long identity = Binder.clearCallingIdentity(); + try { + VisualVoicemailSmsFilterConfig.enableVisualVoicemailSmsFilter( +-- +2.46.1.824.gd892dcdcdd-goog +