1.背景

​ 账号应用在开发过程中发送自定义的受保护广播(protected-broadcast修饰的广播)后发生了crash,日志如下:

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
com.gxatek.cockpit.account E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.gxatek.cockpit.account, PID: 17800
java.lang.SecurityException: Permission Denial: not allowed to send broadcast com.gxa.cockpit.hvac.service from pid=17800, uid=10028
at android.os.Parcel.createException(Parcel.java:1950)
at android.os.Parcel.readException(Parcel.java:1918)
at android.os.Parcel.readException(Parcel.java:1868)
at android.app.IActivityManager$Stub$Proxy.broadcastIntent(IActivityManager.java:3894)
at android.app.ContextImpl.sendBroadcast(ContextImpl.java:1009)
at android.content.ContextWrapper.sendBroadcast(ContextWrapper.java:444)
at com.gxatek.cockpit.account.model.OpenAppModel.gotoHvac(OpenAppModel.java:107)
at com.gxatek.cockpit.account.presentation.presenter.HomePresenter.openHvacHmi(HomePresenter.java:306)
at com.gxatek.cockpit.account.presentation.view.fragment.HomeAccountFragment.onClick(HomeAccountFragment.java:502)
at android.view.View.performClick(View.java:6597)
at android.view.View.performClickInternal(View.java:6574)
at android.view.View.access$3100(View.java:778)
at android.view.View$PerformClick.run(View.java:25885)
at android.os.Handler.handleCallback(Handler.java:873)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:6718)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
Caused by: android.os.RemoteException: Remote stack trace:
at com.android.server.am.ActivityManagerService.broadcastIntentLocked(ActivityManagerService.java:21358)
at com.android.server.am.ActivityManagerService.broadcastIntent(ActivityManagerService.java:21987)
at android.app.IActivityManager$Stub.onTransact$broadcastIntent$(IActivityManager.java:10171)
at android.app.IActivityManager$Stub.onTransact(IActivityManager.java:167)
at com.android.server.am.ActivityManagerService.onTransact(ActivityManagerService.java:3291)

2.问题调查

2.1 源码分析
1
2
3
4
5
// 从log获得到关键信息如下,
// 发送受保护的广播名字:com.gxa.cockpit.hvac.service
// 账号的pid:17800
// 账号的uid:10028
Permission Denial: not allowed to send broadcast com.gxa.cockpit.hvac.service from pid=17800, uid=10028

顺着调用堆栈,查看ActivityManagerService源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
public final int broadcastIntent(IApplicationThread caller,
Intent intent, String resolvedType, IIntentReceiver resultTo,
int resultCode, String resultData, Bundle resultExtras,
String[] requiredPermissions, int appOp, Bundle bOptions,
boolean serialized, boolean sticky, int userId) {
enforceNotIsolatedCaller("broadcastIntent");
synchronized(this) {
intent = verifyBroadcastLocked(intent);

final ProcessRecord callerApp = getRecordForAppLocked(caller);
final int callingPid = Binder.getCallingPid();
final int callingUid = Binder.getCallingUid();
final long origId = Binder.clearCallingIdentity();
// 调用broadcastIntentLocked发送广播
int res = broadcastIntentLocked(callerApp,
callerApp != null ? callerApp.info.packageName : null,
intent, resolvedType, resultTo, resultCode, resultData, resultExtras,
requiredPermissions, appOp, bOptions, serialized, sticky,
callingPid, callingUid, userId);
Binder.restoreCallingIdentity(origId);
return res;
}
}
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
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){
......
// Verify that protected broadcasts are only being sent by system code,
// and that system code is only sending protected broadcasts.

// AndroidManifest.xml中定义了<protected-broadcast>定义的受保护广播
final boolean isProtectedBroadcast;
try {
isProtectedBroadcast = AppGlobals.getPackageManager().isProtectedBroadcast(action);
} catch (RemoteException e) {
Slog.w(TAG, "Remote exception", e);
return ActivityManager.BROADCAST_SUCCESS;
}

// 根据uid和应用中persistent标志位判断当前进程是不是isCallerSystem
final boolean isCallerSystem;
switch (UserHandle.getAppId(callingUid)) {
case ROOT_UID:
case SYSTEM_UID:
case PHONE_UID:
case BLUETOOTH_UID:
case NFC_UID:
case SE_UID:
isCallerSystem = true;
break;
default:
isCallerSystem = (callerApp != null) && callerApp.persistent;
break;
}

// First line security check before anything else: stop non-system apps from
// sending protected broadcasts.
// 如果不是系统应用,且发送了受保护广播,就抛出安全异常。
if (!isCallerSystem) {
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) {
......
}

因此,查看应用AndroidManifest.xml配置文件。

2.2 分析配置文件
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
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.gxatek.cockpit.account"
android:sharedUserId="android.uid.systemui"
android:versionCode="1"
android:versionName="1.0">

<uses-sdk tools:overrideLibrary="com.bumptech.glide" />

<!-- android:sharedUserId="android.uid.systemui" -->
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

<protected-broadcast android:name="com.gxa.cockpit.hvac.service" />

<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:name=".MyApplication"
android:usesCleartextTraffic="true"
android:theme="@style/AppThemeWhite">

......

通过查看,账号应用没有配置persistent。

通知账号应用的开发配置该参数为true然后打个包再试了一下。发现还是在崩溃,发现配置没有生效。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 根据uid和应用中persistent标志位判断当前进程是不是isCallerSystem
final boolean isCallerSystem;
switch (UserHandle.getAppId(callingUid)) {
case ROOT_UID:
case SYSTEM_UID:
case PHONE_UID:
case BLUETOOTH_UID:
case NFC_UID:
case SE_UID:
isCallerSystem = true;
break;
default:
isCallerSystem = (callerApp != null) && callerApp.persistent;
break;
}

分析发现,配置persistent是在default分支判断的。查看账号的uid还是不为1000,说明还不是系统应用。

继续看Manifest配置文件发现,账号的sharedUserIdandroid.uid.systemui不是系统,而是和systemui共享uid。

通知账号开发修改sharedUserIdandroid.uid.system然后再验证。

验证结果显示该配置是生效的。问题得到解决。