广播(Broadcast)作为消息传递的一种方式,在Android系统中有着广泛的应用。系统提供了一系列的广播发送、接收接口,可以非常方便的实现广播的传递。系统中相当部分的状态变化也都是通过广播的方式通知到应用的,例如:系统启动完成(Intent.ACTION_BOOT_COMPLETED),电池状态变化(Intent.ACTION_BATTERY_CHANGED),网络连接状态变化(Intent.CONNECTIVITY_ACTION)等。
但同时也引发一个问题,如果任意应用都能随意发送此类广播,势必会引起系统状态的混乱,因此系统一定会有一套机制来保护此类所谓的受限广播。以下以Intent.ACTION_SCREEN_ON为例,深入探究下Android的受限广播保护机制。
1
2
3
4
5
6
7
8
|
/**
* Broadcast Action: Sent after the screen turns on.
*
*
* by the system.
*/
@SdkConstant
(SdkConstantType.BROADCAST_INTENT_ACTION)
public
static
final
String ACTION_SCREEN_ON =
"android.intent.action.SCREEN_ON"
;
|
Intent.ACTION_SCREEN_ON为屏幕点亮后由系统电源管理服务(PowerManagerService)所发,试想如果任意应用都能随意发送这个广播,则容易被恶意应用利用,造成监听该广播的应用状态、逻辑混乱。因此该广播一定是所谓的受限广播,这点在注释中也得到证实。
This is a protected intent that can only be sent by the system.
1
2
3
4
|
Intent screenOnIntent =
new
Intent(Intent.ACTION_SCREEN_ON);
screenOnIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);
context.sendOrderedBroadcastAsUser(screenIntent, UserHandle.ALL,
null
,
receiver, handler,
0
,
null
,
null
);
|
系统中发送该广播的代码如上,当然Android中还开放了如下非常丰富的发送广播的接口。这些方法有一个共同点,都调用了ActivityManagerService中的broadcastIntent接口,而这些开放出来的不同的发送广播接口,只是在调用broadcastIntent时传递的参数有所差异,如 ordered 、 sticky 等。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
public
void
sendBroadcast(Intent intent);
public
void
sendBroadcast(Intent intent,
String receiverPermission);
public
void
sendOrderedBroadcast(Intent intent,
String receiverPermission);
public
void
sendOrderedBroadcast(Intent intent,
String receiverPermission, BroadcastReceiver resultReceiver,
Handler scheduler,
int
initialCode, String initialData,
Bundle initialExtras);
public
void
sendBroadcastAsUser(Intent intent, UserHandle user);
public
void
sendBroadcastAsUser(Intent intent, UserHandle user,
String receiverPermission);
public
void
sendOrderedBroadcastAsUser(Intent intent, UserHandle user,
String receiverPermission, BroadcastReceiver resultReceiver, Handler scheduler,
int
initialCode, String initialData, Bundle initialExtras);
public
void
sendStickyBroadcast(Intent intent);
public
void
sendStickyOrderedBroadcast(Intent intent,
BroadcastReceiver resultReceiver,
Handler scheduler,
int
initialCode, String initialData,
Bundle initialExtras);
public
void
sendStickyBroadcastAsUser(Intent intent, UserHandle user);
public
void
sendStickyOrderedBroadcastAsUser(Intent intent,
UserHandle user, BroadcastReceiver resultReceiver,
Handler scheduler,
int
initialCode, String initialData,
Bundle initialExtras);
|
在ActivityManagerService的broadcastIntent方法中,首先是对Intent的flags做了一些校验,不是本文讨论范围之内,紧接着便是调用核心的broadcastIntentLocked方法,我们看到,受限保护的检验正是在这里做的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
private
final
int
broadcastIntentLocked(ProcessRecord callerApp,
String callerPackage, Intent intent, String resolvedType,
IIntentReceiver resultTo,
int
resultCode, String resultData,
Bundle map, String requiredPermission,
boolean
ordered,
boolean
sticky,
int
callingPid,
int
callingUid,
int
userId) {
......
/*
* 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 ||
callingUid ==
0
) {
// Always okay.
}
else
if
(callerApp ==
null
|| !callerApp.persistent) {
try
{
if
(AppGlobals.getPackageManager().isProtectedBroadcast(
intent.getAction())) {
String msg =
"Permission Denial: not allowed to send broadcast "
+ intent.getAction() +
" from pid="
+ callingPid +
", uid="
+ callingUid;
Slog.w(TAG, msg);
throw
new
SecurityException(msg);
}
}
catch
(RemoteException e) {
Slog.w(TAG,
"Remote exception"
, e);
return
ActivityManager.BROADCAST_SUCCESS;
}
}
......
}
|
在以上这段代码中,首先查看调用者的UID,如果是 ROOT:0 SYSTEM_UID:1000 PHONE_UID:1001 BLUETOOTH_UID:1002 SHELL_UID:2000 其中之一,则默认拥有发送受限广播权限,跳过校验过程。否则调用AppGlobals.getPackageManager().isProtectedBroadcast()判断是否为受限广播,如是则抛出权限异常,终止广播流程。
至此,受限广播校验流程基本清晰,那么问题来了,AppGlobals.getPackageManager().isProtectedBroadcast()是以什么依据来判断是否受限广播呢?继续从代码中找答案。
1
2
3
4
5
|
public
boolean
isProtectedBroadcast(String actionName) {
synchronized
(mPackages) {
return
mProtectedBroadcasts.contains(actionName);
}
}
|
在PackageManagerService中维护了一个散列表mProtectedBroadcasts,用以标识哪些广播是受限的,而AppGlobals.getPackageManager().isProtectedBoradcast()所做的仅仅查询所发广播是否在这个列表中。继续跟谁在维护这个受限广播表,发现只有PackageManagerService.scanPackageLI会往受限表中添加元素,而添加的元素则来自pkg.protectedBroadcasts。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
private
PackageParser.Package scanPackageLI(PackageParser.Package pkg,
int
parseFlags,
int
scanMode,
long
currentTime, UserHandle user) {
......
synchronized
(mPackages) {
......
if
(pkg.protectedBroadcasts !=
null
) {
N = pkg.protectedBroadcasts.size();
for
(i=
0
; i
mProtectedBroadcasts.add(pkg.protectedBroadcasts.get(i));
}
}
......
}
return
pkg;
}
|
需要说明的是,scanPackageLI会在以下几种情况触发:构造PackageManagerService(扫描apks)、安装全新应用、安装应用更新、卸载系统应用、SD卡Mount/Unmount、APP目录(Framework/System/Vendor)新增文件。scanpackageLI被触发后,会将pkg.protectedcasts中的元素取出,添加到受限表中,由于HashSet的唯一性,不用担心重复添加的问题。因此判断是否受限广播的关键,落在了pkg.protectedBroadcasts上。
protectedBroadcasts是Package类中维护的列表,元素由PackageParser在解析apk时添加。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
private
Package parsePackage(
Resources res, XmlResourceParser parser,
int
flags, String[] outError)
throws
XmlPullParserException, IOException {
while
((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
String tagName = parser.getName();
......
else
if
(tagName.equals(
"protected-broadcast"
)) {
sa = res.obtainAttributes(attrs,
com.android.internal.R.styleable.AndroidManifestProtectedBroadcast);
// Note: don't allow this value to be a reference to a resource
// that may change.
String name = sa.getNonResourceString(
com.android.internal.R.styleable.AndroidManifestProtectedBroadcast_name);
sa.recycle();
if
(name !=
null
&& (flags&PARSE_IS_SYSTEM) !=
0
) {
if
(pkg.protectedBroadcasts ==
null
) {
pkg.protectedBroadcasts =
new
ArrayList
}
if
(!pkg.protectedBroadcasts.contains(name)) {
pkg.protectedBroadcasts.add(name.intern());
}
}
XmlUtils.skipCurrentTag(parser);
}
}
......
return
pkg;
}
|
从PackageParser.parsePackage方法中可以清楚的看到,pkg.protectedBroadcasts对应AndroidManifest.xml中的 protected-broadcast 标签。回到本文开头的Intent.ACTION_SCREEN_ON广播,我们可以在frameworks/base/core/res/AndroidManifest.xml中找到,对应的package为system/framework/framework-res.apk
1
2
3
4
5
6
7
8
9
|
xml
version
=
"1.0"
encoding
=
"utf-8"
?>
<
manifest
xmlns:android
=
"http://schemas.android.com/apk/res/android"
package
=
"android"
coreApp
=
"true"
android:sharedUserId
=
"android.uid.system"
android:sharedUserLabel
=
"@string/android_system_label"
>
......
<
protected-broadcast
android:name
=
"android.intent.action.SCREEN_ON"
/>
......
manifest
>
|
至此,我们将受限广播的声明、解析、校验过程全部了解完了。