Android7.0的静默安装失败问题研究

Android7.0的静默安装失败问题研究

最近遇到了在Android7.0上静默安装失败的问题。应用程序放到系统分区(/system/priv-app/)执行pm命令实现静默安装的方式在其他版本上都可以实现静默安装,在7.0上就安装失败,报这个异常:

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

至于安装失败肯定是pm instll的执行逻辑做了修改,我们直接从Pm的源码开始研究。
网上关于7.0静默安装的资料确实很少,没办法了只能先去查看源码看为啥报这个异常了。手头上也没有7.0的源码,只能去下载了,20个G的源码下载了三个小时左右。7.0源码下载,清华大学Android源码镜像站,在解压的aosp目录下面执行repo sync即可得到完整目录。
看frameworks的,源码查看工具我用的是understand,全局搜索runInstallCreate from pm command asks to run as user没搜到,说明这句话是组装成的,所以拆开搜索,全局搜索asks to run as user,这次搜到了。该方法在UserController类中

int handleIncomingUser(int callingPid, int callingUid, int userId, boolean allowAll,
            int allowMode, String name, String callerPackage) {
        //测试发现这个getUserId的返回值应该为0
        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);
            }
            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;
    }

研究发现callingUserId = UserHandle.getUserId(callingUid)的返回值为0,如果userId的值和callingUserId的值相同就不会执行下面的方法,也就不会抛出异常了。那我们就继续追踪看看userId是什么。这次我们Pm的源码开始,pm install就是在Pm这个类中执行的,调用的是runInstall方法,runInstall的源码就不贴出来了,我们这里只关心两个方法
final InstallParams params = makeInstallParams();和final int sessionId = doCreateSession(params.sessionParams,params.installerPackageName, params.userId);
makeInstallParams的源码:该方法是解析命令中的参数的,这里只看主要的

private InstallParams makeInstallParams() {
        final SessionParams sessionParams = new SessionParams(SessionParams.MODE_FULL_INSTALL);
        final InstallParams params = new InstallParams();
        params.sessionParams = sessionParams;
        String opt;
        while ((opt = nextOption()) != null) {
            switch (opt) {
               ....
                case "-i":
                    params.installerPackageName = nextArg();
                    if (params.installerPackageName == null) {
                        throw new IllegalArgumentException("Missing installer package");
                    }
                    break;
               ...
               ...
                case "--user":
                    params.userId = UserHandle.parseUserArg(nextOptionData());
                    break;
                ....
                default:
                    throw new IllegalArgumentException("Unknown option " + opt);
            }
        }
        return params;
    }

doCreateSession方法中回去调用userId = translateUserId(userId, "runInstallCreate");
translateUserId中回去调用ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId, true, true, logContext, "pm command");看ActivityManagerService中的handleIncomingUser方法

@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);
    }

找到了,就是在这里调用的mUserController.handleIncomingUser方法,抛出的异常。这个userId就是我们调用doCreateSession方法传入的params.userId,我们继续看makeInstallParams的源码,params.userId是--user指定的。
我们在代码中执行Runtime.getRuntime().exec("pm install --user 0 apkpath"),log又抛出一个空指针异常。
调用链:doCreateSession--->mInstaller.createSession(params, installerPackageName, userId)-->createSessionInternal-->mAppOps.checkPackage(callingUid, installerPackageName);
checkPackageName会判断installerPackageName是不是为空,为空就抛出异常。installerPackageNameye也是调用doCreateSession时传入params.installerPackageName 该值也是pm命令指定的,为当前调用执行pm命令的包名
所以修改pm命令为:Runtime.getRuntime().exec("pm install -i 包名 --user 0 apkpath"),静默安装成功!!!
到这里就结束了,后面的源码没有贴出来,但是写出了调用过程,大家可以自己去追踪一下源码!

你可能感兴趣的:(Android7.0的静默安装失败问题研究)