Android受限广播保护机制

来源:互联网 发布:猎头骗阿里程序员 编辑:程序博客网 时间:2024/05/16 16:14

广播(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.
 *
 * <p class="note">This is a protected intent that can only be sent
 * 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, 0nullnull);

        系统中发送该广播的代码如上,当然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<N; 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<String>();
                }
                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>

        至此,我们将受限广播的声明、解析、校验过程全部了解完了。

0 0
原创粉丝点击