android自定义广播无作用,protected-broadcast 规范使用系统应用组件自定义广播

本文详细介绍了Android系统应用如何正确使用自定义广播,特别是涉及protected-broadcast的使用规范。内容包括:设置`android:sharedUserId="android.uid.system"`,AMS对系统应用广播的安全检查,以及如何避免非保护广播的安全风险。建议系统应用在发送广播时指定接收者并添加权限保护,以提升系统安全性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

protected-broadcast 系统应用自定义广播规范

一、android:sharedUserId=”android.uid.system”系统中所有使用android.uid.system作为共享UID的APK,都会首先在manifest节点中增加 android:sharedUserId=”android.uid.system”,然后在Android.mk中增加 LOCAL_CERTIFICATE := platform.如Settings,:

下面所说的系统应用也是指这些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里有声明为保护广播。

...

...

另外一些公共的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 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();

}

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;

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中有声明为保护广播。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值