这里要分两个部分来说,一个是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