Loading core/java/android/app/Notification.java +38 −9 Original line number Original line Diff line number Diff line Loading @@ -2519,8 +2519,11 @@ public class Notification implements Parcelable if (mAllowlistToken == null) { if (mAllowlistToken == null) { mAllowlistToken = processAllowlistToken; mAllowlistToken = processAllowlistToken; } } // Propagate this token to all pending intents that are unmarshalled from the parcel. // 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); parcel.setClassCookie(PendingIntent.class, mAllowlistToken); } when = parcel.readLong(); when = parcel.readLong(); creationTime = parcel.readLong(); creationTime = parcel.readLong(); Loading Loading @@ -2976,10 +2979,25 @@ public class Notification implements Parcelable } } }); }); } } try { 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 { try { // IMPORTANT: Add marshaling code in writeToParcelImpl as we // IMPORTANT: Add marshaling code in writeToParcelImpl as we // want to intercept all pending events written to the parcel. // want to intercept all pending events written to the parcel. writeToParcelImpl(parcel, flags); writeToParcelImpl(parcel, flags); } finally { if (mustClearCookie) { parcel.removeClassCookie(Notification.class, mAllowlistToken); } } synchronized (this) { synchronized (this) { // Must be written last! // Must be written last! parcel.writeArraySet(allPendingIntents); parcel.writeArraySet(allPendingIntents); Loading @@ -2994,7 +3012,10 @@ public class Notification implements Parcelable private void writeToParcelImpl(Parcel parcel, int flags) { private void writeToParcelImpl(Parcel parcel, int flags) { parcel.writeInt(1); 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(when); parcel.writeLong(creationTime); parcel.writeLong(creationTime); if (mSmallIcon == null && icon != 0) { if (mSmallIcon == null && icon != 0) { Loading Loading @@ -3350,13 +3371,21 @@ public class Notification implements Parcelable * Sets the token used for background operations for the pending intents associated with this * Sets the token used for background operations for the pending intents associated with this * notification. * notification. * * * This token is automatically set during deserialization for you, you usually won't need to * Note: Should <em>only</em> be invoked by NotificationManagerService, since this is normally * call this unless you want to change the existing token, if any. * populated by unparceling (and also used there). Any other usage is suspect. * * * @hide * @hide */ */ public void setAllowlistToken(@Nullable IBinder token) { public void overrideAllowlistToken(IBinder token) { mAllowlistToken = token; mAllowlistToken = token; if (publicVersion != null) { publicVersion.overrideAllowlistToken(token); } } /** @hide */ public IBinder getAllowlistToken() { return mAllowlistToken; } } /** /** Loading core/java/android/os/Parcel.java +22 −0 Original line number Original line Diff line number Diff line Loading @@ -651,6 +651,28 @@ public final class Parcel { return mClassCookies != null ? mClassCookies.get(clz) : null; 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 */ /** @hide */ public final void adoptClassCookies(Parcel from) { public final void adoptClassCookies(Parcel from) { mClassCookies = from.mClassCookies; mClassCookies = from.mClassCookies; Loading core/tests/coretests/src/android/os/ParcelTest.java +49 −0 Original line number Original line Diff line number Diff line Loading @@ -16,15 +16,20 @@ package android.os; package android.os; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals; import android.platform.test.annotations.Presubmit; import android.platform.test.annotations.Presubmit; import android.util.Log; import androidx.test.runner.AndroidJUnit4; import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runner.RunWith; import java.util.ArrayList; @Presubmit @Presubmit @RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class) public class ParcelTest { public class ParcelTest { Loading Loading @@ -110,4 +115,48 @@ public class ParcelTest { assertEquals(string, p.readString16()); assertEquals(string, p.readString16()); } } } } @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<Log.TerribleFailure> 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); } } } services/core/java/com/android/server/notification/NotificationManagerService.java +16 −2 Original line number Original line Diff line number Diff line Loading @@ -592,7 +592,7 @@ public class NotificationManagerService extends SystemService { private static final int MY_UID = Process.myUid(); private static final int MY_UID = Process.myUid(); private static final int MY_PID = Process.myPid(); 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; protected RankingHandler mRankingHandler; private long mLastOverRateLogTime; private long mLastOverRateLogTime; private float mMaxPackageEnqueueRate = DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE; private float mMaxPackageEnqueueRate = DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE; Loading Loading @@ -4230,7 +4230,7 @@ public class NotificationManagerService extends SystemService { // Remove background token before returning notification to untrusted app, this // Remove background token before returning notification to untrusted app, this // ensures the app isn't able to perform background operations that are // ensures the app isn't able to perform background operations that are // associated with notification interactions. // associated with notification interactions. notification.setAllowlistToken(null); notification.overrideAllowlistToken(null); return new StatusBarNotification( return new StatusBarNotification( sbn.getPackageName(), sbn.getPackageName(), sbn.getOpPkg(), sbn.getOpPkg(), Loading Loading @@ -6214,6 +6214,15 @@ public class NotificationManagerService extends SystemService { + " trying to post for invalid pkg " + pkg + " in user " + incomingUserId); + " 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); checkRestrictedCategories(notification); // Fix the notification as best we can. // Fix the notification as best we can. Loading Loading @@ -6958,6 +6967,11 @@ public class NotificationManagerService extends SystemService { @Override @Override public void run() { public void run() { synchronized (mNotificationLock) { 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 = final Long snoozeAt = mSnoozeHelper.getSnoozeTimeForUnpostedNotification( mSnoozeHelper.getSnoozeTimeForUnpostedNotification( r.getUser().getIdentifier(), r.getUser().getIdentifier(), Loading services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +350 −4 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
core/java/android/app/Notification.java +38 −9 Original line number Original line Diff line number Diff line Loading @@ -2519,8 +2519,11 @@ public class Notification implements Parcelable if (mAllowlistToken == null) { if (mAllowlistToken == null) { mAllowlistToken = processAllowlistToken; mAllowlistToken = processAllowlistToken; } } // Propagate this token to all pending intents that are unmarshalled from the parcel. // 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); parcel.setClassCookie(PendingIntent.class, mAllowlistToken); } when = parcel.readLong(); when = parcel.readLong(); creationTime = parcel.readLong(); creationTime = parcel.readLong(); Loading Loading @@ -2976,10 +2979,25 @@ public class Notification implements Parcelable } } }); }); } } try { 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 { try { // IMPORTANT: Add marshaling code in writeToParcelImpl as we // IMPORTANT: Add marshaling code in writeToParcelImpl as we // want to intercept all pending events written to the parcel. // want to intercept all pending events written to the parcel. writeToParcelImpl(parcel, flags); writeToParcelImpl(parcel, flags); } finally { if (mustClearCookie) { parcel.removeClassCookie(Notification.class, mAllowlistToken); } } synchronized (this) { synchronized (this) { // Must be written last! // Must be written last! parcel.writeArraySet(allPendingIntents); parcel.writeArraySet(allPendingIntents); Loading @@ -2994,7 +3012,10 @@ public class Notification implements Parcelable private void writeToParcelImpl(Parcel parcel, int flags) { private void writeToParcelImpl(Parcel parcel, int flags) { parcel.writeInt(1); 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(when); parcel.writeLong(creationTime); parcel.writeLong(creationTime); if (mSmallIcon == null && icon != 0) { if (mSmallIcon == null && icon != 0) { Loading Loading @@ -3350,13 +3371,21 @@ public class Notification implements Parcelable * Sets the token used for background operations for the pending intents associated with this * Sets the token used for background operations for the pending intents associated with this * notification. * notification. * * * This token is automatically set during deserialization for you, you usually won't need to * Note: Should <em>only</em> be invoked by NotificationManagerService, since this is normally * call this unless you want to change the existing token, if any. * populated by unparceling (and also used there). Any other usage is suspect. * * * @hide * @hide */ */ public void setAllowlistToken(@Nullable IBinder token) { public void overrideAllowlistToken(IBinder token) { mAllowlistToken = token; mAllowlistToken = token; if (publicVersion != null) { publicVersion.overrideAllowlistToken(token); } } /** @hide */ public IBinder getAllowlistToken() { return mAllowlistToken; } } /** /** Loading
core/java/android/os/Parcel.java +22 −0 Original line number Original line Diff line number Diff line Loading @@ -651,6 +651,28 @@ public final class Parcel { return mClassCookies != null ? mClassCookies.get(clz) : null; 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 */ /** @hide */ public final void adoptClassCookies(Parcel from) { public final void adoptClassCookies(Parcel from) { mClassCookies = from.mClassCookies; mClassCookies = from.mClassCookies; Loading
core/tests/coretests/src/android/os/ParcelTest.java +49 −0 Original line number Original line Diff line number Diff line Loading @@ -16,15 +16,20 @@ package android.os; package android.os; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals; import android.platform.test.annotations.Presubmit; import android.platform.test.annotations.Presubmit; import android.util.Log; import androidx.test.runner.AndroidJUnit4; import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runner.RunWith; import java.util.ArrayList; @Presubmit @Presubmit @RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class) public class ParcelTest { public class ParcelTest { Loading Loading @@ -110,4 +115,48 @@ public class ParcelTest { assertEquals(string, p.readString16()); assertEquals(string, p.readString16()); } } } } @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<Log.TerribleFailure> 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); } } }
services/core/java/com/android/server/notification/NotificationManagerService.java +16 −2 Original line number Original line Diff line number Diff line Loading @@ -592,7 +592,7 @@ public class NotificationManagerService extends SystemService { private static final int MY_UID = Process.myUid(); private static final int MY_UID = Process.myUid(); private static final int MY_PID = Process.myPid(); 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; protected RankingHandler mRankingHandler; private long mLastOverRateLogTime; private long mLastOverRateLogTime; private float mMaxPackageEnqueueRate = DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE; private float mMaxPackageEnqueueRate = DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE; Loading Loading @@ -4230,7 +4230,7 @@ public class NotificationManagerService extends SystemService { // Remove background token before returning notification to untrusted app, this // Remove background token before returning notification to untrusted app, this // ensures the app isn't able to perform background operations that are // ensures the app isn't able to perform background operations that are // associated with notification interactions. // associated with notification interactions. notification.setAllowlistToken(null); notification.overrideAllowlistToken(null); return new StatusBarNotification( return new StatusBarNotification( sbn.getPackageName(), sbn.getPackageName(), sbn.getOpPkg(), sbn.getOpPkg(), Loading Loading @@ -6214,6 +6214,15 @@ public class NotificationManagerService extends SystemService { + " trying to post for invalid pkg " + pkg + " in user " + incomingUserId); + " 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); checkRestrictedCategories(notification); // Fix the notification as best we can. // Fix the notification as best we can. Loading Loading @@ -6958,6 +6967,11 @@ public class NotificationManagerService extends SystemService { @Override @Override public void run() { public void run() { synchronized (mNotificationLock) { 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 = final Long snoozeAt = mSnoozeHelper.getSnoozeTimeForUnpostedNotification( mSnoozeHelper.getSnoozeTimeForUnpostedNotification( r.getUser().getIdentifier(), r.getUser().getIdentifier(), Loading
services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +350 −4 File changed.Preview size limit exceeded, changes collapsed. Show changes