Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c31b9658 authored by Christopher Tate's avatar Christopher Tate Committed by Chris Tate
Browse files

Privileged permission for 'interactive' broadcasts

Add a privileged permission check for the "interactive" broadcast
option.  Non-system users who do not hold this permission will
fail when they send the broadcast.

Bug: 254290593
Test: atest FrameworksMockingServicesTests:BroadcastQueueModernImplTest
Test: atest FrameworksMockingServicesTests:BroadcastQueueTest
Test: atest android.app.activity.BroadcastTest#testBroadcastOption_interactive
Change-Id: I0a38f78caa85ee5a4ff50767bb00d54c37ed5e2e
parent fcccdd84
Loading
Loading
Loading
Loading
+7 −1
Original line number Diff line number Diff line
@@ -226,6 +226,12 @@ public abstract class ActivityManagerInternal {
     */
    public abstract boolean isModernQueueEnabled();

    /**
     * Enforce capability restrictions on use of the given BroadcastOptions
     */
    public abstract void enforceBroadcastOptionsPermissions(@Nullable Bundle options,
            int callingUid);

    /**
     * Returns package name given pid.
     *
@@ -300,7 +306,7 @@ public abstract class ActivityManagerInternal {
    public abstract int handleIncomingUser(int callingPid, int callingUid, @UserIdInt int userId,
            boolean allowAll, int allowMode, String name, String callerPackage);

    /** Checks if the calling binder pid as the permission. */
    /** Checks if the calling binder pid/uid has the given permission. */
    @PermissionMethod
    public abstract void enforceCallingPermission(@PermissionName String permission, String func);

+1 −0
Original line number Diff line number Diff line
@@ -636,6 +636,7 @@ public class BroadcastOptions extends ComponentOptions {
     * @param broadcastIsInteractive
     * @see #isInteractiveBroadcast()
     */
    @RequiresPermission(android.Manifest.permission.BROADCAST_OPTION_INTERACTIVE)
    public void setInteractiveBroadcast(boolean broadcastIsInteractive) {
        mIsInteractiveBroadcast = broadcastIsInteractive;
    }
+6 −0
Original line number Diff line number Diff line
@@ -3152,6 +3152,12 @@
    <permission android:name="android.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND"
                android:protectionLevel="signature|privileged|vendorPrivileged|oem|verifier|role"/>

    <!-- Allows an application to hint that a broadcast is associated with an
         "interactive" usage scenario
         @hide -->
    <permission android:name="android.permission.BROADCAST_OPTION_INTERACTIVE"
                android:protectionLevel="signature|privileged" />

    <!-- @SystemApi Must be required by activities that handle the intent action
         {@link Intent#ACTION_SEND_SHOW_SUSPENDED_APP_DETAILS}. This is for use by apps that
         hold {@link Manifest.permission#SUSPEND_APPS} to interact with the system.
+38 −0
Original line number Diff line number Diff line
@@ -18,6 +18,8 @@ package android.app.activity;

import android.app.Activity;
import android.app.ActivityManager;
import android.app.BroadcastOptions;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -536,4 +538,40 @@ public class BroadcastTest extends ActivityTestsBase {
            Log.i("foo", "Unregister exception", e);
        }
    }

    public void testBroadcastOption_interactive() throws Exception {
        final BroadcastOptions options = BroadcastOptions.makeBasic();
        options.setInteractiveBroadcast(true);
        final Intent intent = makeBroadcastIntent(BROADCAST_REGISTERED);

        try {
            getContext().sendBroadcast(intent, null, options.toBundle());
            fail("No exception thrown with BroadcastOptions.setInteractiveBroadcast(true)");
        } catch (SecurityException se) {
            // Expected, correct behavior - this case intentionally empty
        } catch (Exception e) {
            fail("Unexpected exception " + e.getMessage()
                    + " thrown with BroadcastOptions.setInteractiveBroadcast(true)");
        }
    }

    public void testBroadcastOption_interactive_PendingIntent() throws Exception {
        final BroadcastOptions options = BroadcastOptions.makeBasic();
        options.setInteractiveBroadcast(true);
        final Intent intent = makeBroadcastIntent(BROADCAST_REGISTERED);
        PendingIntent brPending = PendingIntent.getBroadcast(getContext(),
                1, intent, PendingIntent.FLAG_IMMUTABLE);

        try {
            brPending.send(getContext(), 1, null, null, null, null, options.toBundle());
            fail("No exception thrown with BroadcastOptions.setInteractiveBroadcast(true)");
        } catch (SecurityException se) {
            // Expected, correct behavior - this case intentionally empty
        } catch (Exception e) {
            fail("Unexpected exception " + e.getMessage()
                    + " thrown with BroadcastOptions.setInteractiveBroadcast(true)");
        } finally {
            brPending.cancel();
        }
    }
}
+26 −13
Original line number Diff line number Diff line
@@ -13826,6 +13826,25 @@ public class ActivityManagerService extends IActivityManager.Stub
        }
    }
    // Apply permission policy around the use of specific broadcast options
    void enforceBroadcastOptionPermissionsInternal(@Nullable Bundle options, int callingUid) {
        if (options != null && callingUid != Process.SYSTEM_UID) {
            if (options.containsKey(BroadcastOptions.KEY_ALARM_BROADCAST)) {
                if (DEBUG_BROADCAST_LIGHT) {
                    Slog.w(TAG, "Non-system caller " + callingUid
                            + " may not flag broadcast as alarm");
                }
                throw new SecurityException(
                        "Non-system callers may not flag broadcasts as alarm");
            }
            if (options.containsKey(BroadcastOptions.KEY_INTERACTIVE_BROADCAST)) {
                enforceCallingPermission(
                        android.Manifest.permission.BROADCAST_OPTION_INTERACTIVE,
                        "setInteractiveBroadcast");
            }
        }
    }
    @GuardedBy("this")
    final int broadcastIntentLocked(ProcessRecord callerApp,
            String callerPackage, String callerFeatureId, Intent intent, String resolvedType,
@@ -14693,19 +14712,8 @@ public class ActivityManagerService extends IActivityManager.Stub
            // We're delivering the result to the caller
            final ProcessRecord resultToApp = callerApp;
            // Non-system callers can't declare that a broadcast is alarm-related.
            // The PendingIntent invocation case is handled in PendingIntentRecord.
            if (bOptions != null && callingUid != SYSTEM_UID) {
                if (bOptions.containsKey(BroadcastOptions.KEY_ALARM_BROADCAST)
                        || bOptions.containsKey(BroadcastOptions.KEY_INTERACTIVE_BROADCAST)) {
                    if (DEBUG_BROADCAST) {
                        Slog.w(TAG, "Non-system caller " + callingUid
                                + " may not flag broadcast as alarm or interactive");
                    }
                    throw new SecurityException(
                            "Non-system callers may not flag broadcasts as alarm or interactive");
                }
            }
            // Permission regimes around sender-supplied broadcast options.
            enforceBroadcastOptionPermissionsInternal(bOptions, callingUid);
            final long origId = Binder.clearCallingIdentity();
            try {
@@ -16899,6 +16907,11 @@ public class ActivityManagerService extends IActivityManager.Stub
            return mEnableModernQueue;
        }
        @Override
        public void enforceBroadcastOptionsPermissions(Bundle options, int callingUid) {
            enforceBroadcastOptionPermissionsInternal(options, callingUid);
        }
        /**
         * Returns package name by pid.
         */
Loading