抽丝剥茧:理解Android权限机制
前一段时间面试官问我Android在Linux的基础上,权限做了哪些改变。霹雳呱啦说了一堆,但是说着说着,始终感觉自己说的缺了点东西,自己理解还是不够到位
,而且网上的很多文章在原理上基本都是大同小异,很多地方都是语焉不详,所以,自己半看源码半看文章的总结了一下。
一:Android权限是什么
这个是老生长谈的东西了,说到底,权限就是告诉系统我需要干什么,从访问物理数据,到访问第三方组件均是。
二:权限赋予
权限的赋予可分为两类,一类是高层的组件,例如应用和系统服务,这一部分一般采用包管理器依赖进行管理,查询。而另一部分则是低层的组件,这一部分则是利用了传统了Linux DAC机制进行管理,一般不直接访问包管理器。
1:低层权限管理
这里包括但不限于设备文件,UNIX套接字,网络套接字。Android进程主要通过UID,GID以及一组补充的GID实现的。众所周知,Android沙箱是以UID为基础实现的,每个进程拥有自己独特的UID(先不考虑共享UID)。进程的UID和GID会由包管理器映射到应用程序的UID。而补充gid则为额外的权限。值得一提的是,内置权限到组的映射是静态的。部分源码如下
<permission name="android.permission.BLUETOOTH_ADMIN" > <group gid="net_bt_admin" /> </permission> <permission name="android.permission.BLUETOOTH" > <group gid="net_bt" /> </permission> <permission name="android.permission.BLUETOOTH_STACK" > <group gid="bluetooth" /> <group gid="wakelock" /> </permission>
如上,android.permission.BLUETOOTH_ADMIN,android.permission.BLUETOOTH 和GID net_bt_admin组是关联的.而在 android_filesystem_config.h中,组和GID是想映射的,如下
static struct android_id_info android_ids[] = {
....
{ "shell", AID_SHELL, },
{ "cache", AID_CACHE, },
{ "net_bt_admin", AID_NET_BT_ADMIN, },
....
}
#define AID_NET_BT_ADMIN 3001 /* bluetooth: create any socket */ #define AID_NET_BT 3002 /* bluetooth: create sco, rfcomm or l2cap sockets */ #define AID_INET 3003 /* can create AF_INET and AF_INET6 sockets */ #define AID_NET_RAW 3004 /* can create raw INET sockets */ #define AID_NET_ADMIN 3005 /* can configure interfaces and routing tables. */ #define AID_NET_BW_STATS 3006 /* read bandwidth statistics */
可得android.permission.BLUETOOTH_ADMIN映射的GID的3001。总而言之,包管理器在读取platfrom.xml时,并维护一个权限到GID的列表。在对一个安装中的包进行授权时,包管理器会检查每个权限是否有对应的GID。如果有,则加入在补充GID列表。当然,到这里只是确定了进程需要赋予哪些额外的gid。
并没有说怎么赋权的,这里要谈到一个叫zygote的进程,顾名思义,当Android启动新进程的时候,为了减少程序所需内存以及加快启动时间,Android会直接fork()zygote进程,并执行Android特有的函数进行分化而不执行固有的exec函数。简化代码如下(android / platform / dalvik / 7033bed / . / vm / native / dalvik_system_Zygote.cpp forkAndSpecializeCommon()):
pid = fork(); if(pid ==0 ){ err = setgroupsIntarray(gids); //设置补充gid err = setrlimitsFromArray(rlimits); //设置资源限制 err = setresgid(gid, gid, gid); //设置实际用户/组id err = setresuid(uid, uid, uid); //设置有效用户/组id err = setCapabilities(permittedCapabilities, effectiveCapabilities); //设置进程权能 err = set_sched_policy(0, SP_DEFAULT); //设置调度策略 err = setSELinuxContext(uid, isSystemServer, seInfo, niceName); //SElinux }
2.高层权限管理
在仔细讲解之前,先来看看包管理器所维护的安装程序包核心数据库,这个数据库以xml文件的形式放进了/data/system/packages.xml里,先来看看里面究竟有些什么。
<package name="com.android.protips" codePath="/system/app/Protips" nativeLibraryPath="/system/app/Protips/lib" flags="572997" ft="1560a280490" it="1560a280490" ut="1560a280490" version="1" userId="10040"> <sigs count="1"> <cert index="0" /> </sigs> <proper-signing-keyset identifier="2" /> <signing-keyset identifier="2" /> </package> <package name="com.android.launcher" codePath="/system/priv-app/Launcher2" nativeLibraryPath="/system/priv-app/Launcher2/lib" flags="1078509125" ft="1560a29ae58" it="1560a29ae58" ut="1560a29ae58" version="22" userId="10007"> <sigs count="1"> <cert index="3" key="308204a1406035504071302fc58d017971bd0f6b52c262d70819d191967e158dfd3a2c7f1b30fa1eaafc2a556f84" /> </sigs> <proper-signing-keyset identifier="3" /> <signing-keyset identifier="3" /> </package>
<package name="com.android.widgetpreview" codePath="/data/app/WidgetPreview" nativeLibraryPath="/data/app/WidgetPreview/lib" flags="572996" ft="15bbc858e28" it="1560a27f8d8" ut="1560a27f8d8" version="22" userId="10052">
<sigs count="1">
<cert index="0" />
</sigs>
<perms>
<item name="android.permission.READ_EXTERNAL_STORAGE" />
<item name="android.permission.WRITE_EXTERNAL_STORAGE" />
</perms>
<proper-signing-keyset identifier="2" />
<signing-keyset identifier="2" />
</package>
从上面可以发现,这里包括了安装路径,版本号,签名证书,每个包的权限。上层的管理都是通过和包管理器和这个数据库进行交互的。由于组件不能在运行时改变权限,所以权限执行检查都是静态的。但它的执行一般分为两类,一类是静态,另一类是动态的。静态执行和动态执行流程大致相同:Binder.getCallingUid()和Binder.getCallingPid()获取调用者的UID和PID,然后利用UID映射包名,再获得相关权限。如果权限集合中含有所需权限即启动,否则抛出SecurityException异常。
三:共享UID(补充)
使用相同的密匙签发的Android应用可以使用共同的UID运行 ,并且可以运行在同一进程中。这个属性可以简单通过在AndroidManifest.xml的根元素中添加shareUserId属性即可开启,但是不能在已安装的应用里添加该属性,这只会导致它修改自身uid,以至于失去对自身文件的访问权限。
shareid内置了以下几种:
android.uid.system(SYSTEM_UID,1000)
android.uid.phone(PHONE_UID,1001)
android.uid.bluetooth(BLUETOOH_UID,1002)
android.uid.log(LOG_UID,1007)
android.uid.nfc(NFC_UID,1027)
它们在系统引导时自动添加。具有相同userid的进程,可以访问相同的系统资源,还可以对统一资源的组件进行特殊访问控制。