Validate ComponentName for MediaButtonBroadcastReceiver

This is a security fix for b/270049379.

Bug: 270049379
Test: atest CtsMediaBetterTogetherTestCases
(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:c573c83a2aa36ca022302f675d705518dd723a3c)
Merged-In: I4851eb506683c2666d57ec45bf9de787833d9636
Change-Id: I4851eb506683c2666d57ec45bf9de787833d9636
This commit is contained in:
Iván Budnik
2023-04-04 17:58:26 +00:00
committed by Cherrypicker Worker
parent 4680610cf7
commit e80305690c
2 changed files with 61 additions and 4 deletions

View File

@@ -293,12 +293,16 @@ public final class MediaSession {
* Set the component name of the manifest-declared {@link android.content.BroadcastReceiver} * Set the component name of the manifest-declared {@link android.content.BroadcastReceiver}
* class that should receive media buttons. This allows restarting playback after the session * class that should receive media buttons. This allows restarting playback after the session
* has been stopped. If your app is started in this way an {@link Intent#ACTION_MEDIA_BUTTON} * has been stopped. If your app is started in this way an {@link Intent#ACTION_MEDIA_BUTTON}
* intent will be sent to the broadcast receiver. * intent will be sent to the broadcast receiver. On apps targeting Android U and above, this
* <p> * will throw an {@link IllegalArgumentException} if the provided {@link ComponentName} does not
* Note: The given {@link android.content.BroadcastReceiver} should belong to the same package * resolve to an existing {@link android.content.BroadcastReceiver broadcast receiver}.
* as the context that was given when creating {@link MediaSession}. *
* <p>Note: The given {@link android.content.BroadcastReceiver} should belong to the same
* package as the context that was given when creating {@link MediaSession}.
* *
* @param broadcastReceiver the component name of the BroadcastReceiver class * @param broadcastReceiver the component name of the BroadcastReceiver class
* @throws IllegalArgumentException if {@code broadcastReceiver} does not exist on apps
* targeting Android U and above
*/ */
public void setMediaButtonBroadcastReceiver(@Nullable ComponentName broadcastReceiver) { public void setMediaButtonBroadcastReceiver(@Nullable ComponentName broadcastReceiver) {
try { try {

View File

@@ -16,14 +16,22 @@
package com.android.server.media; package com.android.server.media;
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable; import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.app.ActivityManager; import android.app.ActivityManager;
import android.app.ActivityManagerInternal; import android.app.ActivityManagerInternal;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice; import android.content.pm.ParceledListSlice;
import android.content.pm.ResolveInfo;
import android.media.AudioAttributes; import android.media.AudioAttributes;
import android.media.AudioManager; import android.media.AudioManager;
import android.media.AudioSystem; import android.media.AudioSystem;
@@ -42,6 +50,7 @@ import android.media.session.ParcelableListBinder;
import android.media.session.PlaybackState; import android.media.session.PlaybackState;
import android.net.Uri; import android.net.Uri;
import android.os.Binder; import android.os.Binder;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.DeadObjectException; import android.os.DeadObjectException;
import android.os.Handler; import android.os.Handler;
@@ -52,6 +61,7 @@ import android.os.Process;
import android.os.RemoteException; import android.os.RemoteException;
import android.os.ResultReceiver; import android.os.ResultReceiver;
import android.os.SystemClock; import android.os.SystemClock;
import android.os.UserHandle;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.EventLog; import android.util.EventLog;
import android.util.Log; import android.util.Log;
@@ -73,6 +83,17 @@ import java.util.concurrent.CopyOnWriteArrayList;
*/ */
// TODO(jaewan): Do not call service method directly -- introduce listener instead. // TODO(jaewan): Do not call service method directly -- introduce listener instead.
public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionRecordImpl { public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionRecordImpl {
/**
* {@link MediaSession#setMediaButtonBroadcastReceiver(ComponentName)} throws an {@link
* IllegalArgumentException} if the provided {@link ComponentName} does not resolve to a valid
* {@link android.content.BroadcastReceiver broadcast receiver} for apps targeting Android U and
* above. For apps targeting Android T and below, the request will be ignored.
*/
@ChangeId
@EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
static final long THROW_FOR_INVALID_BROADCAST_RECEIVER = 270049379L;
private static final String TAG = "MediaSessionRecord"; private static final String TAG = "MediaSessionRecord";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -871,6 +892,22 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR
} }
}; };
@RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS)
private static boolean componentNameExists(
@NonNull ComponentName componentName, @NonNull Context context, int userId) {
Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
mediaButtonIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
mediaButtonIntent.setComponent(componentName);
UserHandle userHandle = UserHandle.of(userId);
PackageManager pm = context.getPackageManager();
List<ResolveInfo> resolveInfos =
pm.queryBroadcastReceiversAsUser(
mediaButtonIntent, PackageManager.ResolveInfoFlags.of(0), userHandle);
return !resolveInfos.isEmpty();
}
private final class SessionStub extends ISession.Stub { private final class SessionStub extends ISession.Stub {
@Override @Override
public void destroySession() throws RemoteException { public void destroySession() throws RemoteException {
@@ -955,7 +992,9 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR
} }
@Override @Override
@RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS)
public void setMediaButtonBroadcastReceiver(ComponentName receiver) throws RemoteException { public void setMediaButtonBroadcastReceiver(ComponentName receiver) throws RemoteException {
final int uid = Binder.getCallingUid();
final long token = Binder.clearCallingIdentity(); final long token = Binder.clearCallingIdentity();
try { try {
//mPackageName has been verified in MediaSessionService.enforcePackageName(). //mPackageName has been verified in MediaSessionService.enforcePackageName().
@@ -970,6 +1009,20 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR
!= 0) { != 0) {
return; return;
} }
if (!componentNameExists(receiver, mContext, mUserId)) {
if (CompatChanges.isChangeEnabled(THROW_FOR_INVALID_BROADCAST_RECEIVER, uid)) {
throw new IllegalArgumentException("Invalid component name: " + receiver);
} else {
Log.w(
TAG,
"setMediaButtonBroadcastReceiver(): "
+ "Ignoring invalid component name="
+ receiver);
}
return;
}
mMediaButtonReceiverHolder = MediaButtonReceiverHolder.create(mUserId, receiver); mMediaButtonReceiverHolder = MediaButtonReceiverHolder.create(mUserId, receiver);
mService.onMediaButtonReceiverChanged(MediaSessionRecord.this); mService.onMediaButtonReceiverChanged(MediaSessionRecord.this);
} finally { } finally {