- 受保护的广播只能由System进程(参考isCallerSystem小节)发送,否则会报错
- System进程只能发送受保护的广播,除非有下文说的特殊情况(参考Sending non-protected broadcast小节),否则会有wtf日志打印
checkBroadcastFromSystem
private void checkBroadcastFromSystem(Intent intent, ProcessRecord callerApp,
String callerPackage, int callingUid, boolean isProtectedBroadcast, List receivers) {
if ((intent.getFlags() & Intent.FLAG_RECEIVER_FROM_SHELL) != 0) {
// Don't yell about broadcasts sent via shell
return;
}
final String action = intent.getAction();
if (isProtectedBroadcast
|| Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)
|| Intent.ACTION_DISMISS_KEYBOARD_SHORTCUTS.equals(action)
|| Intent.ACTION_MEDIA_BUTTON.equals(action)
|| Intent.ACTION_MEDIA_SCANNER_SCAN_FILE.equals(action)
|| Intent.ACTION_SHOW_KEYBOARD_SHORTCUTS.equals(action)
|| Intent.ACTION_MASTER_CLEAR.equals(action)
|| Intent.ACTION_FACTORY_RESET.equals(action)
|| AppWidgetManager.ACTION_APPWIDGET_CONFIGURE.equals(action)
|| AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)
|| TelephonyManager.ACTION_REQUEST_OMADM_CONFIGURATION_UPDATE.equals(action)
|| SuggestionSpan.ACTION_SUGGESTION_PICKED.equals(action)
|| AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION.equals(action)
|| AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION.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.
return;
}
// This broadcast may be a problem... but there are often system components that
// want to send an internal broadcast to themselves, which is annoying to have to
// explicitly list each action as a protected broadcast, so we will check for that
// one safe case and allow it: an explicit broadcast, only being received by something
// that has protected itself.
if (intent.getPackage() != null || intent.getComponent() != null) {
if (receivers == null || receivers.size() == 0) {
// Intent is explicit and there's no receivers.
// This happens, e.g. , when a system component sends a broadcast to
// its own runtime receiver, and there's no manifest receivers for it,
// because this method is called twice for each broadcast,
// for runtime receivers and manifest receivers and the later check would find
// no receivers.
return;
}
boolean allProtected = true;
for (int i = receivers.size()-1; i >= 0; i--) {
Object target = receivers.get(i);
if (target instanceof ResolveInfo) {
ResolveInfo ri = (ResolveInfo)target;
if (ri.activityInfo.exported && ri.activityInfo.permission == null) {
allProtected = false;
break;
}
} else {
BroadcastFilter bf = (BroadcastFilter)target;
if (bf.requiredPermission == null) {
allProtected = false;
break;
}
}
}
if (allProtected) {
// All safe!
return;
}
}
// 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.
if (callerApp != null) {
Log.wtf(TAG, "Sending non-protected broadcast " + action
+ " from system " + callerApp.toShortString() + " pkg " + callerPackage,
new Throwable());
} else {
Log.wtf(TAG, "Sending non-protected broadcast " + action
+ " from system uid " + UserHandle.formatUid(callingUid)
+ " pkg " + callerPackage,
new Throwable());
}
}
绝大多数从系统内部发送的广播都应该受到保护,以避免出现安全漏洞:
- 代码首先检查广播的一些标志。如果是通过 shell 发送的广播,那么就不做检查。
- 代码检查广播的动作(action)。如果广播的动作是一些特定的公共动作,或者是受保护的广播,则认为是系统内部的广播,那么就不做检查。
- 代码检查广播的接收者。如果广播是显式的(通过指定包名或组件名),并且没有接收者,那么就不做检查。
- 代码检查广播的接收者。如果广播是显式的(通过指定包名或组件名),并且有接收者,且该广播规定了接收权限(静态广播的exported为true的时候加上了permission限制,动态广播指定了requiredPermission),那么就不做检查。
- 否则,就输出wtf日志,认为该广播是不受保护,即不安全的广播,wtf日志包括了发送者的uid和packageName以及堆栈。
isProtectedBroadcast
这段代码的目的是验证受保护的广播只能由系统代码发送,并且系统代码只发送受保护的广播
// Verify that protected broadcasts are only being sent by system code,
// and that system code is only sending protected broadcasts.
final boolean isProtectedBroadcast;
try {
isProtectedBroadcast = AppGlobals.getPackageManager().isProtectedBroadcast(action);
} catch (RemoteException e) {
Slog.w(TAG, "Remote exception", e);
return ActivityManager.BROADCAST_SUCCESS;
}
@Override
public boolean isProtectedBroadcast(String actionName) {
if (actionName != null) {
// TODO: remove these terrible hacks
if (actionName.startsWith("android.net.netmon.lingerExpired")
|| actionName.startsWith("com.android.server.sip.SipWakeupTimer")
|| actionName.startsWith("com.android.internal.telephony.data-reconnect")
|| actionName.startsWith("android.net.netmon.launchCaptivePortalApp")) {
return true;
}
}
// allow instant applications
synchronized (mProtectedBroadcasts) {
return mProtectedBroadcasts.contains(actionName);
}
}
// Broadcast actions that are only available to the system.
// 受保护的广播只能给系统用
@GuardedBy("mProtectedBroadcasts")
final ArraySet<String> mProtectedBroadcasts = new ArraySet<>();
- 校验actionName是否以某个字符串为开始。
- 校验actionName是否存在于mProtectedBroadcasts数据结构中。
如果满足以上两个条件,就认为是受保护的广播。
isCallerSystem
if (isCallerSystem) {
checkBroadcastFromSystem(intent, callerApp, callerPackage, callingUid,
isProtectedBroadcast, registeredReceivers);
}
每次调用checkBroadcastFromSystem进行check的前提是广播发起者是System进程:
final boolean isCallerSystem;
switch (UserHandle.getAppId(callingUid)) {
case ROOT_UID:
case SYSTEM_UID:
case PHONE_UID:
case BLUETOOTH_UID:
case NFC_UID:
case SE_UID:
case NETWORK_STACK_UID:
isCallerSystem = true;
break;
default:
isCallerSystem = (callerApp != null) && callerApp.isPersistent();
break;
}
这里的System进程指的是有特殊UID或者是Persistent进程。也就是说只针对System进程发起的广播进行校验,System进程发起的广播必须是受保护的广播。
Sending non-protected broadcast
这是一个wtf的日志,如何不打印这个wtf呢?
- callapp不是System进程,参考isCallerSystem小节
- 该广播是shell进程发的
- 该广播是受保护的广播,参考isProtectedBroadcast小节,或者action比较特殊。
- 该广播是显示广播,有明确具体的Package或者Component,且没有广播接收者
- 该广播是显示广播,有明确具体的Package或者Component,有广播接收者,此时广播需要有明确的权限限制。
受保护的广播只能由System进程发送
// First line security check before anything else: stop non-system apps from
// sending protected broadcasts.
if (!isCallerSystem) {
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);
首先进行一线安全检查:停止非系统应用程序发送受保护的广播。
网友评论