android5.0 sd卡访问权限简单分析
本文只是记录了早期sd卡访问权限的一些分析,以及当时sdcardfs的一些状态,部分细节未做深入分析。内置SD卡:是指我们用户文件系统一个目录,是呈现给用户可使用的一个空间,也称为内置SD卡,当然这里面有虚拟层面的意义。所以也叫emulated sdcard外置SD卡:就是我们平常见到的TF卡,可插拔,用于扩展手机ROM空间。sdcardfs是最初由三星开发的用于取代android的用...
本文只是记录了早期sd卡访问权限的一些分析,以及当时sdcardfs的一些状态,部分细节未做深入分析。
内置SD卡:是指我们用户文件系统一个目录,是呈现给用户可使用的一个空间,也称为内置SD卡,当然这里面有虚拟层面的意义。所以也叫emulated sdcard
外置SD卡:就是我们平常见到的TF卡,可插拔,用于扩展手机ROM空间。
sdcardfs是最初由三星开发的用于取代android的用户态fuse文件系统,来提高使用性能
先摆出两个问题
1. 问题
问题1:第三方APP是否可以随意读写内置sd卡
问题2:第三方APP是否可以随意读写外置sd卡
2. 测试方法
做一个简单app来测试以上两个问题,代码如下:
File file = new File("/mnt/shell/emulated/0", "abc.txt"); //用于在内置SD卡创建一个新文件adc.txt
//File file = new File("/storage/extSdCard", "abc.txt"); //用于在外置SD卡创建一个新文件abc.txt
try {
file.createNewFile();
} catch (IOException e) {
// TODO Auto-generated catch block
Log.d("test", e.getMessage()); //显示出错的log
e.printStackTrace();
}
测试结果分为两部分
内置SD卡:
03-01 05:51:22.837 D 9009 9009 test: open failed: EACCES (Permission denied)
外置SD卡:
03-01 05:51:42.277 D 9009 9009 test: open failed: EACCES (Permission denied)
非常遗憾,两者情况系统都提示错误,原因都是没有访问权限。
所以对于上面的问题,第三方APP并不可以随意的写内置SD卡,同时也不可以随意写外置SD卡。
3. 为什么不可以写内置sd卡?
内置sd卡的挂载点情况
/dev/block/bootdevice/by-name/userdata /data ext4 rw,
/data/media /mnt/shell/emulated sdcardfs rw,seclabel,nosuid,nodev,relatime,uid=1023,gid=1023,derive=legacy,reserved=20MB 0 0
查看挂载点的文件权限情况。可以看到所有文件所属用户都是root,所属组都是sdcard_r,拥有权限都是rwx
root@a7ltectc:/mnt/shell/emulated/0 # ls -l
drwxrwx--- root sdcard_r 2015-01-01 08:06 Alarms
drwxrwx--x root sdcard_r 2015-01-01 08:06 Android
drwxrwx--- root sdcard_r 2015-02-11 10:00 DCIM
drwxrwx--- root sdcard_r 2015-01-01 08:06 Download
drwxrwx--- root sdcard_r 2015-01-01 08:06 Movies
drwxrwx--- root sdcard_r 2015-01-01 08:06 Music
drwxrwx--- root sdcard_r 2015-01-01 08:06 Notifications
drwxrwx--- root sdcard_r 2015-01-01 08:06 Pictures
drwxrwx--- root sdcard_r 2015-01-01 08:06 Playlists
drwxrwx--- root sdcard_r 2015-01-01 08:06 Podcasts
显然能不能读写取决于我们的APP属不属于sdcard_r用户组。根据头文件 /android/system/core/include/private/android_filesystem_config.h可以查询
#define AID_SDCARD_R 1028 /* external storage read access */
APP是否有sdcard_r用户组?
u0_a207 9009 321 1602296 72120 ffffffff b6fb3994 S com.example.myfiletest //所属用户号码是10207(u0_a207代表10207),进程pid 9009
然后查看用户组,命令cat /proc/pid/status
root@a7ltectc:/mnt/shell/emulated/0 # cat /proc/9009/status
Name: mple.myfiletest
State: S (sleeping)
Tgid: 9009
Pid: 9009
PPid: 321
TracerPid: 0
Uid: 10207 10207 10207 10207 用户ID
Gid: 10207 10207 10207 10207 组ID
FDSize: 256
Groups: 1015 9997 50207 所属组
VmPeak: 1602296 kB
VmSize: 1602296 kB
进程并未包含1028,访问被拒绝。如何让APP拥有1028,sdcard_r的用户组权限?
在APK安装的时候,PackageManagerService会根据应用申请的使用权限来授予不同的用户组,每个权限代表一个或者多个用户组。具体来说就是在APK的源文件AndroidManifest.xml文件里面应该要包含有申请权限的关键字
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
PackageManagerService在安装APK时会解析这些关键字,根据不同的关键字增加不同的用户组权限给APP。
那这些关键字对应什么样的权限呢?我们可以查看Android的源代码
/frameworks/base/data/etc/platform.xml
可以确认的是android.permission.WRITE_EXTERNAL_STORAGE对应的是1028用户组。
所以接下来我们给APP增加android.permission.WRITE_EXTERNAL_STORAGE的permission再来测试一遍。修改之后再检查权限
root@a7ltectc:/mnt/shell/emulated/0 # ps -t | grep myfile
u0_a207 11509 321 1584752 71316 ffffffff b6fb3994 S com.example.myfiletest
root@a7ltectc:/mnt/shell/emulated/0 # cat /proc/11509/status
cat /proc/11509/status
Name: mple.myfiletest
State: S (sleeping)
Tgid: 11509
Pid: 11509
PPid: 321
TracerPid: 0
Uid: 10207 10207 10207 10207
Gid: 10207 10207 10207 10207
FDSize: 256
Groups: 1015 1028 9997 50207
VmPeak: 1602436 kB
可见APP已经拿到1028组,检查测试结果
内置SD卡:
没有内容返回,检查目录abc.txt文件已创建
外置SD卡:
03-01 06:28:56.727 D 9009 9009 test: open failed: EACCES (Permission denied)
内置SD卡问题已解决,但外置SD卡依然无法写入。
4. 为什么不能写入外置sd卡
外置sd卡的挂载点情况
/dev/block/vold/179:64 /mnt/media_rw/extSdCard vfat rw
/mnt/media_rw/extSdCard /storage/extSdCard sdcardfs rw,seclabel,nosuid,nodev,relatime,uid=1023,gid=1023,derive=unified 0 0
查看挂载点的文件权限情况
root@a7ltectc:/storage/extSdCard # ll
drwxrwx--x root sdcard_r 2016-07-25 13:22 Android
-rwxrwx--- root sdcard_r 310445 2015-02-28 11:53 EncryptPhoneDemo.apk
-rwxrwx--- root sdcard_r 3251377 2015-02-28 11:53 EncryptPhoneSDK12_21.apk
drwxrwx--- root sdcard_r 2016-07-17 13:27 LOST.DIR
明显文件权限的情况和刚才内置SD卡的情况是一致的,增加了WRITE_EXTERNAL_STORAGE这个权限之后,APP已经拿到了sdcard_r(1028)这个所属组,按理应该可以正常操作目录才对,但是测试结果确返回失败。
问题答案需要从sdcardfs代码里面寻找,查看sdcardfs创建文件的路径
/kernel/fs/sdcardfs
const struct inode_operations sdcardfs_dir_iops = {
.create = sdcardfs_create,
.lookup = sdcardfs_lookup,
.permission = sdcardfs_permission,
.unlink = sdcardfs_unlink,
.mkdir = sdcardfs_mkdir,
log调试发现,sdcardfs_create返回了-EACCES
static int sdcardfs_create(struct inode *dir, struct dentry *dentry,
umode_t mode, bool excl)
{
...
int has_rw = get_caller_has_rw_locked(sbi->pkgl_id, sbi->options.derive);
if(!check_caller_access_to_name(dir, dentry->d_name.name, sbi->options.derive, 1, has_rw)) {
printk(KERN_INFO "%s: need to check the caller's gid in packages.list\n"
" dentry: %s, task:%s\n",
__func__, dentry->d_name.name, current->comm);
err = -EACCES; //此处返回了EACCES,permission denies的错误。
goto out_eacces;
}
...
}
很快判断了get_caller_has_rw_locked()函数,返回了0,也就是has_rw = 0,最终导致返回了没权限的错误。
int get_caller_has_rw_locked(void *pkgl_id, derive_t derive) {
struct packagelist_data *pkgl_dat = (struct packagelist_data *)pkgl_id;
unsigned long appid;
int ret;
/* No additional permissions enforcement */
if (derive == DERIVE_NONE) {//挂载参数,内置SD卡derive=legecy,外置SD卡derive=unified
return 1;
}
appid = multiuser_get_app_id(current_fsuid());
printk("liangyi get_caller_has_rw_locked appid=%u\n", appid);
mutex_lock(&pkgl_dat->hashtable_lock);
ret = contain_appid_key(pkgl_dat, (void *)appid); 很明显该函数返回了0,appid就是本APP用户,参考前面内容是10207(u0_a207代表10207)
mutex_unlock(&pkgl_dat->hashtable_lock);
//printk(KERN_INFO "sdcardfs: %s: appid=%d, ret=%d\n", __func__, (int)appid, ret);
return ret;
}
contain_appid_key的实现是,查询pkg1_data->appid_with_rw这个哈希表里面,是否包含有本appid,10207。
static int contain_appid_key(struct packagelist_data *pkgl_dat, void *appid) {
struct hashtable_entry *hash_cur;
hash_for_each_possible(pkgl_dat->appid_with_rw, hash_cur, hlist, (unsigned long)appid)
if (appid == hash_cur->key)
return 1;
return 0;
}
因此猜测哈希表里面并没有我们这个appid,稍微理解一下,哈希表名称appid_with_rw,字面意思就是这个appid是否有读写rw的权限。所以这个哈希表里面存的肯定是有读写权限的appid。既然是哈希表,肯定代码有哈希表插入的地方。
接下来查找插入哈希表的位置。简单搜索可以找到函数insert_int_to_null,过滤掉无用代码。
static int insert_int_to_null(struct packagelist_data *pkgl_dat, void *key, int value) {
...
new_entry = kmem_cache_alloc(hashtable_entry_cachep, GFP_KERNEL);
if (!new_entry)
return -ENOMEM;
new_entry->key = key; 这个key就是appid
new_entry->value = value;
hash_add(pkgl_dat->appid_with_rw, &new_entry->hlist,
(unsigned long)new_entry->key);
return 0;
}
哪里调用的?找到read_package_list函数,过滤无用代码之后。
static int read_package_list(struct packagelist_data *pkgl_dat) {
...
fd = sys_open(kpackageslist_file, O_RDONLY, 0); kpackageslist_file = "/data/system/packages.list";
while ((read_amount = sys_read(fd, pkgl_dat->read_buf,
sizeof(pkgl_dat->read_buf))) > 0) {
...
additional_read = read_amount - one_line_len;
if (additional_read > 0)
sys_lseek(fd, -additional_read, SEEK_CUR);
if (sscanf(pkgl_dat->read_buf, "%s %lu %*d %*s %*s %s",
pkgl_dat->app_name_buf, &appid,
pkgl_dat->gids_buf) == 3) {
...
while (token != NULL) {
if (!kstrtoul(token, 10, &ret_gid) &&
(ret_gid == pkgl_dat->write_gid)) {
ret = insert_int_to_null(pkgl_dat, (void *)appid, 1);
...
break;
}
}
}
}
...
}
简单描述代码逻辑:
- 打开文件/data/system/package.list
- 读数据,反复的读,并且解析其中的内容,把文件的内容读到pkg1_data这个数组里面去。
- 面对关键数据pkg1_dat->gids_buf,如果里面的值和pkg1_dat->write_gid匹配的话,把appid插入到哈希表里面去。
思路显然是不复杂的,关键是牵出来一个新的东西。/data/system/package.list是个什么东西。
这涉及到Android的PackageMangerService,这个文件是PackageMangerService根据系统安装的APP情况操作出来的,简单列下其内容:
/data/system/package.list
com.android.providers.telephony 1001 0 /data/data/com.android.providers.telephony platform 1030,3002,1023,1015,3003,3001,1007,3006
com.sec.android.app.parser 1000 0 /data/data/com.sec.android.app.parser platform 2001,3009,3002,1023,1015,3003,3001,1021,3004,3005,1000,2002,1010
com.google.android.googlequicksearchbox 10050 0 /data/data/com.google.android.googlequicksearchbox untrusted 1005,3002,3003,3001
com.android.providers.calendar 10033 0 /data/data/com.android.providers.calendar release 3003
里面描述了APP的包名,APPID,安装位置,APP签名情况,和用户组情况(也就是权限情况),这个文件在任何APP有更新(安装或卸载)的情况下都会发生改变。
sdcardfs启动了一个线程pkgld,来监控此文件的变化,并实时更新自己的哈希表内容。
packagelist_thread = kthread_run(packagelist_reader, (void *)pkgl_dat, "pkgld");
注意,每一个sdcardfs的new mount挂载都会有一个pkgld的线程出现。线程数量和挂载数量挂钩。
根据上面的代码
if (!kstrtoul(token, 10, &ret_gid) && (ret_gid == pkgl_dat->write_gid))
ret = insert_int_to_null(pkgl_dat, (void *)appid, 1);
基本明白,只要APP的用户组权限里面有和pkgl_dat->write_gid匹配的,插入的到哈希表里面去。
接下来,pkgl_dat->write_gid是什么值?
void * packagelist_create(gid_t write_gid)
{
...
pkgl_dat->write_gid = write_gid;
}
static int sdcardfs_read_super(struct super_block *sb, const char *dev_name,
void *raw_data, int silent)
{
if (sb_info->options.derive != DERIVE_NONE)
pkgl_id = packagelist_create(sb_info->options.write_gid);
}
很明显是挂载sdcardfs,带的挂载参数,sb_info->options.write_gid配置的。
查找挂载参数
static int parse_options(struct super_block *sb, char *options, int silent,
int *debug, struct sdcardfs_mount_options *opts)
{
case Opt_wgid:
if (match_int(&args[0], &option))
return 0;
opts->write_gid = option;
break;
}
static const match_table_t sdcardfs_tokens = {
{Opt_uid, "uid=%u"},
{Opt_gid, "gid=%u"},
{Opt_wgid, "wgid=%u"},
{Opt_debug, "debug"},
{Opt_split, "split"},
{Opt_derive, "derive=%s"},
{Opt_lower_fs, "lower_fs=%s"},
{Opt_reserved_mb, "reserved_mb=%u"},
{Opt_err, NULL}
};
解析挂载Opt_wgid这个挂载参数的时候,保存的。使用了关键字wgid=%u,参数解析是在程序/system/bin/sdcard里面完成的。
/android/system/core/sdcard/sdcard.c
static int run(const char* source_path, const char* dest_path, uid_t uid,
gid_t gid, gid_t write_gid, int num_threads, derive_t derive,
bool split_perms, bool support_fat, bool reserved_space) {
res = sprintf(opts_p, "uid=%d,gid=%d,wgid=%d", uid, gid, write_gid);
}
对于内置SD卡,并没有配置-w参数,而外置SD卡,配置了-w为1023。根据sdcarfs代码没有配置采用默认值1015。
/android/device/qcom/common/rootdir/etc/init.qcom.rc
service sdcard /system/bin/sdcard -u 1023 -g 1023 -l -r /data/media /mnt/shell/emulated
class late_start
oneshot
service fuse_extSdCard /system/bin/sdcard -u 1023 -g 1023 -w 1023 -d -f /mnt/media_rw/extSdCard /storage/extSdCard
class late_start
disabled
oneshot
回到最初进程权限列表上看
有1015,1028。但是很可惜没有1023。没有1023意味着哈希表appid_with_rw里面并没有我们这个APP id,也就是哈希表搜索会返回0,最终导致无访问权限。
那1023对应的是什么权限?
搜索下很容易发现
1023的权限是media_rw,需要有android.permission.WRITE_MEDIA_STORAGE权限。
<permission name="android.permission.WRITE_MEDIA_STORAGE" >
<group gid="media_rw" />
<group gid="sdcard_rw" />
</permission>
既然事已至此,我们在APP里面增加这个android.permission.WRITE_MEDIA_STORAGE,事情不就解决了吗?
还是很遗憾,Android4.4, 5.0 和 6.0对于这个权限的开放都仅限于system APP,也就是说第三方APP无法申请该权限,
简单的看就是如果申请该权限会得到这样一个错误
permission is only granted to system apps.
所以现在明白为什么第三方APP不能随意访问外置SD卡了吧。
5. 总结
APP申请android.permission.WRITE_EXTERNAL_STORAGE权限之后,可以随意读写内置sd卡,但是对于外置sd卡,android还是做了限制,需要其他途径来完成外置sd卡读写,这里不做介绍。
sdcardfs暴露出来了什么问题
1. 通过hash表来维护一个可写app list,导致每次写操作都要查询hash表才能做判断,这可能带来性能的损失。
2. 内部packagelist相关代码维护的appid_with_rw哈希表需要先被初始化好,如果sdcardfs先于package manager service初始化话好的话,始终要等待package manager service把/data/system/package.list初始化好,否则无法正常判断是否允许写入内置外置sd卡。这间接的引发了一些one time的时序问题,表现为sdcardfs一直在等待package.list的初始化。
Android8.0之后,sdcardfs被谷歌接管,后面将介绍Android8.0之后sdcardfs在控制sd卡访问权限上的差异。
更多推荐
所有评论(0)