protected-broadcast 系统应用自定义广播规范
一、android:sharedUserId=”android.uid.system”
- 系统中所有使用android.uid.system作为共享UID的APK,都会首先在manifest节点中增加 android:sharedUserId=”android.uid.system”,然后在Android.mk中增加 LOCAL_CERTIFICATE := platform.如Settings,:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" package="com.android.settings" coreApp="true" android:sharedUserId="android.uid.system" android:versionCode="1" android:versionName="1.0" >
下面所说的系统应用也是指这些shareUID是system的应用。
- 在开机PMS初始化的时候,将该name为“android.uid.system”的uid归为1000的system用户ID;
PackageManagerService.java
public PackageManagerService(Context context, Installer installer,
boolean factoryTest, boolean onlyCore) {
...
mSettings.addSharedUserLPw("android.uid.system", Process.SYSTEM_UID,
ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
...
}
Process.java
/** * Defines the UID/GID under which system code runs. */
public static final int SYSTEM_UID = 1000;
二、AMS对系统应用发出的广播进行安全检查
当应用或者组件发送广播时,在广播发送的必经之路上,AMS对系统应用发送的广播进行了检查:
1. 判断是否是系统uid,
ActivityServiceManager.java
final int broadcastIntentLocked(){
final boolean isCallerSystem;
switch (UserHandle.getAppId(callingUid)) {
//说明是以下几个uid的进程都认为是system应用进程
case Process.ROOT_UID:
case Process.SYSTEM_UID:
case Process.PHONE_UID:
case Process.BLUETOOTH_UID:
case Process.NFC_UID:
isCallerSystem = true;
break;
default:
isCallerSystem = (callerApp != null) && callerApp.persistent;
break;
}
}
这里需要注意的是除了SYSTEM_UID被认为是系统uid,还有ROOT_UID,PHONE_UID,BLUETOOTH_UID,NFC_UID,
需要特别注意的是如果都未定义以上的UID,但应用的AndroidManifest.xml中定义了persistent属性为true,即常驻应用,也会被设定为由系统调用,同样需要进行广播的权限检查。
2. 如果是SYSTEM_UID,便对该广播进行安全检查:
if (isCallerSystem) {
checkBroadcastFromSystem(intent, callerApp, callerPackage, callingUid,
isProtectedBroadcast, receivers);
}
3. 在checkBroadcastFromSystem()函数中进行安全检查:
private void checkBroadcastFromSystem(Intent intent, ProcessRecord callerApp,
String callerPackage, int callingUid, boolean isProtectedBroadcast, List receivers) {
final String action = intent.getAction();
if (isProtectedBroadcast //A如果是受保护的广播或者特殊的公共广播
|| 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)
|| 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)) {
// 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 (receivers != null && receivers.size() > 0
&& (intent.getPackage() != null || intent.getComponent() != null)) {
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;
//如果接收者exported设置为false或者接收者设置了权限,则说明做了保护,allProtect为Ture
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;
}
}
//C 这里会打出wtflog,同时在dropbox中会生成wtf文件
// 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());
}
}
1. framework中声明的保护广播和一些放开的公共广播:
isProtectedBroadcast = AppGlobals.getPackageManager().isProtectedBroadcast(action);
isProtectedBroadcast为true则代表该广播在Framework/base/core/res/AndroidManifest.xml中有声明为保护广播,这样的广播只能由系统发出。如果是phone进程的,一般在Teleservice下的AndroidManifest.xml中声明保护广播。 如果是系统应用,则可以在系统应用的AndroidManifest.xml里有声明为保护广播。
<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">
<!-- ================================================ -->
<!-- Special broadcasts that only the system can send -->
<!-- ================================================ -->
<eat-comment />
<protected-broadcast android:name="android.net.tether.CONNECTEDSTA_CHANGE" />
<protected-broadcast android:name="android.intent.action.SCREEN_OFF" />
<protected-broadcast android:name="android.intent.action.SCREEN_ON" />
...
<protected-broadcast android:name="android.intent.action.PRE_BOOT_COMPLETED" />
...
</manifest>
另外一些公共的action,虽然没有保护但是系统允许在系统内部发送;如Intent.ACTION_CLOSE_SYSTEM_DIALOGS,Intent.ACTION_DISMISS_KEYBOARD_SHORTCUTS等;
2. 未在Framework中声明保护广播或者非放开的公共广播
B处的大意是经常有系统组件想发送内部广播给自己,如果必须明确列出每个动作作为受保护的广播是很烦人的,因此在这里进行安全检查,如果符合安全条件及可以正常发送;如果不满足安全条件,则就会走到C处,打印wtf log。wtf的意思是What a Terrible Failure: Report an exception that should never happen.
虽然检查不安全但是系统还是会允许发送该广播,logcat中会出现这样的提醒log:
Sending non-protected broadcast xxxxx from system 7747:com.xxxx.xxxx/1000
pkg com.xxxxx.xxxxx
3. 未给广播加保护影响
虽然广播正常发送了,不影响广播的作用,但是这样的使用是不安全的,系统组件自定义的广播可能会被恶意软件接收或者发送,导致系统不稳定。
且在这个log打印同时会在系统的dropbox下新生成一个wtf的log文件,发多少条这样的广播,就生成多少个这样的文件。而dropbox中默认最多只有1000个文件,再多了就会冲掉旧的文件。Dropbox是我们用于分析ANR tombstone等问题的重要log来源,因此会严重影响稳定性同事对该类问题的统计和解决。
为了提高系统的安全性且避免这样的log,系统应用组件当在使用自发自收的广播时,要尽可能使用明确的广播,及指定接收的包名或者组件名,且对广播发送和接收加权限保护。同时这也使我们使用广播更加规范。
三、整改举例:
如果该广播是an explicit broadcast,且该receiver的android:exported为false,或者ri.activityInfo.permission!=null,及该receiver加了权限保护,系统则认为这个广播时做过保护了的,予以正常发送,便不会打这个log,是个规范使用的广播。
- 动态广播,系统应用可以使用本地广播进行操作,可以满足检查安全的需求。
- 静态广播:
a.如果是系统独立应用的广播,可以在应用的AndroidManifest.xml里声明为保护广播就可以了。不过注意验证的时候,需要使用adb push到system/app或者system/priv-app/下再重启安卓验证;使用adb install -r后验证依然会报未保护提醒。
==================
PS:
adb install 和adb push区别在于,adb install会将应用安装在data/app下,而adb push到system/priv-app下即安装在system/priv-app下,adb install和adb push重启后都会有一个扫描解析apk的动作
pkg = pp.parsePackage(tmpPackageFile, parseFlags);
最终调到:
private Package parseBaseApkCommon(){}
在parseBaseApkCommon()函数中:
//frameworks\base\core\java\android\content\pm\PackageParser.java
private Package parseBaseApkCommon(Package pkg, Set<String> acceptedTags, 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)) {
if(...){...
} else if (tagName.equals(TAG_PROTECTED_BROADCAST)) {
sa = res.obtainAttributes(parser,
com.android.internal.R.styleable.AndroidManifestProtectedBroadcast);
String name = sa.getNonResourceString( com.android.internal.R.styleable.AndroidManifestProtectedBroadcast_name);
sa.recycle();
if (name != null && (flags&PARSE_IS_SYSTEM) != 0) {//如果不是system的parser flag就跳过解析
if (pkg.protectedBroadcasts == null) {
pkg.protectedBroadcasts = new ArrayList<String>();
}
if (!pkg.protectedBroadcasts.contains(name)) {
pkg.protectedBroadcasts.add(name.intern());
}
}
XmlUtils.skipCurrentTag(parser);
}
}
...
}
在system/priv-app下扫描apk是是PARSE_IS_SYSTEM的 flag(在开机时的PMS的构造函数里就设置了扫描系统应用文件夹的parser flag),而data/app下没有这个system的parser flag
因此在这里adb push可以生效,adb install未生效。
==========
b. 使用指定包名并且加权限保护
b1.在Androidmanifest.xml里声明receiver的时候加上自定义的权限,如果是仅需应用内接收,可以将android:exported属性设置为false;
<receiver android:name=".DemoReceiver" android:exported="false" android:permission="com.android.permission.RECV.XXX">
<intent-filter android:priority="1000">
<action android:name="com.android.demo.test.XXX"/>
</intent-filter>
</receiver>
具体加权限请参考 http://blog.csdn.net/javensun/article/details/7334230
b2. 发送广播的地方指定包名或者组件名
Intent i = new Intent("com.android.demo.test.XXX");
i.setPackage("com.XXX.broadcasttest");
sendBroadcast(i,"com.android.permission.RECV.XXX");
注意指给广播加权限是不够的,在checkBroadcastFromSystem对未在framework中声明为保护广播的系统应用自定义广播进行安全检查的前提是这是一个explicit的广播。因此满足上面两步后才能真正消除警告的wtf log.
c. 如果是非独立应用的系统组件,或者是framework下的应用使用的广播,推荐在framework/base/core/res/AndroidManifest.xml中有声明为保护广播。
android exported属性:http://blog.csdn.net/watermusicyes/article/details/46460347
广播权限限制:http://blog.csdn.net/javensun/article/details/7334230
本地广播使用:http://blog.csdn.net/lj2012sy/article/details/51688985