• Android权限机制(一) 权限的申请与保存


    Android系统采用了sandboxes的安全机制,每个app有对应的PID,UID,资源,数据,以及基本的API。当app需要sandbox没有提供的额外API时,需要声明权限。

    在本文中,我们将会探究apk申请的权限信息是如何被保存到系统中的。

    一、声明权限

    1. 在AndroidManifest.xml中声明权限

    AndroidManifest.xml位于工程根目录下

    在<activity>标签之前声明权限

    • 声明了app所需要用到的权限
    • Android要求权限必须在manifest文件中明确声明,不允许在运行时动态声明

    2. “最少权限原则”

    • 如无需要,不要申请
    • 更少的权限,对于system和app都更加安全
      • 有些API可能会被恶意攻击
    • GooglePlay会根据来选择对应设备
      • 权限更少,面向的设备类型更多

    参考:AndroidDeveloper->SecurityTips->Using Permissions

    二、PackageManagerService的构造流程

    • SystemServer会调用PackageManagerService(PKMS)的main方法,构造出对象
    • PKMS会扫描/system/app, /system/priv-app, /data/app, …等目录下的apk
    • apk的AndroidManifest信息会动态保存在PKMS的属性成员中

    三、APK的安装流程

    • adbd是Deamon进程,监听host端的adb命令
    • adbd发送shell命令启动脚本pm (exec)
    • pm.jar的main方法被执行

    • 将apk拷贝到/data/app/下,开始解析AndroidManifest标签信息
    • 与PKMS构造器类似地,调用parsePackage()和scanPackageLI()处理

    四、两个数据结构:PackageParser.Package和Settings.mUserIds

    1. PackageParser.Package

    PKMS中mPackages对应的类型是:

    // Keys are String (package name), values are Package.  This also serves
    // as the lock for the global state.  Methods that must be called with
    // this lock held have the prefix "LP".
    final HashMap<String, PackageParser.Package> mPackages =
            new HashMap<String, PackageParser.Package>();

    部分属性成员和方法如下:

    从上面两个流程,我们可以看到在PackageManagerService(PKMS)启动(即SystemServer启动)与安装apk时,PKMS都会调用PackageParser::parsePackage()和scanPackageLI()方法。

    那么下面我们就来分析一下这两个方法的作用:

    /frameworks/base/core/java/android/content/pm/PackageParser.java

    PackageParser.Package.parsePackage():

    private Package parsePackage(
        Resources res, XmlResourceParser parser, int flags, String[] outError)
        throws XmlPullParserException, IOException {
    
        String pkgName = parsePackageName(parser, attrs, flags, outError);
    
        final Package pkg = new Package(pkgName);
        // ...
        int outerDepth = parser.getDepth();
        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
                continue;
            }
    
            String tagName = parser.getName();
            if (tagName.equals("application")) {
                // ...
                }
            } /* ... */ else if (tagName.equals("uses-permission")) {
                if (!parseUsesPermission(pkg, res, parser, attrs, outError)) {
                    return null;
                }
            }
        }
        // ...
    
        return pkg;
    }

    通过AndroidManifest.xml来获得package名字,然后解析xml文件的tag,并调用相应的方法。 如"uses-permission"标签对应调用parseUsesPermission()方法。

    /frameworks/base/core/java/android/content/pm/PackageParser.java

    PackageParser.parseUsesPermission():

    private boolean parseUsesPermission(Package pkg, Resources res, XmlResourceParser parser,
                                        AttributeSet attrs, String[] outError)
            throws XmlPullParserException, IOException {
    
        // ...
    
        if ((maxSdkVersion == 0) || (maxSdkVersion >= Build.VERSION.RESOURCES_SDK_INT)) {
            if (name != null) {
                int index = pkg.requestedPermissions.indexOf(name);
                if (index == -1) {
                    pkg.requestedPermissions.add(name.intern());
                    pkg.requestedPermissionsRequired.add(required ? Boolean.TRUE : Boolean.FALSE);
                } else {
                    if (pkg.requestedPermissionsRequired.get(index) != required) {
                        outError[0] = "conflicting <uses-permission> entries";
                        mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
                        return false;
                    }
                }
            }
        }
    
        XmlUtils.skipCurrentTag(parser);
        return true;
    }

    parseUsesPermission()判断permission是否已经保存,如果没有则add到pkg的requestedPermissions(ArrayList)中。

    下面我们继续看scanPackageLI()方法:

    /frameworks/base/services/java/com/android/server/pm/PackageManagerService.java

    PackageManagerService.scanPackageLI(PackageParser.Package, ...)

    private PackageParser.Package scanPackageLI(PackageParser.Package pkg,
            int parseFlags, int scanMode, long currentTime, UserHandle user) {
    
        // 判断apk是否已经安装过,跳过重复安装
        // 检查非system app的库是否被映射到正确的路径上
        // 检查是否需要重命名原来的Package Name
        // 为新的apk创建PackageSetting
        // 验证apk的signature
        // 验证apk的ContentProvider是否与已存在的apk的有冲突
        // 把apk的库解压复制到对应目录中
    
        // writer
        synchronized (mPackages) {
            // We don't expect installation to fail beyond this point,
            if ((scanMode&SCAN_MONITOR) != 0) {
                mAppDirs.put(pkg.mPath, pkg);
            }
            // Add the new setting to mSettings
            mSettings.insertPackageSettingLPw(pkgSetting, pkg);
            // Add the new setting to mPackages
            mPackages.put(pkg.applicationInfo.packageName, pkg);
            // Make sure we don't accidentally delete its data.
            final Iterator<PackageCleanItem> iter = mSettings.mPackagesToBeCleaned.iterator();
            while (iter.hasNext()) {
                PackageCleanItem item = iter.next();
                if (pkgName.equals(item.packageName)) {
                    iter.remove();
                }
            }
    
            // Just create the setting, don't add it yet. For already existing packages
            // the PkgSetting exists already and doesn't have to be created.
            // 安装新apk时,会最终调用到newUserIdLPw()
            pkgSetting = mSettings.getPackageLPw(pkg, origPackage, realName, suid, destCodeFile,
                    destResourceFile, pkg.applicationInfo.nativeLibraryDir,
                    pkg.applicationInfo.flags, user, false);
    
            // ...
        }
    
        // ...
    }

    2. Settings

    在Android中有多个名为Settings的类,此Settings为com.android.server.pm.Settings。

    其中,mUserIds属性保存着uid的重要信息。

    2.1. addUserIdLPw()

    从PKMS构造的时序图可以看出,在PKMS进行scanDirLI之前,会先后调用Settings的addSharedUserLPw()和readLPw()。

    public PackageManagerService(Context context, Installer installer,
            boolean factoryTest, boolean onlyCore) {
        // ...
    
        mSettings = new Settings(context);
        mSettings.addSharedUserLPw("android.uid.system",
                Process.SYSTEM_UID, ApplicationInfo.FLAG_SYSTEM);
        mSettings.addSharedUserLPw("android.uid.phone", RADIO_UID, ApplicationInfo.FLAG_SYSTEM);
        mSettings.addSharedUserLPw("android.uid.log", LOG_UID, ApplicationInfo.FLAG_SYSTEM);
        mSettings.addSharedUserLPw("android.uid.nfc", NFC_UID, ApplicationInfo.FLAG_SYSTEM);
        mSettings.addSharedUserLPw("android.uid.bluetooth", BLUETOOTH_UID, ApplicationInfo.FLAG_SYSTEM);
        mSettings.addSharedUserLPw("android.uid.shell", SHELL_UID, ApplicationInfo.FLAG_SYSTEM);
    
        synchronized (mInstallLock) {
        // writer
        synchronized (mPackages) {
            // ...
    
            sUserManager = new UserManagerService(context, this,
                    mInstallLock, mPackages);
    
            readPermissions();
    
            mFoundPolicyFile = SELinuxMMAC.readInstallPolicy();
    
            mRestoredSettings = mSettings.readLPw(this, sUserManager.getUsers(false),
                    mSdkVersion, mOnlyCore);

    addSharedUserLPw()方法的作用是如果某个app的AndroidManifest中声明android:sharedUserId="com.android.process",则它的uid与属性值中的进程uid一样。
    当然,前提是两者的apk签名(signature)相同。

    这样,该进程就能获得uid的权限。

    而readLPw()则是创建新的uid。无论是调用addSharedUserLPw()还是readLPw(),最终都会调用到addUserIdLPw()。

    private boolean addUserIdLPw(int uid, Object obj, Object name) {
        if (uid > Process.LAST_APPLICATION_UID) {
            return false;
        }
    
        if (uid >= Process.FIRST_APPLICATION_UID) {
            int N = mUserIds.size();
            final int index = uid - Process.FIRST_APPLICATION_UID;
            while (index >= N) {
                mUserIds.add(null);
                N++;
            }
            if (mUserIds.get(index) != null) {
                PackageManagerService.reportSettingsProblem(Log.ERROR,
                        "Adding duplicate user id: " + uid
                        + " name=" + name);
                return false;
            }
            mUserIds.set(index, obj);
        } else {
            if (mOtherUserIds.get(uid) != null) {
                PackageManagerService.reportSettingsProblem(Log.ERROR,
                        "Adding duplicate shared id: " + uid
                        + " name=" + name);
                return false;
            }
            mOtherUserIds.put(uid, obj);
        }

    mUserIds是一个ArrayList,以uid减去FIRST_APPLICATION_UID的值为下标索引,而对应的obj是Object类型,实际上是PackageSetting
    (PackageSetting继承于PackageSettingBase,
    而PackageSettingBase继承于GrantedPermissions。
    GrantedPermissions中有一个类型为HashSet的grantedPermissions,存放着权限的String)

    2.2. newUserIdLPw()

    上面的addUserIdLPw,是在PKMS启动时调用的。而安装新apk时,调用的是newUserIdLPw。

    private PackageParser.Package scanPackageLI(PackageParser.Package pkg,
            int parseFlags, int scanMode, long currentTime, UserHandle user) {
    
        // writer
        synchronized (mPackages) {
    
            // Just create the setting, don't add it yet. For already existing packages
            // the PkgSetting exists already and doesn't have to be created.
            // 为新的APK创建PackageSetting
            pkgSetting = mSettings.getPackageLPw(pkg, origPackage, realName, suid, destCodeFile,
                    destResourceFile, pkg.applicationInfo.nativeLibraryDir,
                    pkg.applicationInfo.flags, user, false);
            if (pkgSetting == null) {
                Slog.w(TAG, "Creating application package " + pkg.packageName + " failed");
                mLastScanError = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
                return null;
            }
        }            
    }

    最终会调用到newUserIdLPw():

    private int newUserIdLPw(Object obj) {
        // Let's be stupidly inefficient for now...
        final int N = mUserIds.size();
        for (int i = 0; i < N; i++) {
            if (mUserIds.get(i) == null) {
                mUserIds.set(i, obj);
                return Process.FIRST_APPLICATION_UID + i;
            }
        }
    
        // None left?
        if (N > (Process.LAST_APPLICATION_UID-Process.FIRST_APPLICATION_UID)) {
            return -1;
        }
    
        mUserIds.add(obj);
        return Process.FIRST_APPLICATION_UID + N;
    } 

    如果新安装的apk具有android:sharedUserId的话,scanPackageLI()中将会调用getSharedUserLPw(),最终还是会调用到newUserIdLPw(),这一段我们就不再详细探究。

    通过以上的分析,我们可以得出以下结论:

    • SystemServer启动时与安装apk时都会扫描apk
    • 扫描后得到的标签信息保存到PKMS.mPackages和Settings.mUserIds中,而不是文件中
    • 标签信息存放到两个不同的数据结构,应该是有不同的应用场合

    五、通过PackageManager获得PackageInfo

    保存了apk的信息之后,我们就可以通过PackageManager来间接获得PKMS中的这些信息。 PackageManager.getPackageInfo()是Android API level 1的公开接口:

    public abstract PackageInfo getPackageInfo(String packageName, int flags)

    通过AIDL,PKMS的getPackageInfo()会被调用到:

    /frameworks/base/services/java/com/android/server/pm/PackageManagerService.java

    PackageManagerService.getPackageInfo()

    @Override
    public PackageInfo getPackageInfo(String packageName, int flags, int userId) {
        if (!sUserManager.exists(userId)) return null;
        enforceCrossUserPermission(Binder.getCallingUid(), userId, false, "get package info");
        // reader
        synchronized (mPackages) {
            PackageParser.Package p = mPackages.get(packageName);
            if (DEBUG_PACKAGE_INFO)
                Log.v(TAG, "getPackageInfo " + packageName + ": " + p);
            if (p != null) {
                return generatePackageInfo(p, flags, userId);
            }
            if((flags & PackageManager.GET_UNINSTALLED_PACKAGES) != 0) {
                return generatePackageInfoFromSettingsLPw(packageName, flags, userId);
            }
        }
        return null;
    }

    这样,apk的<uses-permission>信息也就能够被第三方app获取到了。

  • 相关阅读:
    Oracle中的substr()函数和INSTR()函数和mysql中substring_index函数字符截取函数用法:计算BOM系数用量拼接字符串*计算值方法
    (转载)SDRAM驱动笔记
    【转】Verilog阻塞与非阻塞赋值使用要点
    【转转】(筆記) always block內省略else所代表的電路 (SOC) (Verilog)
    (原創) 如何處理signed integer的加法運算與overflow? (SOC) (Verilog)
    [转载]亚稳态
    Dev Exprss 发布部署
    Dev splliter 去除中间的分割显示
    DevTreeList中的新增、修改的设计
    Oracle 常用网址
  • 原文地址:https://www.cnblogs.com/jacobchen/p/3804994.html
Copyright © 2020-2023  润新知