自从android4.4 以来,第三方应用程序是不能再随便的访问sdcard了,从开发者的角度而言,研究一下android系统到底是怎么样来实现这样的控制还是比较有价值的。

首先分析下现状,现在已知,对于内部存储系统android的控制策略还是没有多大改变的,内部sdcard还是可以在申请了android.permission.WRITE_EXTERNAL_STORAGE

这个权限后随便访问的,而对于外置sdcard就没那么方便。网络上一般的说法是只有内置app才可以访问,其他的app就只能限定/data/data/Package_name/ 的目录下。

那具体这个差别是怎么实现的呢?

首先sdcard的权限管理是fuse 即用户空间文件系统(Filesystem in Userspace)来实现的,一旦使用了fuse,文件的访问权限就不在是单纯的Linux的ACL机制,不能光凭借文件的umask来判断谁具有读写权限了。以5.0为例:

drwxrwx--- root sdcard_r 2014-01-01 07:47 Alarms

drwxrwx--x root sdcard_r 2014-01-01 07:47 Android

drwxrwx--- root sdcard_r 2014-01-02 06:35 DCIM

drwxrwx--- root sdcard_r 2014-01-01 07:47 Download

即使app的gid加入了sdcard_r甚至sdcard_rw也是不能访问这些目录了。原因就是这个fuse的机制。本质上android的fuse并不是正统的fuse,而是fuse代码的重写,以sdcard的deamo的形式存在在android系统中,这个是google说的。

那么找到sdcard的deamo的实现就可以找到线索了。在android源码system/core/sdcard中可以找sdcard.c这么个文件。它最终被编译成为sdcard可执行程序,这就是所谓的sdcard的守护进程。查看它的代码可以发现它在初始化以后就跑了一个for (;;)的循环,一直从/dev/fuse这个设备中读取请求,然后交给handle_fuse_request去处理。从Android的整体设计原则来猜想,这又是一个CS的典型结构,即只有这个进程可以进行真正意义上的读写操作,上层的对于sdcard的io操作,实际上是通过/dev/fuse交给这个守护进程来处理的。

事实上是这样吗。来看下sdcard是谁启动的:


service sdcard /system/bin/sdcard -u 1023 -g 1023 -l /data/media /mnt/shell/emulated

class late_start

service fuse_sdcard1 /system/bin/sdcard -u 1023 -g 1023 -w 1023 -d /mnt/media_rw/sdcard1 /storage/sdcard1

class late_start

disabled


这个是init.rc中的片段,/system/bin/sdcard 启动时候加了很多参数

-u 1023 应该是uid

-g 1023 应该是gid

-l /data/media /mnt/shell/emulated 这个表示的什么要看代码实现了,代码里前一个是source_path 后一个dest_path, dest_path挂载了/dev/fuse。

根据经验来猜测,上层针对 /mnt/shell/emulated的操作本质上是对/data/media 的操作,但是中间夹杂着fuse的处理,必须由sdcard这个程序来决定真正的操作。

drwxrwx--- media_rw media_rw 2014-01-01 07:47 media

可以发现这个目录的uid和gid都是media_rw, 而 # ps -c sdcard

USER PID PPID VSIZE RSS CPU WCHAN PC NAME

media_rw 2098 1 16232 1120 4 ffffffff 9b64f1d4 S /system/bin/sdcard

发现这个进程的user正式media_rw,这样就和之前的猜测不谋而合了。即,只有/system/bin/sdcard 能够真正的操作/data/media ,而/data/media 真是虚拟的sdcard的本尊!

所有对与虚拟sdcard的操作都通过fuse的设计,经kernel从/dev/fuse又给/system/bin/sdcard 这个进程,由它来正真的handle请求。 Ok,大概的逻辑就是这样了,详细的实现还是要从代码中去学习。

问题回到原来的,为什么内置sdcard可以随便访问,而外置不行呢。答案还在代码中,查看sdcard.c中对于创建文件的处理,

static int handle_mknod(struct fuse* fuse, struct fuse_handler* handler,

const struct fuse_in_header* hdr, const struct fuse_mknod_in* req, const char* name)

{

bool has_rw;

struct node* parent_node;

char parent_path[PATH_MAX];

char child_path[PATH_MAX];

const char* actual_name;

pthread_mutex_lock(&fuse->lock);
has_rw = get_caller_has_rw_locked(fuse, hdr);
parent_node = lookup_node_and_path_by_id_locked(fuse, hdr->nodeid,
parent_path, sizeof(parent_path));
TRACE("[%d] MKNOD %s 0%o @ %"PRIx64" (%s)\n", handler->token,
name, req->mode, hdr->nodeid, parent_node ? parent_node->name : "?");
pthread_mutex_unlock(&fuse->lock); if (!parent_node || !(actual_name = find_file_within(parent_path, name,
child_path, sizeof(child_path), 1))) {
return -ENOENT;
}
if (!check_caller_access_to_name(fuse, hdr, parent_node, name, W_OK, has_rw)) {
return -EACCES;
}
__u32 mode = (req->mode & (~0777)) | 0664;
if (mknod(child_path, mode, req->rdev) < 0) {
return -errno;
}
return fuse_reply_entry(fuse, hdr->unique, parent_node, name, actual_name, child_path);

}

其中check_caller_access_to_name 是关键,因为他会返回EACCES!

static bool check_caller_access_to_name(struct fuse* fuse,

const struct fuse_in_header hdr, const struct node parent_node,

const char* name, int mode, bool has_rw) {

/* Always block security-sensitive files at root */

if (parent_node && parent_node->perm == PERM_ROOT) {

if (!strcasecmp(name, "autorun.inf")

|| !strcasecmp(name, ".android_secure")

|| !strcasecmp(name, "android_secure")) {

return false;

}

}

/* No additional permissions enforcement */
if (fuse->derive == DERIVE_NONE) {
return true;
} /* Root always has access; access for any other UIDs should always
* be controlled through packages.list. */
if (hdr->uid == 0) {
return true;
} /* If asking to write, verify that caller either owns the
* parent or holds sdcard_rw. */
if (mode & W_OK) {
if (parent_node && hdr->uid == parent_node->uid) {
return true;
} return has_rw;
} /* No extra permissions to enforce */
return true;

}

注释写的比较清楚就不多分析了,重点是has_rw ,这个谁决定

has_rw = get_caller_has_rw_locked(fuse, hdr);

get_caller_has_rw_locked的实现

/* Return if the calling UID holds sdcard_rw. /

static bool get_caller_has_rw_locked(struct fuse
fuse, const struct fuse_in_header hdr) {

/
No additional permissions enforcement */

if (fuse->derive == DERIVE_NONE) {

return true;

}

  appid_t appid = multiuser_get_app_id(hdr->uid);
return hashmapContainsKey(fuse->appid_with_rw, (void*) (uintptr_t) appid);

}

hashmapContainsKey这个方法,看appid是不是在appid_with_rw 这个map中,那这个map是哪里来的呢?通过追踪可以发现时通过/data/system/packages.list这个文件解析得出的。

 if (strtoul(token, NULL, 10) == fuse->write_gid) {
hashmapPut(fuse->appid_with_rw, (void*) (uintptr_t) appid, (void*) (uintptr_t) 1);
break;
}

这段代码在解析packages.list中的每一行,如果有gid = fuse->write_gid的那就会被加到这个Hashmap中了。

com.android.providers.downloads.ui 10005 0 /data/data/com.android.providers.downloads.ui default 1028,1015,1023,1024,2001,3003,3007

com.android.pacprocessor 10040 0 /data/data/com.android.pacprocessor platform 3003

com.android.certinstaller 10024 0 /data/data/com.android.certinstaller platform none

com.android.speechrecorder 10050 0 /data/data/com.android.speechrecorder default none

android 1000 0 /data/system platform 1028,1015,3002,3001,3003

那fuse->write_gid 是什么呢?

gid_t write_gid = AID_SDCARD_RW; 这是初始化时候的值,AID_SDCARD_RW 在system/core/include/private/android_filesystem_config.h中有定义, 1015正是sdcard_rw的group id。

但是,但是,还记得init.rc里面的 -w吗?

service fuse_sdcard1 /system/bin/sdcard -u 1023 -g 1023 -w 1023 -d /mnt/media_rw/sdcard1 /storage/sdcard1

这个-w的值就覆盖了write_gid , 1023是什么gid?

define AID_MEDIA_RW 1023

ok,正是media_rw的group id。

AID_SDCARD_RW 和 AID_MEDIA_RW 有什么差别,怎么才能加入这个对应group呢?

android权限管理机制,加permission呗。那来看看对应的permission是怎么声明的。

<permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:permissionGroup="android.permission-group.STORAGE"
android:label="@string/permlab_sdcardWrite"
android:description="@string/permdesc_sdcardWrite"
android:protectionLevel="dangerous" /> <!-- @SystemApi Allows an application to write to internal media storage
@hide -->
<permission android:name="android.permission.WRITE_MEDIA_STORAGE"
android:permissionGroup="android.permission-group.STORAGE"
android:label="@string/permlab_mediaStorageWrite"
android:description="@string/permdesc_mediaStorageWrite"
android:protectionLevel="signature|system" />

要加入sdcard_rw组需要加android.permission.WRITE_EXTERNAL_STORAGE,

要加入media_rw组需要加android.permission.WRITE_MEDIA_STORAGE。

Ok,真相大白了! WRITE_EXTERNAL_STORAGE的protectionLevel 仅仅是dangerous, 而WRITE_MEDIA_STORAGE的protectionLevel 是signature|system, 这就是为什么第三方应用程序不能随便访问sdcard的原因!

转载自:https://blog.csdn.net/louyong0571/article/details/45046163

android sdcard 权限管理策略研究的更多相关文章

  1. 大约Android 了解权限管理

    如Android应用程序开发人员.为android权限机制一直觉得很奇怪.为什么要这个东西权限?为什么要AndroidManifest里面写的uses-permission 这样的事情?我一直搞不清楚 ...

  2. Android 开发 权限管理

    Android 开发 权限管理 https://sspai.com/post/42779 $ adb shell pm list permissions -d -g https://zhuanlan. ...

  3. [Android Pro] android 4.4 Android原生权限管理:AppOps

    reference : http://m.blog.csdn.net/blog/langzxz/45308199 reference : http://blog.csdn.net/hyhyl1990/ ...

  4. android 自定义权限管理

    在Android6.0后有些权限就需要进行询问,虽然可以将targetSdkVersion设置成小于等于23,但是这样可能有些东西无法使用,所以要进行权限的管理. 实现逻辑:打开页面就询问权限,如果没 ...

  5. Android应用权限管理总结

      访问登记属性 android.permission.ACCESS_CHECKIN_PROPERTIES ,读取或写入登记check-in数据库属性表的权限 获取错略位置 android.permi ...

  6. android 手机权限管理——PermissionsDispatcher

    Android6.0 之后某些权限需要动态申请,相比于之前版本复杂了许多.不过已经有大神给我们写好了框架(PermissionsDispatcher),我们用起来还是很方便. 1.添加引用 根据 gr ...

  7. 第一篇、Android Supersu 权限管理定制,隐藏过滤权限,指定APP最高权限

    近期有个需求,在预装ROM的时候,须要权限,可是又不同意全部的应用都有权限,仅仅同意自己的应用有最高的权限(当然没有系统签名情况下). 所以.编译了CM 提取了supersu进行了二次定制,让他进行权 ...

  8. Android权限管理之RxPermission解决Android 6.0 适配问题

    前言: 上篇重点学习了Android 6.0的运行时权限,今天还是围绕着Android 6.0权限适配来总结学习,这里主要介绍一下我们公司解决Android 6.0权限适配的方案:RxJava+RxP ...

  9. Android权限管理之Android 6.0运行时权限及解决办法

    前言: 今天还是围绕着最近面试的一个热门话题Android 6.0权限适配来总结学习,其实Android 6.0权限适配我们公司是在今年5月份才开始做,算是比较晚的吧,不过现在Android 6.0以 ...

随机推荐

  1. 【bzoj1495】[NOI2006]网络收费 暴力+树形背包dp

    题目描述 给出一个有 $2^n$ 个叶子节点的完全二叉树.每个叶子节点可以选择黑白两种颜色. 对于每个非叶子节点左子树中的叶子节点 $i$ 和右子树中的叶子节点 $j$ :如果 $i$ 和 $j$ 的 ...

  2. java中初始化块、静态初始化块和构造方法

    (所谓的初始化方法init()是另一回事, 在构造方法之后执行, 注意不要混淆) 在Java中,有两种初始化块:静态初始化块和非静态初始化块.它们都是定义在类中,用大括号{}括起来,静态代码块在大括号 ...

  3. (转)MS14-068域内提权漏洞总结

    0x01 漏洞起源 说到ms14-068,不得不说silver ticket,也就是银票.银票是一张tgs,也就是一张服务票据.服务票据是客户端直接发送给服务器,并请求服务资源的.如果服务器没有向域控 ...

  4. 【agc012E】Camel and Oases

    Portal --> agc012 Description 有一排点,两点间有一定距离,初始的时候有一个行走值\(v\),如果说两点间距离不超过\(v\),那么可以在这两点间自由行走,如果当前\ ...

  5. 【bzoj3105】新Nim游戏

    Portal--> bzoj3105 新Nim游戏 Solution 转化一下问题 首先看一下原来的Nim游戏,先手必胜的条件是:每堆数量的异或和不为\(0\) 所以在新的游戏中,如果要保证自己 ...

  6. Listener 介绍

    当 web 应用在 web 容器中运行时,web 应用内部会不断地发生各种事件:如 web 应用启动.web 应用停止,用户 session 开始.用户 session 结束.用户请求到达等. 实际上 ...

  7. [python]乱码:python抓取脚本

    参考: http://www.zhxl.me/1409.html 使用 python urllib2 抓取网页时出现乱码的解决方案 发表回复 这里记录的是一个门外汉解决使用 urllib2 抓取网页时 ...

  8. lightoj 1148 Mad Counting(数学水题)

    lightoj 1148 Mad Counting 链接:http://lightoj.com/volume_showproblem.php?problem=1148 题意:民意调查,每一名公民都有盟 ...

  9. 关于C#微信公众号开发的前言说明

    本人是昨天开始接触微信公众号开发的,昨天看一天官方文档,基本上晕乎乎的,刚开始接触这个真的有点困难,特别是C#在这方面的资料不多,不如php java方面的资料全. 所以我准备每天写一点关于C#微信开 ...

  10. 关于NUL

    问题:正常的order by不起作用了,如下图 分析:使用notepad++打开,发现 NUL以字符'\0'作为字符串结束标志.'\0'是一个ASCII码为0的字符,从ASCII码表中可以看到ASCI ...