protected-broadcast 系統應用自定義廣播規範
轉載:https://blog.csdn.net/TaylorPotter/article/details/70194248
<h1 id="protected-broadcast-系統應用自定義廣播規範"><a name="t0"></a>protected-broadcast 系統應用自定義廣播規範</h1>
一、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" >
- 1
- 2
- 3
- 4
- 5
- 6
- 7
下面所說的系統應用也是指這些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);
...
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
Process.java
/**
* Defines the UID/GID under which system code runs.
*/
public static final int SYSTEM_UID = 1000;
- 1
- 2
- 3
- 4
- 5
二、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;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
這裡需要注意的是除了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);
}
- 1
- 2
- 3
- 4
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
- 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
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
1. framework中宣告的保護廣播和一些放開的公共廣播:
isProtectedBroadcast = AppGlobals.getPackageManager().isProtectedBroadcast(action);
- 1
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>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
另外一些公共的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
- 1
- 2
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(){}
- 1
- 2
- 3
在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);
}
}
...
}
- 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
在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>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
具體加許可權請參考 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");
- 1
- 2
- 3
注意指給廣播加許可權是不夠的,在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