Android文件访问权限的管理机制以及SDCardFS

Android文件访问权限的管理机制以及SDCardFS

1. 原生Linux文件访问权限控制

原生的Linux操作系统是通过拥有者 ID(uid) 和群组 ID(gid)对文件的访问权限进行管理,如:

user0@user0:testLinuxPermission$ ls -l
  访问权限     uid    gid                    文件名
-rw-rw---- 1 root   root   0 Apr 24 22:12 123.txt
-rw-r--r-- 1 user0  user0  0 Apr 24 22:11 abc.txt

123.txt是属于root用户,且属于root群组,且文件的权限设置为-rw-rw----,即660权限,允许文件拥有者,与文件拥有者同一组的用户(具有相同gid)进行访问。由于user0并不是123.txt的拥有者,也不属于同一组,因此user0无法访问123.txt文件。

文件的访问权限可以在终端可以通过chmod命令进行修改,文件拥有者通过chown命令对进行修改,将某个用户加入某个组,可以通过useradd命令。

内核层面上,文件的访问权限由inode->mode指定,文件拥有者,以及所属群组分别通过inode->uidinode->gid进行指定。

2. Android文件访问权限控制

2.1 内部存储以及外部存储

Android的存储区域分为内部存储外部存储。由于现在一般手机已经不使用SD-Card、TF-Card等扩展存储器,因此内部存储外部存储大部分情况下是一个逻辑上划分区域的概念。内部存储一般指的是/data/目录,而外部存储指的是/sdcard/目录。

/data/目录: 主要包含两个子目录app目录和data目录,app目录主要用于存放系统APP,以及用户APP的可执行文件,例如.apk,.dex,.so文件等。data目录主要存放APP的私有数据,例如用户的信息,缓存文件等。随着APP的删除,app目录和data目录里面的文件也会随之删除。

/sdcard/目录: 该目录作用很多。它既包含多个可以共用子目录,例如Download目录、Music目录、Pictures目录等,也包含一些用于存放用户APP的体积较大的私有数据的目录(视频、图片等)。

内部存储以及外部存储的私有目录是APP默认就可以获取权限进行访问,但是外部存储的公有目录需要APP安装或者启动的时候进行权限请求。例如,APP会在AndroidManifest.xml文件进行如下权限声明:

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

然后系统在安装的过程或者启动的过程中,授予APP相应的外部存储公有目录的访问权限。

2.2 文件访问权限控制

私有数据和公有数据

从上一节可以知道,Android会将数据分为私有数据和公有数据,例如APP1的私有数据,不能被APP2去访问。那么是Android是如何实现私有数据和公有数据的控制呢?

Android沿用了第一节介绍的Linux对文件权限管理模式,通过uid和gid进行管理,分为如下步骤:

  • Android给每一个APP分配一个独特的APP ID,然后将这个APP ID作为文件的uid(inode->uid),写入到文件系统中。

  • 下一步设置文件的gid(inode->gid)

    • 对于内部存储的文件,将文件的gid(inode->gid)设置为APP ID
    • 对于外部存储的文件,将文件的gid(inode->gid)设置为sdcard_rw,它们的差别后面继续讨论。
  • 最后给每一个文件设置相应的权限(inode->mode),

    • 对于内部存储的文件,设置为rwx------(700),那么表示只允许APP本身(具有相同APP ID)进行访问
    • 对于外部存储的文件,设置为rwxrwx---(770),那么表示除了允许APP本身(具有相同APP ID)进行访问以外,也允许属于同一组的APP(具有相同gid)进行访问。因此,外部存储可以同时实现公有文件和私有文件的访问控制。一般情况下,外部存储的文件的gid设置为sdcard_rw

读写权限控制

  1. 对于内部存储文件,目的是不让APP1访问APP2的私有文件,这个目的基于Linux的访问权限机制就可以实现(uid + 700权限)。

  2. 对于外部存储文件,可能会有一些问题:

从上面的描述,Android可以通过uidgid的组合,控制每一个APP的文件访问权限。Android在此基础上更进一步,要求独立控制每一个APP对于外部存储的权限。例如外部存储一个典型场景:

Android可能要求外部存储对于APP1是只读的,但是对于APP2是读写均可。
即判断APP是否设置了READ_EXTERNAL_STORAGE以及WRITE_EXTERNAL_STORAGE,然后授予该APP对外部存储的相应的权限。

这个情况基于传统的Linux访问权限机制(uidgid)是无法实现。因为外部存储内的每一个文件只有一个inode->gid,它可以被分配去只读rwxr-x---,或者读写rwxrwx---,但是不能单独给每一个APP设置为只读和读写两种状态。因此,Android使用了SDCardFS去处理单个文件对于不同的APP访问权限的问题。

2.3 SDCardFS的原理以及作用

从2.1节知道,Android从逻辑上分为内部存储和外部存储,且外部存储目录的名字为/sdcard/。因此为了解决外部存储目录的权限访问控制问题,设计了SDCardFS文件系统,作为/sdcard/目录的文件系统,管理它的权限。SDCardFS跟SD-Card、TF-Card那些没什么关系,是一个历史遗留命名方式。

2.3.1 SDCardFS设计

从2.1节知道,为了实现APP访问隔离,Android从逻辑上分为内部存储和外部存储,但是物理上都是属于同一段物理空间,同一个ext4文件系统。因此,SDCardFS是一个包装文件系统,它给将其中一段ext4文件系统空间包装了起来,并在包装层实现了访问控制。

2.3.2 如何解决单个文件对不同APP具有不同的访问权限的问题

Android可以采用每一个APP动态挂载SDCardFS方式去解决这个问题,每一个APP的在启动的时候,跟据该APP在AndroidManifest.xml申请的访问权限,通过挂载参数选择将文件系统挂载成为只读、读写类型(仍然是那一段物理空间,只是挂载为不同访问类型)。

但是这个方案有一个明显的缺点: 显然如果每一个APP都需要动态挂载一个文件系统,会耗费大量内存,因为每一个挂载的文件系统都有各自独立的大量的dentryinode等内存数据结构。

因此Android使用了一个改进方案: 为了减少内存使用,Android使用了bind remount的方式进行多次挂载,bind remount有几个特性:

  • 将已经挂载好的SDCardFS,重新挂载到一个新目录
  • 新挂载的SDCardFS与原始的SDCardFS共用一个inode,因此可以减少内存使用
  • 由于共用一个inode,因此inode->gidinode->uidinode->mode还是一样的,访问模式也是一样的
2.3.3 如何解决inode->gidinode->uidinode->mode一样的问题

然而,inode->gidinode->uidinode->mode如果跟原始的SDCardFS一样的话,无法解决APP单独控制读写权限的问题。因此SDCardFS作为包装文件系统最重要的功能出现了:

通过动态bind remount的挂载选项,通过对inode的包装动态设定inode的uid,gid,mode值

实现方案的思路: 既然inode->gid只有一个,那么就让inode->mode可以动态变化。

内核实现:

  • 每一次调用open函数,都会对inode进行访问鉴权,一般是通过generic_permission函数进行。
  • SDCardFS在执行generic_permission函数之前,先创建一个临时的inode,然后给这个临时inode赋予特定inode->uidinode->gidinode->mode。其中inode->mode的值与SDCardFS挂载选项opt->mask值相关,关系为inode->mode = 0775 & ~opt->mask
  • 使用这个临时inode代替原来的inode进行鉴权。

那么会出现什么情形呢?假设SDCardFS0被bind remount了2次,分别是SDCardFS1SDCardFS2,其中挂载参数opt->mask分别是23和7,则:

  • SDCardFS1, mask=23: 挂载参数opt->mask的值是23,那么临时inode->mode =750,即只读权限,任何通过SDCardFS1访问的inode都会被临时inode处理为只读权限。

  • SDCardFS2, mask=7: 挂载参数opt->mask的值是7,那么临时inode->mode =770,即读写权限,任何通过SDCardFS2访问的inode都会被临时inode处理为读写权限。

因此,对于一个原始SDCardFS修改挂载参数进行bind remount,可以在节省内存的同时,让同一个目录具有多种访问权限。

实际例子:

/sdcard/目录实际上是一个链接,链接到/storage/emulated/0目录,这个目录通过各种挂载关系之后,链接到/data/media目录,同时/data/media目录也被挂载到/mnt/runtime/default/emulated。因此/sdcard//mnt/runtime/default/emulated的数据是关联的,接下来主要讨论/mnt/runtime/default/emulated目录。

a) 系统层面

Android最原始的SDCardFS是在系统启动的过程中挂载的,目录是/mnt/runtime/default/emulated,如下:

/data/media on /mnt/runtime/default/emulated type sdcardfs (rw,nosuid,nodev,noexec,noatime,fsuid=1023,fsgid=1023,gid=1015,multiuser,mask=6,...)

然后系统通过bind remount的方式挂载了/mnt/runtime/read/emulated目录以及/data/media on /mnt/runtime/write/emulated目录,使其拥有了不同的访问权限,如下:

/data/media on /mnt/runtime/read/emulated type sdcardfs (rw,nosuid,nodev,noexec,noatime,fsuid=1023,fsgid=1023,gid=9997,multiuser,mask=23,...)
/data/media on /mnt/runtime/write/emulated type sdcardfs (rw,nosuid,nodev,noexec,noatime,fsuid=1023,fsgid=1023,gid=9997,multiuser,mask=7,...)

可以看到挂载参数gidmask不同,他们有什么不同呢?

  • mask的不同: 前面内核实现这一节提到inode->mode = 0775 & ~opt->mask,因此mask的值影响到了临时inode的访问权限。通过简单计算可以得到mask=7 --> 771mask=23 --> 750mask=7 --> 770,即/mnt/runtime/read/emulated读写权限/mnt/runtime/read/emulated只读权限/mnt/runtime/write/emulated读写权限
  • gid的不同: gid=1015其实就是之前提及到的sdcard_rw 组的id,不是该组的应用无法访问里面的数据。gid=9997就是everybody组的id,一般APP都属于这一组。因此,只要APP挂载了/mnt/runtime/read/emulated目录或者/mnt/runtime/write/emulated,就可以访问到里面的文件。

综上所述,对于同一个/sdcard/目录,使用了三种挂载的方式,挂载到三个目录中,并基于bind remount,以及挂载选项+临时inode的方法,实现了三个挂载目录具有不同的访问权限。

b) APP层面

现在回到最开始的问题,讨论APP是如何将其实现的:

Android可能要求外部存储对于APP1是只读的,但是对于APP2是读写均可。

由于APP1只申请了只读的权限,它在启动时,挂载/mnt/runtime/read/emulated目录,因此APP1只能只读地访问/sdcard/。(前面提及/mnt/runtime/read/emulated/sdcard/是对应的)

由于APP2申请了读写的权限,它在启动时,挂载/mnt/runtime/write/emulated目录,因此APP2可以读写模式访问/sdcard/

因此就实现了,对于同一个文件,对于APP1是只读的,对于APP2是读写的。

如果一个APP不申请任何权限,那么它会挂载/mnt/runtime/default/emulated目录,同时由于该APP不属于sdcard_rw组,因此对/sdcard/文件没有读写权限。

  • 4
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值