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获取到了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值