删除Shell应用后adb install不能安装应用

本文深入探讨了在删除/system/priv-app/Shell后,使用ADB安装应用时遇到的SecurityException错误。分析了错误原因在于调用者缺少INTERACT_ACROSS_USERS_FULL权限,并详细解释了权限检查流程及解决方案。
部署运行你感兴趣的模型镜像

平台

RK3288 + Android 7.1

问题:

删除 /system/priv-app/Shell 后, 通过adb install安装应用出现:

adb shell install xxx.apk
Error: java.lang.SecurityException: Permission Denial: runInstallCreate from pm command asks to run as user -1 but is calling from user 0; this requires android.permission.INTERACT_ACROSS_USERS_FULL

此时, 使用adb root 再执行adb install 则不会存在相同的问题.

分析:

首先, adb install 执行后, 会调用系统中的pm 命令来进行安装.

|-- frameworks/base/cmds/pm/src/com/android/commands/pm/Pm.java
    private int runInstallCreate() throws RemoteException {
        final InstallParams installParams = makeInstallParams();
        final int sessionId = doCreateSession(installParams.sessionParams,
                installParams.installerPackageName, installParams.userId);

        // NOTE: adb depends on parsing this string
        System.out.println("Success: created install session [" + sessionId + "]");
        return PackageInstaller.STATUS_SUCCESS;
    }

	//
	private InstallParams makeInstallParams() {
        final SessionParams sessionParams = new SessionParams(SessionParams.MODE_FULL_INSTALL);
        final InstallParams params = new InstallParams();
        params.sessionParams = sessionParams;
...
                case "--user":
                    params.userId = UserHandle.parseUserArg(nextOptionData());
                    break;
...
        }

        return params;
    }

	private static class InstallParams {
        SessionParams sessionParams;
        String installerPackageName;
		//这里对应的是LOG中的 user -1
        int userId = UserHandle.USER_ALL;
    }

	private int doCreateSession(SessionParams params, String installerPackageName, int userId)
            throws RemoteException {
		//下面是真正抛异常的函数
        userId = translateUserId(userId, "runInstallCreate");
        if (userId == UserHandle.USER_ALL) {
            userId = UserHandle.USER_SYSTEM;
            params.installFlags |= PackageManager.INSTALL_ALL_USERS;
        }

        final int sessionId = mInstaller.createSession(params, installerPackageName, userId);
        return sessionId;
    }

	private int translateUserId(int userId, String logContext) {
        return ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
                userId, true, true, logContext, "pm command");
    }
|--frameworks/base/core/java/android/app/ActivityManager.java
    public static int handleIncomingUser(int callingPid, int callingUid, int userId,
            boolean allowAll, boolean requireFull, String name, String callerPackage) {
        if (UserHandle.getUserId(callingUid) == userId) {
            return userId;
        }
        try {
            return ActivityManagerNative.getDefault().handleIncomingUser(callingPid,
                    callingUid, userId, allowAll, requireFull, name, callerPackage);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }
|--frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
    @Override
    public int handleIncomingUser(int callingPid, int callingUid, int userId, boolean allowAll,
            boolean requireFull, String name, String callerPackage) {
        return mUserController.handleIncomingUser(callingPid, callingUid, userId, allowAll,
                requireFull ? ALLOW_FULL_ONLY : ALLOW_NON_FULL, name, callerPackage);
    }
|--frameworks/base/services/core/java/com/android/server/am/UserController.java
    int handleIncomingUser(int callingPid, int callingUid, int userId, boolean allowAll,
            int allowMode, String name, String callerPackage) {
        final int callingUserId = UserHandle.getUserId(callingUid);
        if (callingUserId == userId) {
            return userId;
        }

        // Note that we may be accessing mCurrentUserId outside of a lock...
        // shouldn't be a big deal, if this is being called outside
        // of a locked context there is intrinsically a race with
        // the value the caller will receive and someone else changing it.
        // We assume that USER_CURRENT_OR_SELF will use the current user; later
        // we will switch to the calling user if access to the current user fails.
        int targetUserId = unsafeConvertIncomingUserLocked(userId);

        if (callingUid != 0 && callingUid != SYSTEM_UID) {
            final boolean allow;
            if (mService.checkComponentPermission(INTERACT_ACROSS_USERS_FULL, callingPid,
                    callingUid, -1, true) == PackageManager.PERMISSION_GRANTED) {
                // If the caller has this permission, they always pass go.  And collect $200.
                allow = true;
            } else if (allowMode == ALLOW_FULL_ONLY) {
                // We require full access, sucks to be you.
                allow = false;
            } else if (mService.checkComponentPermission(INTERACT_ACROSS_USERS, callingPid,
                    callingUid, -1, true) != PackageManager.PERMISSION_GRANTED) {
                // If the caller does not have either permission, they are always doomed.
                allow = false;
            } else if (allowMode == ALLOW_NON_FULL) {
                // We are blanket allowing non-full access, you lucky caller!
                allow = true;
            } else if (allowMode == ALLOW_NON_FULL_IN_PROFILE) {
                // We may or may not allow this depending on whether the two users are
                // in the same profile.
                allow = isSameProfileGroup(callingUserId, targetUserId);
            } else {
                throw new IllegalArgumentException("Unknown mode: " + allowMode);
            }
			//出错LOG打印的地方:
            if (!allow) {
                if (userId == UserHandle.USER_CURRENT_OR_SELF) {
                    // In this case, they would like to just execute as their
                    // owner user instead of failing.
                    targetUserId = callingUserId;
                } else {
                    StringBuilder builder = new StringBuilder(128);
                    builder.append("Permission Denial: ");
                    builder.append(name);
                    if (callerPackage != null) {
                        builder.append(" from ");
                        builder.append(callerPackage);
                    }
                    builder.append(" asks to run as user ");
                    builder.append(userId);
                    builder.append(" but is calling from user ");
                    builder.append(UserHandle.getUserId(callingUid));
                    builder.append("; this requires ");
                    builder.append(INTERACT_ACROSS_USERS_FULL);
                    if (allowMode != ALLOW_FULL_ONLY) {
                        builder.append(" or ");
                        builder.append(INTERACT_ACROSS_USERS);
                    }
                    String msg = builder.toString();
                    Slog.w(TAG, msg);
                    throw new SecurityException(msg);
                }
            }
        }
        if (!allowAll && targetUserId < 0) {
            throw new IllegalArgumentException(
                    "Call does not support special user #" + targetUserId);
        }
        // Check shell permission
        if (callingUid == Process.SHELL_UID && targetUserId >= UserHandle.USER_SYSTEM) {
            if (hasUserRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, targetUserId)) {
                throw new SecurityException("Shell does not have permission to access user "
                        + targetUserId + "\n " + Debug.getCallers(3));
            }
        }
        return targetUserId;
    }

到这里, 问题比较明显跟

mService.checkComponentPermission(INTERACT_ACROSS_USERS_FULL, callingPid,
                    callingUid, -1, true) == PackageManager.PERMISSION_GRANTED

这行代码有关.

|--frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
    int checkComponentPermission(String permission, int pid, int uid,
            int owningUid, boolean exported) {
        if (pid == MY_PID) {
            return PackageManager.PERMISSION_GRANTED;
        }
        return ActivityManager.checkComponentPermission(permission, uid,
                owningUid, exported);
    }
|--frameworks/base/core/java/android/app/ActivityManager.java
    /** @hide */
    public static int checkComponentPermission(String permission, int uid,
            int owningUid, boolean exported) {
        // Root, system server get to do everything.
        final int appId = UserHandle.getAppId(uid);
        if (appId == Process.ROOT_UID || appId == Process.SYSTEM_UID) {
            return PackageManager.PERMISSION_GRANTED;
        }
        // Isolated processes don't get any permissions.
        if (UserHandle.isIsolated(uid)) {
            return PackageManager.PERMISSION_DENIED;
        }
        // If there is a uid that owns whatever is being accessed, it has
        // blanket access to it regardless of the permissions it requires.
        if (owningUid >= 0 && UserHandle.isSameApp(uid, owningUid)) {
            return PackageManager.PERMISSION_GRANTED;
        }
        // If the target is not exported, then nobody else can get to it.
        if (!exported) {
            /*
            RuntimeException here = new RuntimeException("here");
            here.fillInStackTrace();
            Slog.w(TAG, "Permission denied: checkComponentPermission() owningUid=" + owningUid,
                    here);
            */
            return PackageManager.PERMISSION_DENIED;
        }
        if (permission == null) {
            return PackageManager.PERMISSION_GRANTED;
        }
        try {
            return AppGlobals.getPackageManager()
                    .checkUidPermission(permission, uid);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }
frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
    @Override
    public int checkUidPermission(String permName, int uid) {
        final int userId = UserHandle.getUserId(uid);

        if (!sUserManager.exists(userId)) {
            return PackageManager.PERMISSION_DENIED;
        }

        synchronized (mPackages) {
            Object obj = mSettings.getUserIdLPr(UserHandle.getAppId(uid));
            if (obj != null) {
                final SettingBase ps = (SettingBase) obj;
                final PermissionsState permissionsState = ps.getPermissionsState();
                if (permissionsState.hasPermission(permName, userId)) {
                    return PackageManager.PERMISSION_GRANTED;
                }
                // Special case: ACCESS_FINE_LOCATION permission includes ACCESS_COARSE_LOCATION
                if (Manifest.permission.ACCESS_COARSE_LOCATION.equals(permName) && permissionsState
                        .hasPermission(Manifest.permission.ACCESS_FINE_LOCATION, userId)) {
                    return PackageManager.PERMISSION_GRANTED;
                }
            } else {
                ArraySet<String> perms = mSystemPermissions.get(uid);
                if (perms != null) {
                    if (perms.contains(permName)) {
                        return PackageManager.PERMISSION_GRANTED;
                    }
                    if (Manifest.permission.ACCESS_COARSE_LOCATION.equals(permName) && perms
                            .contains(Manifest.permission.ACCESS_FINE_LOCATION)) {
                        return PackageManager.PERMISSION_GRANTED;
                    }
                }
            }
        }

        return PackageManager.PERMISSION_DENIED;
    }

实际上, 就是去检查调用者(shell)是否包含INTERACT_ACROSS_USERS_FULL的权限.
通过命令可以看出当前adb调用的用户信息:

adb shell id
uid=2000(shell) gid=2000(shell) groups=2000(shell),1004(input),1007(log),1011(adb),1015(sdcard_rw),1028(sdcard_r),3001(net_bt_admin),3002(net_bt),3003(inet),3006(net_bw_stats),3009(readproc) context=u:r:shell:s0

为什么会跟 Shell这个应用有关系?

|--frameworks/base/packages/Shell/AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.android.shell"
        coreApp="true"
        android:sharedUserId="android.uid.shell"
        >

继续用命令可以获得结果:

adb shell pm dump com.android.shell结果中有:
  Packages:
    Package [com.android.shell] (e456cc6):
      userId=2000

所以, shell用户是否包含INTERACT_ACROSS_USERS_FULL的权限是由 Shell这个应用决定的.
若删除掉Shell 则会导致权限缺失.

PS

若通过remount 后再删除Shell应用, 重启等操作, adb install 工作都是正常的.
会出问题一般是在烧录前或恢复出厂前, Shell不存在

您可能感兴趣的与本文相关的镜像

GPT-oss:20b

GPT-oss:20b

图文对话
Gpt-oss

GPT OSS 是OpenAI 推出的重量级开放模型,面向强推理、智能体任务以及多样化开发场景

<think>我们正在讨论的是在Android应用内部执行APK安装(自安装)的场景。用户的问题是在应用内部执行自动安装时应该使用`pm install`还是`adb install`命令。 关键点: 1. `adb install`是用于从开发机器(通过USB连接)向设备安装应用的工具,它运行在主机上(PC/Mac),而不是设备上。 2. `pm install`是设备上的包管理器(Package Manager)命令,可以在设备的shell环境中运行,用于安装应用。 在Android应用内部执行安装(自安装)时: - 我们不能使用`adb install`,因为`adb`是一个调试桥工具,它需要从外部机器(PC)执行,而不是从设备上的应用内部执行。 - 我们应该使用`pm install`命令(或者更准确地说,是通过代码调用包管理器的安装功能),因为这是在设备内部执行安装的正确方式。 但是,在应用内部执行`pm install`命令需要特别注意: 1. 需要系统权限:普通应用无法直接执行`pm install`命令,因为它需要系统权限(`android.permission.INSTALL_PACKAGES`)并且必须是系统应用(使用系统签名,并声明共享用户ID为`android.uid.system`)。 2. 执行方式:在应用内部,我们可以通过以下方式之一执行安装: a. 使用`Runtime.getRuntime().exec()`执行`pm install`命令(需要系统权限,且设备已root或应用具有系统权限)。 b. 使用PackageManager的隐藏API(通过反射调用)来执行安装,这同样需要系统权限。 引用说明: - 引用[1]提到:`pm install`命令使用前需要将APK推入设备,而`adb install`则不需要(因为它是从PC端拷贝文件到设备并安装)。但在应用内部,我们已经有APK文件在设备上(例如在应用的私有目录或外部存储中),所以我们不需要像PC端使用`adb`那样先推送文件。 - 引用[2][3][4]都提到了ADB安装流程,但这是从PC端通过ADB命令安装的流程,不适用于应用内部自安装。 因此,在Android应用内部执行自安装时,正确的选择是使用`pm install`命令(通过执行shell命令)或者使用PackageManager的安装API(需要系统权限)。 但是,对于普通应用(非系统应用),无法使用上述方法,只能通过系统提供的安装界面(使用Intent启动安装): ```java Intent intent = new Intent(Intent.ACTION_VIEW); intent.setDataAndType(Uri.fromFile(new File(apkPath)), "application/vnd.android.package-archive"); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent); ``` 注意:在Android 7.0及以上,不能直接使用`file://`URI,需要使用FileProvider。 总结: - 如果应用是系统应用(具有系统签名和安装权限),则可以使用`pm install`命令(通过执行shell命令)或者反射调用PackageManager的隐藏方法实现静默安装- 如果是普通应用,则只能通过启动系统安装界面(使用Intent)来安装。 因此,回答用户的问题:在应用内执行自动安装时,应该使用`pm install`命令(但前提是应用具有系统权限),否则只能使用Intent打开安装界面。 下面给出一个完整的示例(系统应用使用命令安装):</think>在 Android 应用内执行自安装时,**必须使用 `pm install` 命令**而非 `adb install`。以下是详细解析和实现方案: ### 核心区别 | **命令** | **使用场景** | **应用内可行性** | |----------------|---------------------------|-----------------| | `adb install` | 从PC通过USB调试连接安装 | ❌ 不可用(需外部PC) | | `pm install` | 设备内部直接安装APK | ✅ 可用(需系统权限) | ### 系统应用内实现方案(Java) ```java import android.content.Context; import java.io.DataOutputStream; public class SilentInstaller { // 静默安装APK(需系统权限) public static boolean installPackage(Context context, String apkPath) { try { // 构建pm install命令 String command = "pm install -r " + apkPath; // -r表示升级安装 // 获取root权限执行命令 Process process = Runtime.getRuntime().exec("su"); DataOutputStream os = new DataOutputStream(process.getOutputStream()); os.writeBytes(command + "\n"); os.writeBytes("exit\n"); os.flush(); // 检查执行结果 int exitCode = process.waitFor(); return (exitCode == 0); // 返回0表示成功 } catch (Exception e) { e.printStackTrace(); return false; } } } ``` ### 关键配置要求 1. **系统权限声明**(AndroidManifest.xml): ```xml <uses-permission android:name="android.permission.INSTALL_PACKAGES" /> <application android:sharedUserId="android.uid.system" /> ``` 2. **系统签名**:应用必须使用平台密钥签名(如`platform.pk8`和`platform.x509.pem`) 3. **APK存储位置**:必须位于系统可访问路径(如`/data/local/tmp/`) ### 使用示例 ```java // 升级安装/data/local/tmp/update.apk String apkPath = "/data/local/tmp/update.apk"; boolean success = SilentInstaller.installPackage(context, apkPath); if (success) { // 安装成功处理 } else { // 安装失败处理 } ``` ### 版本适配关键点 1. **Android 7.0+** 需使用FileProvider: ```java if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { Uri uri = FileProvider.getUriForFile(context, "com.example.fileprovider", apkFile); command = "pm install -r -d " + uri.toString(); } ``` 2. **Android 10+** 添加权限: ```xml <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" /> ``` 3. **多用户支持**: ```java command = "pm install -r --user " + Process.myUserHandle() + " " + apkPath; ``` ### 为什么不用 adb install1. **依赖外部连接**:`adb install` 需要PC通过USB连接设备[^1] 2. **权限限制**:普通应用无法调用ADB守护进程 3. **执行环境**:`adb install` 本质是在PC端执行的命令[^3],而 `pm install` 是设备内置的包管理器命令 > **重要提示**:此方案仅适用于**系统应用**(预置在`/system/priv-app/`)。普通应用需改用`Intent`跳转系统安装界面: > ```java > Intent intent = new Intent(Intent.ACTION_VIEW); > intent.setDataAndType(Uri.fromFile(apkFile), "application/vnd.android.package-archive"); > startActivity(intent); > ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值