[Q]Sending non-protected broadcast问题分析

/ android / 4 条评论 / 3898浏览

问题

异常信息

E ActivityManager: Sending non-protected broadcast android.bluetooth
.ftp.action.STATE_CHANGED from system 2407:com.android.bluetooth
/1002 pkg com.android.bluetooth

E ActivityManager: java.lang.Throwable

E ActivityManager: at com.android.server.am.ActivityManagerService
.broadcastIntentLocked(ActivityManagerService.java:18195)

E ActivityManager: at com.android.server.am.ActivityManagerService
.broadcastIntent(ActivityManagerService.java:18761)

E ActivityManager: at android.app.ActivityManagerNative
.onTransact(ActivityManagerNative.java:499)

E ActivityManager: at com.android.server.am.ActivityManagerService
.onTransact(ActivityManagerService.java:2889)

E ActivityManager: at android.os.Binder.execTransact(Binder.java:565)

代码执行流程图

non-protected broadcast 查看原图

用到的代码文件

frameworks/base/services/core/java/
com/android/server/am/ActivityManagerService.java
com/android/server/pm/PackageManagerService.java

frameworks/base/core/java/
android/os/Process.java
android/app/AppGlobals.java
android/content/pm/PackageParser.java
android/content/res/AssetManager.java
android/content/res/XmlResourceParser.java
android/content/res/XmlBlock.java
res/res/values/attrs_manifest.xml

libcore/xml/src/main/java/
org/xmlpull/v1/XmlPullParser.java

模块编译

mm frameworks/base/services/ -> system/framework/services.jar
mm frameworks/base/core/res/ -> system/framework/framework-res.apk

代码执行流程分析

下面的代码是以android N(api 24)为基础。

本文基于高通(Qualcomm)平台基线。

由异常信息得知是ActivityManagerService类中broadcastIntentLocked()方法出现问题。
下面进入该方法。

final int broadcastIntentLocked(ProcessRecord callerApp,
        String callerPackage, Intent intent, String resolvedType,
        IIntentReceiver resultTo, int resultCode, String resultData,
        Bundle resultExtras, String[] requiredPermissions,
        int appOp, Bundle bOptions,
        boolean ordered, boolean sticky, int callingPid, 
        int callingUid, int userId) {
    /*callerApp: ProcessRecord{28e44c4 2407:
    com.android.bluetooth/1002}*/
    log("callerApp: "+callerApp);
    //com.android.bluetooth
    log("callerPackage: "+callerPackage);
    /*Intent { act=android.bluetooth.
        ftp.action.STATE_CHANGED (has extras) }*/
    log("intent: "+intent);
    log("resolvedType: "+resolvedType);//null
    log("resultTo: "+resultTo);//null
    log("resultCode: "+resultCode);//-1
    log("resultData: "+resultData);//null
    log("resultExtras: "+resultExtras);//null
    // [Ljava.lang.String;@f59560b
    log("requiredPermissions: "+requiredPermissions);
    log("appOp: "+appOp);//-1
    log("bOptions: "+bOptions);//null
    log("ordered: "+ordered);//false
    log("sticky: "+sticky);//false
    log("callingPid: "+callingPid);//2407
    log("callingUid: "+callingUid);//1002
    log("userId: "+userId);//0
    intent = new Intent(intent);
    // By default broadcasts do not go to stopped apps.
    intent.addFlags(Intent
            .FLAG_EXCLUDE_STOPPED_PACKAGES);

    // If we have not finished booting, 
    // don't allow this to launch new processes.
    if (!mProcessesReady && (intent.getFlags()
            &Intent.FLAG_RECEIVER_BOOT_UPGRADE) == 0) {
        intent.addFlags(Intent
                .FLAG_RECEIVER_REGISTERED_ONLY);
    }

    if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST,
            (sticky ? "Broadcast sticky: ": "Broadcast: ") + intent
            + " ordered=" + ordered + " userid=" + userId);
    if ((resultTo != null) && !ordered) {
        Slog.w(TAG, "Broadcast " + intent 
                + " not ordered but result callback requested!");
    }

    userId = mUserController.handleIncomingUser(callingPid, 
            callingUid, userId, true,
            ALLOW_NON_FULL, "broadcast", callerPackage);
    log("handleIncomingUser userId: "+userId);//0
    // Make sure that the user who is receiving this 
    // broadcast is running.
    // If not, we will just skip it. Make an exception for 
    // shutdown broadcasts and upgrade steps.

    if (userId != UserHandle.USER_ALL 
            && !mUserController.isUserRunningLocked(userId, 0)) {
        if ((callingUid != Process.SYSTEM_UID
                || (intent.getFlags() 
                & Intent.FLAG_RECEIVER_BOOT_UPGRADE) == 0)
                && !Intent.ACTION_SHUTDOWN
                        .equals(intent.getAction())) {
            Slog.w(TAG, "Skipping broadcast of " + intent
                    + ": user " + userId + " is stopped");
            return ActivityManager
                          .BROADCAST_FAILED_USER_STOPPED;
        }
    }

    .....

    // 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 {
        // com.android.server.pm.PackageManagerService
        log("AppGlobals.getPackageManager(): "
                +AppGlobals.getPackageManager().getClass().getName());
        isProtectedBroadcast = AppGlobals
                .getPackageManager().isProtectedBroadcast(action);
        ////action: android.bluetooth.ftp.action.STATE_CHANGED
        log("action: "+action);
        log("isProtectedBroadcast: "+isProtectedBroadcast);//false
    } catch (RemoteException e) {
        Slog.w(TAG, "Remote exception", e);
        return ActivityManager.BROADCAST_SUCCESS;
    }

    log("UserHandle.getAppId(callingUid): "
            +UserHandle.getAppId(callingUid));//1002
    final boolean isCallerSystem;
    switch (UserHandle.getAppId(callingUid)) {
        case Process.ROOT_UID://0
        case Process.SYSTEM_UID://1000
        case Process.PHONE_UID://1001
        case Process.BLUETOOTH_UID://1002
        case Process.NFC_UID://1027
            isCallerSystem = true;
            break;
        default:
            isCallerSystem = (callerApp != null) 
                                      && callerApp.persistent;
            break;
    }

    if(null!=callerApp){
    log("callerApp.persistent: "+callerApp.persistent);//false
    }

    if (isCallerSystem) {// -> true
        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)
                || AppWidgetManager
                        .ACTION_APPWIDGET_CONFIGURE.equals(action)
                || AppWidgetManager
                        .ACTION_APPWIDGET_UPDATE.equals(action)
                || LocationManager
                        .HIGH_POWER_REQUEST_CHANGE_ACTION
                        .equals(action)
                || TelephonyIntents
                        .ACTION_REQUEST_OMADM_CONFIGURATION_UPDATE
                        .equals(action)
                || SuggestionSpan
                        .ACTION_SUGGESTION_PICKED
                        .equals(action)) { // -> false
            // 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.
            /*
             * 问题出现在这里
             * 由判断条件可以得知当前广播是由系统app发出,
             * 但是action不是ProtectedBroadcast。
             */
            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());
            }
        }

    } 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)
                || 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,
            // just limit it to the caller.
            if (callerPackage == null) {
                String msg = 
                        "Permission Denial: not allowed to send broadcast "
                        + action + " from unknown caller.";
                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(
                        callerPackage)) {
                    String msg = 
                           "Permission Denial: not allowed to send broadcast "
                            + action + " to "
                            + intent.getComponent().getPackageName() 
                            + " from "
                            + callerPackage;
                    Slog.w(TAG, msg);
                    throw new SecurityException(msg);
                }
            } else {
                // Limit broadcast to their own package.
                intent.setPackage(callerPackage);
            }
        }
    }

    ......(此处省略小一千行代码......)

    return ActivityManager.BROADCAST_SUCCESS;
}

下面进入AppGlobals类中的getPackageManager()方法。

/**
 * Return the raw interface to the package manager.
 * @return The package manager.
 */
public static IPackageManager getPackageManager() {
    //返回的是PackageManagerService
    return ActivityThread.getPackageManager();
}

下面进入PackageManagerService类中的isProtectedBroadcast()方法。

@Override
public boolean isProtectedBroadcast(String actionName) {
    synchronized (mPackages) {
        if (mProtectedBroadcasts.contains(actionName)) {
            return true;
        } else 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;
            }
        }
    }
    return false;
}

从上面的方法可以看出该广播的action不包含在mProtectedBroadcasts这个变量中。
下面查找mProtectedBroadcasts这个变量的赋值,发现在PackageManagerService中只有一处 地方为其进行赋值。

下面是PackageManagerService类中的scanPackageDirtyLI()方法。

// Broadcast actions that are only available to the system.
final ArraySet<String> mProtectedBroadcasts = 
          new ArraySet<String>();

private PackageParser.Package scanPackageDirtyLI(
        PackageParser.Package pkg,
        final int policyFlags, final int scanFlags, 
        long currentTime, UserHandle user)
        throws PackageManagerException {
    ......
    if (pkg.protectedBroadcasts != null) {
        N = pkg.protectedBroadcasts.size();
        for (i=0; i<N; i++) {
            mProtectedBroadcasts
            .add(pkg.protectedBroadcasts.get(i));
        }
    }
    ......
}

下面看下PackageParser.Package这个类。
这个类中定义了“public ArrayList protectedBroadcasts;”这个变量。

变量protectedBroadcasts的赋值是在PackageParser类的parseBaseApkCommon()方法中。

private static final String TAG_PROTECTED_BROADCAST =
           "protected-broadcast";

private Package parseBaseApkCommon(Package pkg, 
        Set<String> acceptedTags, Resources res,
        XmlResourceParser parser, int flags, 
        String[] outError) throws XmlPullParserException,
        IOException {
    ......
    /**
      * 这块儿在进行manifest xml文件的解析
      * com.android.internal.R.styleable声明在attrs_manifest.xml文件中。
      *
      */
    } else if (tagName.equals(TAG_PROTECTED_BROADCAST)) {
        sa = res.obtainAttributes(parser,
                com.android.interna
                l.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);

    } else if (tagName.equals(TAG_INSTRUMENTATION)) {
    ......
}

上面方法中XmlResourceParser parser是怎么来的呢?
在PackageParser类中的parseBaseApk()方法中找到了。

private static final String ANDROID_MANIFEST_FILENAME =
         "AndroidManifest.xml";

private Package parseBaseApk(File apkFile, 
        AssetManager assets, int flags)
        throws PackageParserException {
    final String apkPath = apkFile.getAbsolutePath();
    ......
    Resources res = null;
    XmlResourceParser parser = null;
    try {
        res = new Resources(assets, mMetrics, null);
        assets.setConfiguration(0, 0, null, 
                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                Build.VERSION.RESOURCES_SDK_INT);
        /**
          * 关键点
          * ANDROID_MANIFEST_FILENAME = "AndroidManifest.xml";
          *
          */
        parser = assets.openXmlResourceParser(cookie, 
                ANDROID_MANIFEST_FILENAME);

        final String[] outError = new String[1];
        final Package pkg = parseBaseApk(res, parser, flags, 
                outError);
        if (pkg == null) {
            throw new PackageParserException(mParseError,
                    apkPath + " (at " 
                    + parser.getPositionDescription() + "): " + outError[0]);
        }

        pkg.setVolumeUuid(volumeUuid);
        pkg.setApplicationVolumeUuid(volumeUuid);
        pkg.setBaseCodePath(apkPath);
        pkg.setSignatures(null);

        return pkg;

    } catch (PackageParserException e) {
    ......
}

下面进入AssetManager类的openXmlResourceParser()方法中。

public final XmlResourceParser openXmlResourceParser(int cookie,
        String fileName) throws IOException {
    XmlBlock block = openXmlBlockAsset(cookie, fileName);
    XmlResourceParser rp = block.newParser();
    block.close();
    return rp;
}

下面进入XmlBlock类中的newParser()方法中。

public XmlResourceParser newParser() {
    synchronized (this) {
        if (mNative != 0) {
            /**
              * 终于找到了这个XML解析器了。
              * 这个Parse类是定义在XmlBlock中的内部类,并实
              * 现了XmlResourceParser这个接口。
              *
              * 经过查看XmlResourceParser接口实现
              * 了XmlPullParser接口。
              *
              * 在XmlPullParser接口中找到了http://xmlpull.org/,
              * 应该是一个第三方开源的解析器了。
              * 具体的就暂不研究了。
              */
            return new Parser(nativeCreateParseState(mNative), this);
        }
        return null;
    }
}

总结一下,android系统每次启动都会去解析system/framework/framework-res.apk,然后使用XmlResourceParser这个xml解析器去解析AndroidManifest.xml这个文件,如果遇到了tag “protected-broadcast”就将其对应的值存储在Package的protectedBroadcasts中。最终所有Package的信息都在PackageManagerService中进行汇总。将所有protected-broadcast储存在PackageManagerService中mProtectedBroadcasts这个ArraySet对象中。

当Bluetooth这个system app发送一个广播时,会在ActivityManagerService类的broadcastIntentLocked()方法中进行检查。如果当前广播是系统app发送但是action没有声明为protected-broadcast,那么就会打印一个错误信息。但是不影响该广播的后续发送和接收。如那块儿代码注释的一样,错误信息就是为了提示要处理这个问题,防止安全漏洞。

    // 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.

解决办法

因为这个action定义在frameworks/base/core/java/android/bluetooth/BluetoothFtpClient.java中,所以在frameworks/base/core/res/AndroidManifest.xml的manifest标签中声明一下就ok了。

<protected-broadcast 
        android:name="android.bluetooth.ftp.action.STATE_CHANGED" />

将framework-res.apk push到手机system/framework/目录,重启手机,验证解决问题,搞定!

  1. 啊钊, 你现在很可以!

    回复
    1. @老李

      rx,你这现在都是精通android和web前端了,我现在是追赶你的步伐......

      回复
  2. 你好,这个protected-broadcast标签具体写在manifest,哪个层级下面? 我写上去之后会提示Element protected-broadcast is not allow here

    回复
    1. @赵曜

      直接写在<manifest></manifest>之间的,但是有一点要注意:“这个广播是系统app才能使用的,普通的android应用是无法使用的”。

      回复