问题
异常信息
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)
代码执行流程图
用到的代码文件
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的赋值是在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/目录,重启手机,验证解决问题,搞定!
本文由 tuzhao 创作,采用 知识共享署名4.0 国际许可协议进行许可
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名
最后编辑时间为:
2017/12/05 00:20
如果action不是固定的,有解决方案吗
不是固定的话估计你就得修改framework的源码了,可能会用到aidl通信,自己去操作存储广播的变量。
啊钊, 你现在很可以!
rx,你这现在都是精通android和web前端了,我现在是追赶你的步伐......
你好,这个protected-broadcast标签具体写在manifest,哪个层级下面? 我写上去之后会提示Element protected-broadcast is not allow here
直接写在<manifest></manifest>之间的,但是有一点要注意:“这个广播是系统app才能使用的,普通的android应用是无法使用的”。