Better protection for system broadcast intents.

When the system sends broadcast intents, it's in a very authoritative
position, and many apps blindly trust the sender.  This is why we've
historically had the concept of "protected broadcasts" which can only
be sent by the system.

However, it's far too easy to send new broadcasts from the system
without adding them to the protected list.  This CL adds logic to help
catch those cases.  Currently it just logs the error and continues
sending the broadcast.

Based on boot analysis of a typical device, add 36 new protected
broadcasts.

Bug: 24571095
Change-Id: Ie2cc6b0b2026e67c64730af897e4eb3e0e8404f1
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 8f3822f..4fda222 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -325,6 +325,49 @@
 
     <protected-broadcast android:name="android.intent.action.MANAGED_PROFILE_AVAILABILITY_CHANGED" />
     <protected-broadcast android:name="android.intent.action.AVAILABILITY_CHANGED" />
+
+    <!-- Added in N -->
+    <protected-broadcast android:name="android.intent.action.ANR" />
+    <protected-broadcast android:name="android.intent.action.CALL" />
+    <protected-broadcast android:name="android.intent.action.DROPBOX_ENTRY_ADDED" />
+    <protected-broadcast android:name="android.intent.action.INPUT_METHOD_CHANGED" />
+    <protected-broadcast android:name="android.intent.action.internal_sim_state_changed" />
+    <protected-broadcast android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
+    <protected-broadcast android:name="android.intent.action.PRECISE_CALL_STATE" />
+    <protected-broadcast android:name="android.intent.action.PRECISE_DATA_CONNECTION_STATE_CHANGED" />
+    <protected-broadcast android:name="android.intent.action.SUBSCRIPTION_PHONE_STATE" />
+    <protected-broadcast android:name="android.intent.action.USER_INFO_CHANGED" />
+    <protected-broadcast android:name="android.intent.action.USER_UNLOCKED" />
+    <protected-broadcast android:name="android.intent.action.WALLPAPER_CHANGED" />
+
+    <protected-broadcast android:name="android.app.action.DEVICE_POLICY_MANAGER_STATE_CHANGED" />
+    <protected-broadcast android:name="android.bluetooth.adapter.action.BLE_STATE_CHANGED" />
+    <protected-broadcast android:name="android.content.jobscheduler.JOB_DELAY_EXPIRED" />
+    <protected-broadcast android:name="android.content.syncmanager.SYNC_ALARM" />
+    <protected-broadcast android:name="android.media.INTERNAL_RINGER_MODE_CHANGED_ACTION" />
+    <protected-broadcast android:name="android.media.STREAM_DEVICES_CHANGED_ACTION" />
+    <protected-broadcast android:name="android.media.STREAM_MUTE_CHANGED_ACTION" />
+    <protected-broadcast android:name="android.net.sip.SIP_SERVICE_UP" />
+    <protected-broadcast android:name="android.nfc.action.ADAPTER_STATE_CHANGED" />
+    <protected-broadcast android:name="android.os.action.CHARGING" />
+    <protected-broadcast android:name="android.os.action.DISCHARGING" />
+    <protected-broadcast android:name="android.search.action.SEARCHABLES_CHANGED" />
+    <protected-broadcast android:name="android.security.STORAGE_CHANGED" />
+    <protected-broadcast android:name="android.telecom.action.PHONE_ACCOUNT_REGISTERED" />
+    <protected-broadcast android:name="android.telecom.action.PHONE_ACCOUNT_UNREGISTERED" />
+    <protected-broadcast android:name="android.telephony.action.CARRIER_CONFIG_CHANGED" />
+
+    <protected-broadcast android:name="com.android.bluetooth.btservice.action.ALARM_WAKEUP" />
+    <protected-broadcast android:name="com.android.ims.IMS_SERVICE_UP" />
+    <protected-broadcast android:name="com.android.server.action.NETWORK_STATS_POLL" />
+    <protected-broadcast android:name="com.android.server.action.NETWORK_STATS_UPDATED" />
+    <protected-broadcast android:name="com.android.server.NetworkTimeUpdateService.action.POLL" />
+    <protected-broadcast android:name="com.android.server.telecom.intent.action.CALLS_ADD_ENTRY" />
+    <protected-broadcast android:name="com.android.settings.location.MODE_CHANGING" />
+
+    <protected-broadcast android:name="ScheduleConditionProvider.EVALUATE" />
+    <protected-broadcast android:name="wifi_scan_available" />
+
     <!-- ====================================================================== -->
     <!--                          RUNTIME PERMISSIONS                           -->
     <!-- ====================================================================== -->
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index d6106d7..f22062c 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -17000,6 +17000,15 @@
             }
         }
 
+        final String action = intent.getAction();
+        final boolean isProtectedBroadcast;
+        try {
+            isProtectedBroadcast = AppGlobals.getPackageManager().isProtectedBroadcast(action);
+        } catch (RemoteException e) {
+            Slog.w(TAG, "Remote exception", e);
+            return ActivityManager.BROADCAST_SUCCESS;
+        }
+
         /*
          * Prevent non-system code (defined here to be non-persistent
          * processes) from sending protected broadcasts.
@@ -17009,49 +17018,54 @@
             || callingAppId == Process.SHELL_UID || callingAppId == Process.BLUETOOTH_UID
             || callingAppId == Process.NFC_UID || callingUid == 0) {
             // Always okay.
+
+            // Yell if the system is trying to send a non-protected broadcast.
+            // The vast majority of broadcasts sent from system callers should
+            // be protected to avoid security holes, so exceptions here should
+            // be incredibly rare.
+            if (!isProtectedBroadcast
+                    && !Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) {
+                // TODO: eventually switch over to hard throw
+                Log.wtf(TAG, "Sending non-protected broadcast " + action
+                        + " from system", new Throwable());
+            }
+
         } else if (callerApp == null || !callerApp.persistent) {
-            try {
-                if (AppGlobals.getPackageManager().isProtectedBroadcast(
-                        intent.getAction())) {
+            if (isProtectedBroadcast) {
+                String msg = "Permission Denial: not allowed to send broadcast "
+                        + action + " from pid="
+                        + callingPid + ", uid=" + callingUid;
+                Slog.w(TAG, msg);
+                throw new SecurityException(msg);
+            } else if (AppWidgetManager.ACTION_APPWIDGET_CONFIGURE.equals(action)) {
+                // Special case for compatibility: we don't want apps to send this,
+                // but historically it has not been protected and apps may be using it
+                // to poke their own app widget.  So, instead of making it protected,
+                // just limit it to the caller.
+                if (callerApp == null) {
                     String msg = "Permission Denial: not allowed to send broadcast "
-                            + intent.getAction() + " from pid="
-                            + callingPid + ", uid=" + callingUid;
+                            + action + " from unknown caller.";
                     Slog.w(TAG, msg);
                     throw new SecurityException(msg);
-                } else if (AppWidgetManager.ACTION_APPWIDGET_CONFIGURE.equals(intent.getAction())) {
-                    // Special case for compatibility: we don't want apps to send this,
-                    // but historically it has not been protected and apps may be using it
-                    // to poke their own app widget.  So, instead of making it protected,
-                    // just limit it to the caller.
-                    if (callerApp == null) {
+                } else if (intent.getComponent() != null) {
+                    // They are good enough to send to an explicit component...  verify
+                    // it is being sent to the calling app.
+                    if (!intent.getComponent().getPackageName().equals(
+                            callerApp.info.packageName)) {
                         String msg = "Permission Denial: not allowed to send broadcast "
-                                + intent.getAction() + " from unknown caller.";
+                                + action + " to "
+                                + intent.getComponent().getPackageName() + " from "
+                                + callerApp.info.packageName;
                         Slog.w(TAG, msg);
                         throw new SecurityException(msg);
-                    } else if (intent.getComponent() != null) {
-                        // They are good enough to send to an explicit component...  verify
-                        // it is being sent to the calling app.
-                        if (!intent.getComponent().getPackageName().equals(
-                                callerApp.info.packageName)) {
-                            String msg = "Permission Denial: not allowed to send broadcast "
-                                    + intent.getAction() + " to "
-                                    + intent.getComponent().getPackageName() + " from "
-                                    + callerApp.info.packageName;
-                            Slog.w(TAG, msg);
-                            throw new SecurityException(msg);
-                        }
-                    } else {
-                        // Limit broadcast to their own package.
-                        intent.setPackage(callerApp.info.packageName);
                     }
+                } else {
+                    // Limit broadcast to their own package.
+                    intent.setPackage(callerApp.info.packageName);
                 }
-            } catch (RemoteException e) {
-                Slog.w(TAG, "Remote exception", e);
-                return ActivityManager.BROADCAST_SUCCESS;
             }
         }
 
-        final String action = intent.getAction();
         if (action != null) {
             switch (action) {
                 case Intent.ACTION_UID_REMOVED:
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 4f04519..f65dba1 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -4093,8 +4093,15 @@
     @Override
     public boolean isProtectedBroadcast(String actionName) {
         synchronized (mPackages) {
-            return mProtectedBroadcasts.contains(actionName);
+            if (mProtectedBroadcasts.contains(actionName)) {
+                return true;
+            } else if (actionName != null
+                    && actionName.startsWith("android.net.netmon.lingerExpired")) {
+                // TODO: remove this terrible hack
+                return true;
+            }
         }
+        return false;
     }
 
     @Override