这里要分两个部分来说,一个是Linux权限组的设定,一个是SELinux。
我们先不考虑SELinux。先单独来说Linux权限组。
因为要想对某个文件进行操作(read,write,execute等),必须先满足Linux权限组的规则,然后再满足SeLinux的allow条件,才能成功操作。
我们最好找一台userdebug软件的手机来进行调试学习。我这边是在Android O上进行示范。
Linux权限组:
我们知道每开一个进程,都对应一个虚拟机实例。它有一个对应uid。
adb shell
EDA51-1:/ # ps -A |grep com
system 26267 899 4378628 82036 SyS_epoll_wait 6ffcd7b420 S com.android.settings
u0_a30 1669 899 4418756 118956 SyS_epoll_wait 6ffcd7b420 S com.android.systemui
system 26333 899 4338916 63952 SyS_epoll_wait 6ffcd7b420 S com.potter.honeysigapk
nfc 2456 899 4345896 56824 SyS_epoll_wait 6ffcd7b420 S com.android.nfc
radio 3856 899 4317736 37988 SyS_epoll_wait 6ffcd7b420 S com.qualcomm.simcontacts
这里的system,u0_a30,nfc等等就是uid。
首先,这些uid是怎么来的,在哪定义的?
system/core/include/private/android_filesystem_config.h
#define AID_ROOT 0 /* traditional unix root user */
#define AID_SYSTEM 1000 /* system server */
#define AID_RADIO 1001 /* telephony subsystem, RIL */
#define AID_BLUETOOTH 1002 /* bluetooth subsystem */
…
值的关注的有:
#define AID_ROOT 0
#define AID_SYSTEM 1000
#define AID_APP 10000 /* TODO: switch users over to AID_APP_START */
#define AID_APP_START 10000 /* first app user */
#define AID_APP_END 19999 /* last app user */
#define AID_USER 100000
这里只有大写的,你可以会问,应该是一一对应的,比如init,rc里面也有用到root,system等,要对应小写的才对呀?
上面那个.h文件底部有以下注释。在Android O上已经是自动生成的了。在早些版本是声明的。
/*
* android_ids has moved to pwd/grp functionality.
* If you need to add one, the structure is now
* auto-generated based on the AID_ constraints
* documented at the top of this header file.
* Also see build/tools/fs_config for more details.
*/
早些版本声明类似:
static const struct android_id_info android_ids[] = {
{ "root", AID_ROOT, },
{ "system", AID_SYSTEM, },
{ "radio", AID_RADIO, },
{ "bluetooth", AID_BLUETOOTH, },
….
如果你和我一样好奇,可能还会问
u0_a30 1669 899 4418756 118956 SyS_epoll_wait 6ffcd7b420 S com.android.systemui
这个u0_a30,还有其他的u0_axx都是怎么来的,不可能全部定义吧,这个规则在哪?
上一个问题里,我们知道其实android_ids是自动生成的。详细步骤参考:
app的uid/100000的结果为userid,填到ux的x处。
app的uid减去10000为appid,填到axx的xx处。
例如某个app的uid是10022,经过计算,userid为10022/100000=0,appid为10022-10000=22,则那么最终通过ps打印得到uid字串就是u0_a22
可能你还会问,Settings和SystemUI都是系统apk,为什么一个是system,一个是u0_axx?
这个其实很简单,Settings的AndroidManifest.xml里面声明了android:sharedUserId="android.uid.system"
我们知道sharedUserID一致,同时签名一致的两个apk可以共享数据。这里多说一句,其实
SystemUI和Keyguard在android高版本里面也是同一个uid。
frameworks/base/packages/Keyguard/AndroidManifest.xml:21: android:sharedUserId="android.uid.systemui"
frameworks/base/packages/SystemUI/AndroidManifest.xml:22: android:sharedUserId="android.uid.systemui"
这里我们对uid的定义,来由有了一些了解。下面继续看为什么需要uid,它有什么用。
下面的ipsm分区是我司自己定制的,大家可以忽略。
EDA51-1:/ # ls -l
drwxrwx--x 47 system system 4096 1970-01-07 12:06 data
drwxr-xr-x 14 root root 3800 2018-11-08 15:11 dev
drwxrwx--x 4 system system 4096 1970-01-07 12:05 ipsm
lrw-r--r-- 1 root root 21 2018-11-08 17:12 sdcard -> /storage/self/primary
drwxr-xr-x 5 root root 100 2018-11-08 15:06 storage
dr-xr-xr-x 13 root root 0 1970-01-07 12:12 sys
drwxr-xr-x 16 root root 4096 2018-11-08 17:12 system
drwxr-xr-x 17 root root 4096 1970-01-01 08:00 vendor
‘
第一列 共有10位,从左到右边意思如下
1 位 表示文件的类型
2~4位 表示文件所有者的权限就是系统对该文件所拥有的权限
5~7位 表示文件所在群组的权限
8~10 表示文件的其它用户权限
第二列 纯数字 表示文件链接个数
第三列 表示文件所有者
第四列 表示文件所在的群组
第五列 表示文件的大小
第六列 表示文件最后更新的时间
第七列 表示文件的名称
b就是block
d就是directory
l就是link
举个例子,我们知道某个apk的SharedPreference数据是放在/data/data/com.xxx.xxx/files/里的
EDA51-1:/data/data # ls -l
drwx------ 4 system system 4096 1970-01-07 12:06 android
drwxr-x--x 4 u0_a78 u0_a78 4096 2018-11-08 15:06 com.action.ext.servicie
drwx------ 4 u0_a29 u0_a29 4096 2018-11-08 15:06 com.android.apps.tag
drwxr-x--x 4 system system 4096 2018-11-08 15:06 com.android.backup
drwx------ 4 u0_a49 u0_a49 4096 2018-11-08 15:06 com.android.egg
drwx------ 7 u0_a50 u0_a50 4096 2018-11-08 15:07 com.android.email
drwx------ 4 u0_a13 u0_a13 4096 2018-11-08 15:06 com.android.emergency
可以看到/data/data/com.xxx.xxx的目录,对应的apk是具有读写权限的,而其他组则没有。
我们以文件管理器再来举例子:
EDA51-1:/data/data # ps -A|grep file
u0_a38 26659 899 4364576 100916 SyS_epoll_wait 6ffcd7b420 S com.cyanogenmod.filemanager
从我们的经验来看,文件管理是可以操作外部存储的(我们插入的sdcard以及storage/emulated/0/)
我们先去storage/emlulated/0目录下去ls -l看一下
EDA51-1:/storage/emulated # ls -l
drwxrwx--x 16 root sdcard_rw 4096 2018-11-09 07:47 0
EDA51-1:/storage/emulated/0 # ls -l
drwxrwx--x 2 root sdcard_rw 4096 2018-11-08 15:07 Alarms
drwxrwx--x 3 root sdcard_rw 4096 2018-11-08 15:07 Android
drwxrwx--x 2 root sdcard_rw 4096 2018-11-08 15:07 DCIM
这里可以看到,root和sdcard_rw这两个id都可以写storage/emulated/0这个目录的,可是我们的文件管理器既不是root也不是sdcard_rw啊,而是u0_a38 ,怎么做到的?
这个问题的解答涉及到一个文件:frameworks/base/data/etc/platform.xml
它定义了声明哪些权限可以得到哪些对应组的操作权限。
<permission name="android.permission.WRITE_MEDIA_STORAGE" >
<group gid="media_rw" />
<group gid="sdcard_rw" />
</permission>
也就是说我的FileManager只需要在AndroidManifest.xml里面声明了android.permission.WRITE_MEDIA_STORAGE就ok了。
这里又衍生出一个问题?
为什么我们经常是用的是
<permission name="android.permission.READ_EXTERNAL_STORAGE" />
<permission name="android.permission.WRITE_EXTERNAL_STORAGE" />而不是android.permission.WRITE_MEDIA_STORAGE?
是否他们都在一个权限组呢?我们知道动态权限给了其中一个,整个组的权限都会给到
frameworks\base\core\res\AndroidManifest.xml
里面有
<!-- Allows an application to write to external storage.
{@link android.content.Context#getExternalFilesDir} and
{@link android.content.Context#getExternalCacheDir}.
<permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:permissionGroup="android.permission-group.STORAGE"
<permission android:name="android.permission.READ_EXTERNAL_STORAGE"
android:permissionGroup="android.permission-group.STORAGE"
<!-- @SystemApi Allows an application to write to internal media storage
@hide -->
<permission android:name="android.permission.WRITE_MEDIA_STORAGE"
android:protectionLevel="signature|privileged" />
可以看到,WRITE_EXTERNAL_STORAGE和READ_EXTERNAL_STORAGE在同一个权限组。而WRITE_MEDIA_STORAGE不在里面。那为什么给了WRITE_EXTERNAL_STORAGE权限,就可以读外部存储呢(虽然注释写了可以读外部存储,但是我需要确凿的证据)?
那应该有个地方声明:
<permission name="android.permission.WRITE_EXTERNAL_STORAGE" >
<group gid="sdcard_rw" />
</permission>
为什么在framework目录没找到呢,frameworks/base/data/etc/platform.xml里面也没有啊?
frameworks/base/data/etc/platform.xml里面是这个写的:
<!-- These are permissions that were mapped to gids but we need
to keep them here until an upgrade from L to the current
version is to be supported. These permissions are built-in
and in L were not stored in packages.xml as a result if they
are not defined here while parsing packages.xml we would
ignore these permissions being granted to apps and not
propagate the granted state. From N we are storing the
built-in permissions in packages.xml as the saved storage
is negligible (one tag with the permission) compared to
the fragility as one can remove a built-in permission which
no longer needs to be mapped to gids and break grant propagation. -->
<permission name="android.permission.READ_EXTERNAL_STORAGE" />
<permission name="android.permission.WRITE_EXTERNAL_STORAGE" />
好吧,这段我也没理解太懂,我去找了下packages.xml,它生成在/data/system/packages.xml,还有个/data/system/packages.list,两个都pull出来,也没找到sdcard_rw.
简单看no longer needs to be mapped to gids就可以了.这一块具体在哪我就没再深究了。
这一部分,意思就是说我们可以通过申请权限来加入到一些gid里面去。扩展我们apk的文件操作范围。
再多讲一个我这边遇到的问题
这个例子强调的对文件的操作(如把文件A拷贝到另一个目录),拷贝后的文件一定要关注它开放给其他组的权限。
客户有个需求,是动态替换开机动画。他们传入一个路径字符串。我们提供接口。
实现的方式是在BootAnimation.cpp扩展一个目录,默认是/system/media/bootanimation.zip嘛。
然后通过我们定义的api(我们的一个系统apk,sharedUserID是system的)把客户的zip文件通过代码拷贝到我们的目标路径。
当然,这中间涉及到我司的ipsm分区和一些Selinux对应的te文件修改,这些不细说。
只讲,代码拷贝过来的文件的权限问题,就是拷贝过来的文件,哪些组可以对他进行rwe.
我们尝试adb的方法(在userdebug上)
adb push bootanimation.zip /ipsm/media/honeywell
重启手机可以看到生效额。
但是先把bootanimation.zip放在sdcard,然后用代码拷贝过去却不行?
adb push后:
EDA51-1:/ipsm/media/honeywell # ls -l
-rw-rw-rw- 1 root root 7649510 2018-08-22 23:55 bootanimation.zip
代码拷贝后:
EDA51-1:/ipsm/media/honeywell # ls -l
-rw------- 1 system system 7649510 2018-11-08 14:09 bootanimation.zip
发现push的是root,而且其他用户是可以rw的。
而代码拷贝是system,其他用户是不能读写的。(其实我们回头来看这个是情理之中的,就好比第三方apk通过申请WRITE_EXTERNAL_STORAGE权限,然后在storage/emulated/0创建一个输入它自己的目录,里面有它下载歌曲啊,图片什么的,假设这是个音乐music,实际其它应用也是没法读写它的目录的嘛,要不岂不是乱套了)
EDA51-1:/ipsm/media/honeywell # chmod -R 777 bootanimation.zip后
EDA51-1:/ipsm/media/honeywell # ls -l
-rwxrwxrwx 1 system system 7649510 2018-11-08 14:09 bootanimation.zip
重启可以了。
那么代码里面如何修改权限呢?
代码里面通过Runtime去做肯定不行了,貌似android M以后的user版本就没有su了。
不是su的话,shell里面也是不行的,这种方式更别提代码里面了。
EDA51-1:/ipsm/media/honeywell $ chmod -R 777 bootanimation.zip
chmod: chmod 'bootanimation.zip' to 100777: Operation not permitted提示没有权限。
可以通过以下方式:
private void changeFolderPermission(File dirFile) throws IOException {
Set<PosixFilePermission> perms = new HashSet<PosixFilePermission>();
perms.add(PosixFilePermission.OWNER_READ);
perms.add(PosixFilePermission.OWNER_WRITE);
perms.add(PosixFilePermission.OWNER_EXECUTE);
perms.add(PosixFilePermission.GROUP_READ);
perms.add(PosixFilePermission.GROUP_WRITE);
perms.add(PosixFilePermission.GROUP_EXECUTE);
perms.add(PosixFilePermission.OTHERS_READ);
perms.add(PosixFilePermission.OTHERS_WRITE);
perms.add(PosixFilePermission.OTHERS_EXECUTE);
try {
Path path = Paths.get(dirFile.getAbsolutePath());
Files.setPosixFilePermissions(path, perms);
} catch (Exception e) {
//logger.log(Level.SEVERE, "Change folder " + dirFile.getAbsolutePath() + " permission failed.", e);
}
}
这个api是AndroidO才有的。那么Android N及以下怎么处理呢?
方式一:使用File的api
setReadable(boolean readable, boolean ownerOnly)
setWritable (boolean readable, boolean ownerOnly)
setExecutable (boolean readable, boolean ownerOnly)
方式二:去bootanimation.cpp里面加
我们可以在bootanimation.cpp里面加上system("chmod 777 /ipsm/media/honeywell/bootanimation.zip");当然,这样一开始也是不会生效的,因为会因为Selinux报avc。
avc: denied { execute } for name="sh" dev="mmcblk0p32" ino=995 scontext=u:r:bootanim:s0 tcontext=u:object_r:shell_exec:s0 tclass=file permissive=0
这个需要去bootanim.te里面去allow一下就可以了。
好吧,这个方式我自己后续慢慢不停的加allow,不停的编译。
allow bootanim shell_exec:file { read open execute execute_no_trans getattr };
allow bootanim toolbox_exec:file { getattr execute };
allow bootanim vendor_toolbox_exec:file { getattr execute };
好吧,到这里的时候,编译终于报错了,与neverallow冲突了,说明这条路不可行。
neverallow check failed at out/target/product/hon450/obj/ETC/nonplat_sepolicy.cil_intermediates/nonplat_sepolicy.cil:7156
(neverallow base_typeattr_179_27_0 vendor_toolbox_exec_27_0 (file (execute execute_no_trans entrypoint)))
<root>
allow at out/target/product/hon450/obj/ETC/nonplat_sepolicy.cil_intermediates/nonplat_sepolicy.cil:7847
(allow bootanim_27_0 vendor_toolbox_exec_27_0 (file (getattr execute)))
总结来说:
判断一个apk是否能对某个目录或者文件进行对应的操作?
在不考虑SELinux的情况下,需要考虑以下几点:
1.ps -A|grep com.xxx.xx 获取这个apk的uid
2.cd 到对应目录,ls -l,看这个目录或者文件的对应操作权限对那些uid开放。
3.可以通过修改sharedUserID,在AndroidManifest.xml增加对应权限加入到对应gid组里面去,获取对文件or目录的操作权限。
SELinux:
selinux这个大家一般都用的比较多了。网上资料也比较多。
我个人的心得是下面几点:
1.userdebug软件上可以通过adb shell 然后getenforce,setenforce(0或者1)去进行调试
Permissive说明是关闭(宽容)的。这种模式也会打印avc denied的log,但是实际没做限制。
Enforcing说明是打开的。即打印log,也实际生效,默认高版本的Android Selinux是打开的。
需要注意一点,Selinux的状态在重启后会重置。比如开机的时候从默认的Enforcing改为Permissive,重启后又变成Enforcing。这一点在调试重启阶段的问题会有点蛋疼。
2.因为修改SELinux对应的te文件可能会导致gms测试有fail项,一般来说我们修改
device\xxx\sepolicy\common\下的te文件,不要去动到system/sepolicy目录下的te文件的neverallow,就不会有什么问题。如果allow和neverallow冲突了,编译是会报错的。
3.SELinux有这么几个概念
Domain - 一个进程或一组进程的标签。也称为域类型,因为它只是指进程的类型(ps -A -Z查看)
Type - 一个对象(例如,文件、套接字)或一组对象的标签。(到具体目录ls -Z查看)
Class - 要访问的对象(例如,文件、套接字)的类型。
Permission - 要执行的操作(例如,读取、写入)。
例如avc: denied { execute } for name="sh" dev="mmcblk0p32" ino=995 scontext=u:r:bootanim:s0 tcontext=u:object_r:shell_exec:s0 tclass=file permissive=0
这里的scontext可以通过ps -A -Z去查看
1|EDA51-1:/ $ ps -A -Z|grep launcher
u:r:platform_app:s0:c512,c768 u0_a15 4198 1961 4431684 110332 SyS_epoll_wait 0 S com.android.launcher3
这里的tcontext可以通过ls -Z去查看
EDA51-1:/ $ ls -Z
u:object_r:device:s0 dev u:object_r:rootfs:s0 sdcard
u:object_r:adsprpcd_file:s0 dsp u:object_r:secure_data_file:s0 secure
u:object_r:rootfs:s0 etc u:object_r:storage_file:s0 storage