Make AppWidgets encryption-aware.
Only parse and load AppWidget configuration details after a user has
been unlocked. Yell loudly if someone accidentally tries loading
data for a locked user.
Tidy up protected broadcast logic a bit more to handle persistent
processes. Add backwards compatible behavior for APPWIDGET_UPDATE
broadcast simliar to APPWIDGET_CONFIGURE, since some apps are sending
it to themselves.
Add hidden USER_HANDLE extra to a handful of broadcasts to make
logic more consistent.
Bug: 26247049, 26219971
Change-Id: I54e4f2e343488571f9baa1a316962f41186c1a2c
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index df18d3e..946b233 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -16,6 +16,7 @@
package com.android.server.appwidget;
+import android.app.ActivityManager;
import android.app.AlarmManager;
import android.app.AppGlobals;
import android.app.AppOpsManager;
@@ -76,6 +77,7 @@
import android.view.WindowManager;
import android.widget.RemoteViews;
+import com.android.internal.R;
import com.android.internal.appwidget.IAppWidgetHost;
import com.android.internal.appwidget.IAppWidgetService;
import com.android.internal.os.BackgroundThread;
@@ -83,7 +85,6 @@
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.widget.IRemoteViewsAdapterConnection;
import com.android.internal.widget.IRemoteViewsFactory;
-import com.android.internal.R;
import com.android.server.LocalServices;
import com.android.server.WidgetBackupProvider;
@@ -139,8 +140,10 @@
private static final int CURRENT_VERSION = 1;
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
+ final String action = intent.getAction();
+ final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
if (DEBUG) {
Slog.i(TAG, "Received broadcast: " + action);
@@ -148,23 +151,16 @@
if (Intent.ACTION_CONFIGURATION_CHANGED.equals(action)) {
onConfigurationChanged();
- } else if (Intent.ACTION_USER_STARTED.equals(action)) {
- onUserStarted(intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
- UserHandle.USER_NULL));
+ } else if (Intent.ACTION_USER_UNLOCKED.equals(action)) {
+ onUserUnlocked(userId);
} else if (Intent.ACTION_USER_STOPPED.equals(action)) {
- onUserStopped(intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
- UserHandle.USER_NULL));
+ onUserStopped(userId);
} else if (Intent.ACTION_USER_SWITCHED.equals(action)) {
- refreshProfileWidgetsMaskedState(intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
- UserHandle.USER_NULL));
+ refreshProfileWidgetsMaskedState(userId);
} else if (Intent.ACTION_MANAGED_PROFILE_AVAILABILITY_CHANGED.equals(action)) {
- UserHandle profile = (UserHandle)intent.getParcelableExtra(Intent.EXTRA_USER);
- if (profile != null) {
- refreshWidgetMaskedState(profile.getIdentifier());
- }
+ refreshWidgetMaskedState(userId);
} else {
- onPackageBroadcastReceived(intent, intent.getIntExtra(
- Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL));
+ onPackageBroadcastReceived(intent, userId);
}
}
};
@@ -263,7 +259,7 @@
sdFilter, null, null);
IntentFilter userFilter = new IntentFilter();
- userFilter.addAction(Intent.ACTION_USER_STARTED);
+ userFilter.addAction(Intent.ACTION_USER_UNLOCKED);
userFilter.addAction(Intent.ACTION_USER_STOPPED);
userFilter.addAction(Intent.ACTION_USER_SWITCHED);
mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL,
@@ -340,6 +336,8 @@
}
private void onPackageBroadcastReceived(Intent intent, int userId) {
+ if (!isUserRunningAndUnlocked(userId)) return;
+
final String action = intent.getAction();
boolean added = false;
boolean changed = false;
@@ -419,9 +417,7 @@
* Refresh the masked state for all profiles under the given user.
*/
private void refreshProfileWidgetsMaskedState(int userId) {
- if (userId == UserHandle.USER_NULL) {
- return;
- }
+ if (!isUserRunningAndUnlocked(userId)) return;
List<UserInfo> profiles = mUserManager.getEnabledProfiles(userId);
if (profiles != null) {
for (int i = 0; i < profiles.size(); i++) {
@@ -435,6 +431,7 @@
* Mask/unmask widgets in the given profile, depending on the quiet state of the profile.
*/
private void refreshWidgetMaskedState(int profileId) {
+ if (!isUserRunningAndUnlocked(profileId)) return;
final long identity = Binder.clearCallingIdentity();
try {
UserInfo user = mUserManager.getUserInfo(profileId);
@@ -484,6 +481,11 @@
}
private void ensureGroupStateLoadedLocked(int userId) {
+ if (!isUserRunningAndUnlocked(userId)) {
+ throw new IllegalStateException(
+ "User " + userId + " must be unlocked for widgets to be available");
+ }
+
final int[] profileIds = mSecurityPolicy.getEnabledGroupProfileIds(userId);
// Careful lad, we may have already loaded the state for some
@@ -2321,7 +2323,7 @@
}
}
- private void onUserStarted(int userId) {
+ private void onUserUnlocked(int userId) {
synchronized (mLock) {
ensureGroupStateLoadedLocked(userId);
@@ -2510,6 +2512,15 @@
mWidgetPackages.clear();
}
+ private boolean isUserRunningAndUnlocked(int userId) {
+ if (userId == UserHandle.USER_NULL) {
+ return false;
+ } else {
+ return mContext.getSystemService(ActivityManager.class)
+ .isUserRunningAndUnlocked(userId);
+ }
+ }
+
@Override
public boolean isBoundWidgetPackage(String packageName, int userId) {
if (Binder.getCallingUid() != Process.SYSTEM_UID) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index b805c10..1d555c6 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -17061,6 +17061,8 @@
}
}
+ // Verify that protected broadcasts are only being sent by system code,
+ // and that system code is only sending protected broadcasts.
final String action = intent.getAction();
final boolean isProtectedBroadcast;
try {
@@ -17070,35 +17072,47 @@
return ActivityManager.BROADCAST_SUCCESS;
}
- /*
- * Prevent non-system code (defined here to be non-persistent
- * processes) from sending protected broadcasts.
- */
- int callingAppId = UserHandle.getAppId(callingUid);
- if (callingAppId == Process.SYSTEM_UID || callingAppId == Process.PHONE_UID
- || callingAppId == Process.SHELL_UID || callingAppId == Process.BLUETOOTH_UID
- || callingAppId == Process.NFC_UID || callingUid == 0) {
- // Always okay.
+ final boolean isCallerSystem;
+ switch (UserHandle.getAppId(callingUid)) {
+ case Process.ROOT_UID:
+ case Process.SYSTEM_UID:
+ case Process.PHONE_UID:
+ case Process.SHELL_UID:
+ case Process.BLUETOOTH_UID:
+ case Process.NFC_UID:
+ isCallerSystem = true;
+ break;
+ default:
+ isCallerSystem = (callerApp != null) && callerApp.persistent;
+ break;
+ }
- // 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
+ if (isCallerSystem) {
+ if (isProtectedBroadcast
+ || Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)
+ || Intent.ACTION_MEDIA_SCANNER_SCAN_FILE.equals(action)
+ || AppWidgetManager.ACTION_APPWIDGET_CONFIGURE.equals(action)
+ || AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) {
+ // Broadcast is either protected, or it's a public action that
+ // we've relaxed, so it's fine for system internals to send.
+ } else {
+ // The vast majority of broadcasts sent from system internals
+ // should be protected to avoid security holes, so yell loudly
+ // to ensure we examine these cases.
Log.wtf(TAG, "Sending non-protected broadcast " + action
+ " from system", new Throwable());
}
- } else if (callerApp == null || !callerApp.persistent) {
+ } else {
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)) {
+
+ } else if (AppWidgetManager.ACTION_APPWIDGET_CONFIGURE.equals(action)
+ || AppWidgetManager.ACTION_APPWIDGET_UPDATE.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,
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 62e78a4..e49bc3c 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -245,6 +245,7 @@
mHandler.sendMessage(mHandler.obtainMessage(SYSTEM_USER_UNLOCK_MSG, userId, 0));
final Intent unlockedIntent = new Intent(Intent.ACTION_USER_UNLOCKED);
+ unlockedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
unlockedIntent.addFlags(
Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);
mService.broadcastIntentLocked(null, null, unlockedIntent, null, null, 0, null,
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index f5da103..13f4826 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -526,6 +526,7 @@
if (parentHandle != null) {
intent = new Intent(Intent.ACTION_MANAGED_PROFILE_AVAILABILITY_CHANGED);
intent.putExtra(Intent.EXTRA_USER, profileHandle);
+ intent.putExtra(Intent.EXTRA_USER_HANDLE, profileHandle.getIdentifier());
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
intent.putExtras(extras);
mContext.sendBroadcastAsUser(intent, parentHandle);
@@ -2073,6 +2074,7 @@
managedProfileIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY |
Intent.FLAG_RECEIVER_FOREGROUND);
managedProfileIntent.putExtra(Intent.EXTRA_USER, new UserHandle(removedUserId));
+ managedProfileIntent.putExtra(Intent.EXTRA_USER_HANDLE, removedUserId);
mContext.sendBroadcastAsUser(managedProfileIntent, new UserHandle(parentUserId), null);
}