Permission Denial: INTERACT_ACROSS_USERS_FULL or INTERACT_ACROSS_USERS

本文解析了Android系统中多用户环境下,非管理员账户操作导致的SecurityException异常,特别是与INTERACT_ACROSS_USERS_FULL权限相关的问题。提供了两种解决方案,一是调整活动启动方式,二是更改底层实现,避免直接调用系统接口。

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

今天遇到一个崩溃,具体crash日志如下:

Caused by: java.lang.SecurityException: Permission Denial: broadcast from android asks to run as user 0 but is calling from user 10; this requires android.permission.INTERACT_ACROSS_USERS_FULL or android.permission.INTERACT_ACROSS_USERS
        at android.os.Parcel.createException(Parcel.java:1953)
        at android.os.Parcel.readException(Parcel.java:1921)
        at android.os.Parcel.readException(Parcel.java:1871)
        at android.media.IAudioService$Stub$Proxy.setMicrophoneMute(IAudioService.java:1293)
        at android.media.AudioManager.setMicrophoneMute(AudioManager.java:1834)

        ...............
        at io.reactivex.internal.observers.LambdaObserver.onNext(LambdaObserver.java:63)
        at io.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.drainNormal(ObservableObserveOn.java:201) 
        at io.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.run(ObservableObserveOn.java:255) 
        at io.reactivex.android.schedulers.HandlerScheduler$ScheduledRunnable.run(HandlerScheduler.java:124) 
        at android.os.Handler.handleCallback(Handler.java:907) 
        at android.os.Handler.dispatchMessage(Handler.java:105) 
        at android.os.Looper.loop(Looper.java:216) 
        at android.app.ActivityThread.main(ActivityThread.java:7593) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:524) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:987) 
     Caused by: android.os.RemoteException: Remote stack trace:
        at com.android.server.am.UserController.handleIncomingUser(IIIZILjava/lang/String;Ljava/lang/String;)I(libmapleservices.so:3030397)
        at com.android.server.am.ActivityManagerService.broadcastIntentLocked(Lcom/android/server/am/ProcessRecord;Ljava/lang/String;Landroid/content/Intent;Ljava/lang/String;Landroid/content/IIntentReceiver;ILjava/lang/String;Landroid/os/Bundle;[Ljava/lang/String;ILandroid/os/Bundle;ZZIII)I(libmapleservices.so:4749673)
        at com.android.server.am.HwActivityManagerService.broadcastIntentLocked(Lcom/android/server/am/ProcessRecord;Ljava/lang/String;Landroid/content/Intent;Ljava/lang/String;Landroid/content/IIntentReceiver;ILjava/lang/String;Landroid/os/Bundle;[Ljava/lang/String;ILandroid/os/Bundle;ZZIII)I(libmaplehwServices.so:3359213)
2019-08-01 10:01:53.458 24987-24987/? E/AndroidRuntime:     at com.android.server.am.ActivityManagerService.broadcastIntent(Landroid/app/IApplicationThread;Landroid/content/Intent;Ljava/lang/String;Landroid/content/IIntentReceiver;ILjava/lang/String;Landroid/os/Bundle;[Ljava/lang/String;ILandroid/os/Bundle;ZZI)I(libmapleservices.so:4767617)
        at android.app.ContextImpl.sendBroadcast(Landroid/content/Intent;)V(libmapleframework.so:2763689)

原因:

与安卓的多用户管理有关系,特别是华为一些手机有多账号功能,用非管理员账户操作时容易出现这个问题。

解决:

方法一、从上层的角度考虑,可以修改活动的启动方式。参考链接:https://mazhuang.org/2016/02/10/am-start-user-parameter/

大致的解决思路,在 API level 17 以上的 Android 设备里,通过 am start 命令来启动 Activity 时会校验调用 am 命令的进程的 user id 与 am 进程从 –user 参数获取到的 user id(默认值为 UserHandle.USER_CURRENT,即 -2)是否相等, 如果想在 APP 或者 APP 调用的 Native 进程里使用 am start 来启动 Activity,那么需要给其传递能通过校验的 –user 参数,参数值可以直接硬编码为 0,也可以使用 android.os.Process.myUserHandle().hashCode() 的值。

如果不给 am start 传递正确的 –user 参数,那调用进程对应 uid 需要拥有 INTERACT_ACROSS_USERS_FULL 权限,但是该权限的 protectionLevel 为 signature|installer,一般场景下是无法获取到的。

方法二、从底层的角度考虑,改变实现方式。以本人遇到的问题为例,是因为调用系统接口setMicrophoneMute,导致getUserId不为0,这与接口的设计初衷是冲突的。所以,为了实现mute麦克风的功能,我采取了将采集后的音频数据抹掉的方法。这样就避免了调用setMicrophoneMute方法,绕开了多用户的坑。

06-05 06:24:05.669 5900 5900 D AndroidRuntime: Shutting down VM 06-05 06:24:05.672 5900 5900 E AndroidRuntime: FATAL EXCEPTION: main 06-05 06:24:05.672 5900 5900 E AndroidRuntime: Process: com.avatr.cockpit.settings, PID: 5900 06-05 06:24:05.672 5900 5900 E AndroidRuntime: java.lang.RuntimeException: Unable to create application com.avatr.cockpit.settings.CockpitSettingsApplication: java.lang.SecurityException: Permission Denial: Component com.avatr.cockpit.settings/.service.SettingsService requests FLAG_SINGLE_USER, but app does not hold android.permission.INTERACT_ACROSS_USERS 06-05 06:24:05.672 5900 5900 E AndroidRuntime: at android.app.ActivityThread.handleBindApplication(ActivityThread.java:7107) 06-05 06:24:05.672 5900 5900 E AndroidRuntime: at android.app.ActivityThread.-$$Nest$mhandleBindApplication(Unknown Source:0) 06-05 06:24:05.672 5900 5900 E AndroidRuntime: at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2305) 06-05 06:24:05.672 5900 5900 E AndroidRuntime: at android.os.Handler.dispatchMessage(Handler.java:106) 06-05 06:24:05.672 5900 5900 E AndroidRuntime: at android.os.Looper.loopOnce(Looper.java:218) 06-05 06:24:05.672 5900 5900 E AndroidRuntime: at android.os.Looper.loop(Looper.java:307) 06-05 06:24:05.672 5900 5900 E AndroidRuntime: at android.app.ActivityThread.main(ActivityThread.java:8309) 06-05 06:24:05.672 5900 5900 E AndroidRuntime: at java.lang.reflect.Method.invoke(Native Method) 06-05 06:24:05.672 5900 5900 E AndroidRuntime: at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:573) 06-05 06:24:05.672 5900 5900 E AndroidRuntime: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1049) 06-05 06:24:05.672 5900 5900 E AndroidRuntime: Caused by: java.lang.SecurityException: Permission Denial: Component com.avatr.cockpit.settings/.service.SettingsService requests FLAG_SINGLE_USER, but app does not hold android.permission.INTERACT_ACROSS_USERS 06-05 06:24:05.672 5900 5900 E AndroidRuntime: at android.os.Parcel.createExceptionOrNull(Parcel.java:3079) 06-05 06:24:05.672 5900 5900 E AndroidRuntime: at android.os.Parcel.createException(Parcel.java:3063) 06-05 06:24:05.672 5900 5900 E AndroidRuntime: at android.os.Parcel.readException(Parcel.java:3046) 06-05 06:24:05.672 5900 5900 E AndroidRuntime: at android.os.Parcel.readException(Parcel.java:2988) 06-05 06:24:05.672 5900 5900 E AndroidRuntime: at android.app.IActivityManager$Stub$Proxy.startService(IActivityManager.java:6047) 06-05 06:24:05.672 5900 5900 E AndroidRuntime: at android.app.ContextImpl.startServiceCommon(ContextImpl.java:1931) 06-05 06:24:05.672 5900 5900 E AndroidRuntime: at android.app.ContextImpl.startService(ContextImpl.java:1900) 06-05 06:24:05.672 5900 5900 E AndroidRuntime: at android.content.ContextWrapper.startService(ContextWrapper.java:825) 06-05 06:24:05.672 5900 5900 E AndroidRuntime: at com.avatr.cockpit.settings.CockpitSettingsApplication.onCreate(CockpitSettingsApplication.java:20) 06-05 06:24:05.672 5900 5900 E AndroidRuntime: at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1316) 06-05 06:24:05.672 5900 5900 E AndroidRuntime: at android.app.ActivityThread.handleBindApplication(ActivityThread.java:7102) 06-05 06:24:05.672 5900 5900 E AndroidRuntime: ... 9 more 06-05 06:24:05.672 5900 5900 E AndroidRuntime: Caused by: android.os.RemoteException: Remote stack trace: 06-05 06:24:05.672 5900 5900 E AndroidRuntime: at com.android.server.am.ActivityManagerService.isSingleton(ActivityManagerService.java:13546) 06-05 06:24:05.672 5900 5900 E AndroidRuntime: at com.android.server.am.ActiveServices.retrieveServiceLocked(ActiveServices.java:4394) 06-05 06:24:05.672 5900 5900 E AndroidRuntime: at com.android.server.am.ActiveServices.startServiceLocked(ActiveServices.java:833) 06-05 06:24:05.672 5900 5900 E AndroidRuntime: at com.android.server.am.ActiveServices.startServiceLocked(ActiveServices.java:794) 06-05 06:24:05.672 5900 5900 E AndroidRuntime: at com.android.server.am.ActivityManagerService.startService(ActivityManagerService.java:13415) 06-05 06:24:05.672 5900 5900 E AndroidRuntime:
06-07
你现在是一名资深的蓝牙工程师,请你详细讲解下面的BtpermissionUtils.java的代码: package com.android.server.bluetooth; 18 19 import static android.Manifest.permission.BLUETOOTH_CONNECT; 20 import static android.Manifest.permission.BLUETOOTH_PRIVILEGED; 21 import static android.content.pm.PackageManager.SIGNATURE_MATCH; 22 import static android.os.Process.SYSTEM_UID; 23 import static android.permission.PermissionManager.PERMISSION_GRANTED; 24 import static android.permission.PermissionManager.PERMISSION_HARD_DENIED; 25 26 import static com.android.server.bluetooth.ChangeIds.RESTRICT_ENABLE_DISABLE; 27 28 import android.annotation.RequiresPermission; 29 import android.annotation.SuppressLint; 30 import android.app.ActivityManager; 31 import android.app.AppOpsManager; 32 import android.app.admin.DevicePolicyManager; 33 import android.app.compat.CompatChanges; 34 import android.content.AttributionSource; 35 import android.content.ComponentName; 36 import android.content.Context; 37 import android.content.pm.ApplicationInfo; 38 import android.content.pm.PackageManager; 39 import android.os.Binder; 40 import android.os.Process; 41 import android.os.UserHandle; 42 import android.os.UserManager; 43 import android.permission.PermissionManager; 44 45 import java.util.Objects; 46 47 class BtPermissionUtils { 48 private static final String TAG = BtPermissionUtils.class.getSimpleName(); 49 50 private static final int FLAGS_SYSTEM_APP = 51 ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP; 52 53 private final int mSystemUiUid; 54 55 BtPermissionUtils(Context ctx) { 56 // Check if device is configured with no home screen, which implies no SystemUI. 57 int systemUiUid = -1; 58 try { 59 systemUiUid = 60 ctx.createContextAsUser(UserHandle.SYSTEM, 0) 61 .getPackageManager() 62 .getPackageUid( 63 "com.android.systemui", 64 PackageManager.PackageInfoFlags.of( 65 PackageManager.MATCH_SYSTEM_ONLY)); 66 Log.d(TAG, "Detected SystemUiUid: " + systemUiUid); 67 } catch (PackageManager.NameNotFoundException e) { 68 Log.w(TAG, "Unable to resolve SystemUI's UID."); 69 } 70 mSystemUiUid = systemUiUid; 71 } 72 73 /** 74 * Returns true if the BLUETOOTH_CONNECT permission is granted for the calling app. Returns 75 * false if the result is a soft denial. Throws SecurityException if the result is a hard 76 * denial. 77 * 78 * <p>Should be used in situations where the app op should not be noted. 79 */ 80 @SuppressLint("AndroidFrameworkRequiresPermission") 81 @RequiresPermission(BLUETOOTH_CONNECT) 82 static boolean checkConnectPermissionForDataDelivery( 83 Context ctx, 84 PermissionManager permissionManager, 85 AttributionSource source, 86 String message) { 87 final String permission = BLUETOOTH_CONNECT; 88 AttributionSource currentSource = 89 new AttributionSource.Builder(ctx.getAttributionSource()) 90 .setNext(Objects.requireNonNull(source)) 91 .build(); 92 final int result = 93 permissionManager.checkPermissionForDataDeliveryFromDataSource( 94 permission, currentSource, message); 95 if (result == PERMISSION_GRANTED) { 96 return true; 97 } 98 99 final String msg = "Need " + permission + " permission for " + source + ": " + message; 100 if (result == PERMISSION_HARD_DENIED) { 101 throw new SecurityException(msg); 102 } 103 Log.w(TAG, msg); 104 return false; 105 } 106 107 /** 108 * Return an empty string if the current call is allowed to toggle bluetooth state 109 * 110 * <p>Return the error description if this caller is not allowed to toggle Bluetooth 111 */ 112 String callerCanToggle( 113 Context ctx, 114 AttributionSource source, 115 UserManager userManager, 116 AppOpsManager appOpsManager, 117 PermissionManager permissionManager, 118 String message, 119 boolean requireForeground) { 120 if (isBluetoothDisallowed(userManager)) { 121 return "Bluetooth is not allowed"; 122 } 123 124 if (!checkBluetoothPermissions( 125 ctx, 126 source, 127 userManager, 128 appOpsManager, 129 permissionManager, 130 message, 131 requireForeground)) { 132 return "Missing Bluetooth permission"; 133 } 134 135 if (requireForeground && !checkCompatChangeRestriction(source, ctx)) { 136 return "Caller does not match restriction criteria"; 137 } 138 139 return ""; 140 } 141 142 static void enforcePrivileged(Context ctx) { 143 ctx.enforceCallingOrSelfPermission( 144 BLUETOOTH_PRIVILEGED, "Need BLUETOOTH_PRIVILEGED permission"); 145 } 146 147 static int getCallingAppId() { 148 return UserHandle.getAppId(Binder.getCallingUid()); 149 } 150 151 static boolean isCallerSystem(int callingAppId) { 152 return callingAppId == Process.SYSTEM_UID; 153 } 154 155 static boolean isCallerNfc(int callingAppId) { 156 return callingAppId == Process.NFC_UID; 157 } 158 159 private static boolean isCallerShell(int callingAppId) { 160 return callingAppId == Process.SHELL_UID; 161 } 162 163 private static boolean isCallerRoot(int callingAppId) { 164 return callingAppId == Process.ROOT_UID; 165 } 166 167 private boolean isCallerSystemUi(int callingAppId) { 168 return callingAppId == mSystemUiUid; 169 } 170 171 private static boolean isPrivileged(Context ctx, int pid, int uid) { 172 return (ctx.checkPermission(BLUETOOTH_PRIVILEGED, pid, uid) == PERMISSION_GRANTED) 173 || (ctx.getPackageManager().checkSignatures(uid, SYSTEM_UID) == SIGNATURE_MATCH); 174 } 175 176 private static boolean isProfileOwner(Context ctx, int uid, String packageName) { 177 Context userContext; 178 try { 179 userContext = 180 ctx.createPackageContextAsUser( 181 ctx.getPackageName(), 0, UserHandle.getUserHandleForUid(uid)); 182 } catch (PackageManager.NameNotFoundException e) { 183 Log.e(TAG, "Unknown package name"); 184 return false; 185 } 186 if (userContext == null) { 187 Log.e(TAG, "Unable to retrieve user context for " + uid); 188 return false; 189 } 190 DevicePolicyManager devicePolicyManager = 191 userContext.getSystemService(DevicePolicyManager.class); 192 if (devicePolicyManager == null) { 193 Log.w(TAG, "isProfileOwner: Error retrieving DevicePolicyManager service"); 194 return false; 195 } 196 return devicePolicyManager.isProfileOwnerApp(packageName); 197 } 198 199 private static boolean isDeviceOwner(Context ctx, int uid, String packageName) { 200 if (packageName == null) { 201 Log.e(TAG, "isDeviceOwner: packageName is null, returning false"); 202 return false; 203 } 204 205 DevicePolicyManager devicePolicyManager = ctx.getSystemService(DevicePolicyManager.class); 206 if (devicePolicyManager == null) { 207 Log.w(TAG, "isDeviceOwner: Error retrieving DevicePolicyManager service"); 208 return false; 209 } 210 UserHandle deviceOwnerUser = null; 211 ComponentName deviceOwnerComponent = null; 212 long ident = Binder.clearCallingIdentity(); 213 try { 214 deviceOwnerUser = devicePolicyManager.getDeviceOwnerUser(); 215 deviceOwnerComponent = devicePolicyManager.getDeviceOwnerComponentOnAnyUser(); 216 } finally { 217 Binder.restoreCallingIdentity(ident); 218 } 219 if (deviceOwnerUser == null 220 || deviceOwnerComponent == null 221 || deviceOwnerComponent.getPackageName() == null) { 222 return false; 223 } 224 225 return deviceOwnerUser.equals(UserHandle.getUserHandleForUid(uid)) 226 && deviceOwnerComponent.getPackageName().equals(packageName); 227 } 228 229 private static boolean isSystem(Context ctx, String packageName, int uid) { 230 long ident = Binder.clearCallingIdentity(); 231 try { 232 ApplicationInfo info = 233 ctx.getPackageManager() 234 .getApplicationInfoAsUser( 235 packageName, 0, UserHandle.getUserHandleForUid(uid)); 236 return (info.flags & FLAGS_SYSTEM_APP) != 0; 237 } catch (PackageManager.NameNotFoundException e) { 238 return false; 239 } finally { 240 Binder.restoreCallingIdentity(ident); 241 } 242 } 243 244 private static boolean isBluetoothDisallowed(UserManager userManager) { 245 final long callingIdentity = Binder.clearCallingIdentity(); 246 try { 247 return userManager.hasUserRestrictionForUser( 248 UserManager.DISALLOW_BLUETOOTH, UserHandle.SYSTEM); 249 } finally { 250 Binder.restoreCallingIdentity(callingIdentity); 251 } 252 } 253 254 /** 255 * Check ifthe packageName belongs to calling uid 256 * 257 * <p>A null package belongs to any uid 258 */ 259 private static void checkPackage(AppOpsManager appOpsManager, String packageName) { 260 final int callingUid = Binder.getCallingUid(); 261 if (packageName == null) { 262 Log.w(TAG, "checkPackage(): called with null packageName from " + callingUid); 263 return; 264 } 265 266 try { 267 appOpsManager.checkPackage(callingUid, packageName); 268 } catch (SecurityException e) { 269 Log.w(TAG, "checkPackage(): " + packageName + " does not belong to uid " + callingUid); 270 throw new SecurityException(e.getMessage()); 271 } 272 } 273 274 boolean checkIfCallerIsForegroundUser(UserManager userManager) { 275 final int callingUid = Binder.getCallingUid(); 276 final UserHandle callingUser = UserHandle.getUserHandleForUid(callingUid); 277 final UserHandle foregroundUser; 278 final UserHandle parentUser; 279 final long callingIdentity = Binder.clearCallingIdentity(); 280 try { 281 // `getCurrentUser` need to be call by system server because it require one of 282 // INTERACT_ACROSS_USERS | INTERACT_ACROSS_USERS_FULL 283 foregroundUser = UserHandle.of(ActivityManager.getCurrentUser()); 284 // `getProfileParent` need to be call by system server because it require one of 285 // MANAGE_USERS | INTERACT_ACROSS_USER and 286 parentUser = userManager.getProfileParent(callingUser); 287 } finally { 288 Binder.restoreCallingIdentity(callingIdentity); 289 } 290 final int callingAppId = UserHandle.getAppId(callingUid); 291 292 // TODO(b/280890575): Remove isCallerX() to only check isForegroundUser 293 294 final boolean valid = 295 Objects.equals(callingUser, foregroundUser) 296 || Objects.equals(parentUser, foregroundUser) 297 || isCallerNfc(callingAppId) 298 || isCallerSystemUi(callingAppId) 299 || isCallerShell(callingAppId); 300 301 if (!valid) { 302 Log.d( 303 TAG, 304 "checkIfCallerIsForegroundUser: REJECTED:" 305 + " callingUser=" 306 + callingUser 307 + " parentUser=" 308 + parentUser 309 + " foregroundUser=" 310 + foregroundUser 311 + " callingAppId=" 312 + callingAppId); 313 } 314 return valid; 315 } 316 317 @RequiresPermission(BLUETOOTH_CONNECT) 318 private boolean checkBluetoothPermissions( 319 Context ctx, 320 AttributionSource source, 321 UserManager userManager, 322 AppOpsManager appOpsManager, 323 PermissionManager permissionManager, 324 String message, 325 boolean requireForeground) { 326 final int callingAppId = getCallingAppId(); 327 if (isCallerSystem(callingAppId)) return true; 328 if (isCallerShell(callingAppId)) return true; 329 if (isCallerRoot(callingAppId)) return true; 330 checkPackage(appOpsManager, source.getPackageName()); 331 332 if (requireForeground && !checkIfCallerIsForegroundUser(userManager)) { 333 Log.w(TAG, "Not allowed for non-active and non system user"); 334 return false; 335 } 336 337 if (!checkConnectPermissionForDataDelivery(ctx, permissionManager, source, message)) { 338 return false; 339 } 340 return true; 341 } 342 343 /** Starting from T, enable/disable APIs are limited to system apps or device owners. */ 344 private static boolean checkCompatChangeRestriction(AttributionSource source, Context ctx) { 345 final String packageName = source.getPackageName(); 346 347 final int callingUid = Binder.getCallingUid(); 348 final int callingPid = Binder.getCallingPid(); 349 if (CompatChanges.isChangeEnabled(RESTRICT_ENABLE_DISABLE, callingUid) 350 && !isPrivileged(ctx, callingPid, callingUid) 351 && !isSystem(ctx, packageName, callingUid) 352 && !isDeviceOwner(ctx, callingUid, packageName) 353 && !isProfileOwner(ctx, callingUid, packageName)) { 354 Log.e(TAG, "Caller is not one of: privileged | system | deviceOwner | profileOwner"); 355 return false; 356 } 357 return true; 358 } 359 }
07-18
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Data-Mining

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值