Link File and Sdcard in Android

由于工作上的变动,以及研究目标的转变,笔者没有完成博客建立之初订的一月一篇博文的目标,今天动笔才发现博客开通已经整整两年。回看这两年来的文章,其中不乏有些凑数的水文,实是没有什么价值。 那么就将这两年之际新作为一个的开始,重新制定一个目标,从头再来。

问题提出

sdcard 目录是 Android 系统中存在的默认目录,有时候即使手机中没有插入 sdcard 也会存在一个 sdcard 目录,这个目录可以被所有的已安装应用访问,笔者因此有一个想法,是否可以通过 sdcard 中的链接文件来绕过某些针对文件路径的合法性检测。

Linux中的链接

软链接、硬链接和符号链接之间的比较是一个经常被提起的话题,网络上的各种帖子文章所说也都不同。其实这个问题需要分系统讨论。

在 Windows 系统中,软链接、硬链接和符号链接的含义均不相同。软链接和硬链接均由文件系统实现,需要文件系统的支持,而“符号链接”是指“符号链接对象”,由对象管理器实现,与文件系统无关;而在 linux 系统中符号链接就是软链接。这里只对 Linux 系统做详细介绍。

硬链接的源文件和目标文件共享同一个内核对象 INode,源文件的 dentry 和目标文件的 dentry 指向同一个 inode,Inode 在同一个文件系统中是唯一的,因此硬链接无法跨文件系统创建

而软链接则占有一个新的内核对象,其 dentry 指向一个新的 inode,Inode 对象的内容就是源文件的文件名。

查看源码中硬链接的实现,其主要逻辑可以使用伪代码表述为

1
2
3
4
5
6
7
8
1. user_path_parent(newdfd, newname, &nd, &to); // 查找父目录
2. if (old_path.mnt != nd.path.mnt) return // 判断是否属于同一个文件系统
3. new_dentry = lookup_create(&nd, 0); // 为链接文件创建 dentry
4. vfs_link(old_path.dentry, nd.path.dentry->d_inode, new_dentry); // 初始化 dentry
4.1 may_create(dir, new_dentry) // 检查是否有权限在父目录下新增 dentry
4.2 error = dir->i_op->link(old_dentry, dir, new_dentry); // 通过索引节点操作表调用具体文件系统
4.2.1将链接文件的dentry添加到父目录的数据块
4.2.2将源文件的inode号记录在链接文件dentry中

相对应的软链接的实现逻辑为

1
2
3
4
5
6
7
8
1. user_path_parent(newdfd, newname, &nd, &to); // 查找父目录
2. new_dentry = lookup_create(&nd, 0); // 在父目录为链接文件创建 dentry
3. vfs_symlink(nd.path.dentry->d_inode, dentry, from); // 初始化 dentry
3.1 may_create(dir, new_dentry) // 检查是否有权限在父目录下新增 dentry
3.2 error = dir->i_op->symlink(dir, dentry, oldname); // 通过索引节点操作表调用具体文件系统
3.2.1 为软链接创建一个新的 inode 结构
3.2.2 将源文件名记录在 inode 中
3.2.3 将链接文件的 inode 和 dentry 关联并加入其父目录数据块中

Android 中的链接

Android 系统脱胎于 Linux,其文件系统也继承了 Linux 的 VFS,可以通过 VFS 机制在多个文件系统间协同工作。通过 df 命令和 mount 命令可以查看挂载的文件系统信息。如图是在 Pixel 上执行的命令,可以看出系统中同时存在多个文件系统 ext4、tmpfs、devpts、proc、sysfs、selinuxfs。 查看最后几条记录,可以看出 sdcard 其实是在 data/media 目录下虚拟出来的一块空间,文件系统为 sdcardfs,这些解释了为什么有些手机没有插入 sdcard 也会有这个目录

Sdcardfs 是专为 sdcard 设计的一套文件系统,其前身是 Fuse (Filesystem in Userspace),这里感谢 CSDN 上的研究者对 sdcard 和 fuse 的详细分析,无论是 sdcardfs 还是 Fuse 其本质都可以看作是底层文件系统 Ext4 的代理,对文件的访问操作都需要经过 sdcardfs 来进行中转,sdcaardfs 的速度更快。

那么回到最初的问题,在 Android 系统中是否可以通过创建 sdcard 中的链接文件来绕过某些路径合法性检测呢,

经实验发现,无论是硬链接还是软链接在 sdcard 上创建均会失败。在 sdcard 挂载的根目录位置创建的链接文件在 sdcard 中则无法解析。硬链接的原因比较直接,由于硬链接无法跨文件系统,因此在创建硬链接的第二步时会判断,这里的异常就发生在这里。而软链接的失败是什么原因呢

查看安卓系统源码 sdcardfs 的实现 https://android.googlesource.com/kernel/msm/+/android-7.0.0_r0.1/fs/sdcardfs/inode.c,发现这里其实实现了 link 和 symlink 方法。但是 VFS 的函数调用是通过 inode_operations 结构体来进行的,查看这个结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const struct inode_operations sdcardfs_dir_iops = {
.create = sdcardfs_create,
.lookup = sdcardfs_lookup,
.unlink = sdcardfs_unlink,
.mkdir = sdcardfs_mkdir,
.rmdir = sdcardfs_rmdir,
.rename = sdcardfs_rename,
.setattr = sdcardfs_setattr,
.getattr = sdcardfs_getattr,
/* XXX Following operations are implemented,
* but FUSE(sdcard) or FAT does not support them
* These methods are *NOT* perfectly tested.
.symlink = sdcardfs_symlink,
.link = sdcardfs_link,
.mknod = sdcardfs_mknod,
*/
};

源码并没有在 operations 中导出 symlink 和 link 方法,因此即使有这两个函数的实现,但是通过 VFS 执行相应命令时还是会失败。

总结

从上面的分析可以看出,安卓系统的 sdcard 在设计之初就有着针对链接文件的防御,不允许在 sdcard 上创建链接文件。但是我们仍然可以通过在 data/data 目录创建链接文件的方式进行某些攻击。