正文
本文分析基于Android T(13)
fdsan,全名为file descriptor sanitizer,是Android中的一种检测工具,用于检测fd的use-after-close和double-close错误。这两个错误会给设备留下安全漏洞,甚至造成数据泄露等安全问题。然而现实情况是这两种错误非常隐蔽,且难以排查。这才催生了fdsan的诞生。
fdsan由Google的工程师Josh Gao开发。最初在Android 10中引入,检测到错误时会打印log并继续运行。Android 11更改了运行模式,一旦检测到错误就立即abort,让问题暴露得更加醒目。
要让这些问题可以被检测,关键是要构建fd的所有权体系。
这套体系的建立有两个重要前提:
- Android中大多数fd并非直接通过open/close的原生接口进行管理,而是通过unique_fd,FileOutputStream,ParcelFileDescriptor之类的封装形式。
- fd是一个整数,且每个进程所能创建的fd都有上限,早期为1024,现在为32768。因此即便为每个fd创建单独的所有权管理数据也不会消耗多少空间。
一个fd由生到死的所有者
基于这两个条件,fdsan使用一个64-bit的tag来表明一个fd由生到死的所有者。
tag组成
tag由两部分组成,最高位的8-bit构成type,后面的56-bit构成value。Type表示fd通过何种封装形式进行管理,譬如ANDROID_FDSAN_OWNER_TYPE_UNIQUE_FD
就表示fd通过unique_fd进行管理。
对于应用工程师而言,他们接触到的大多为Java/Kotlin代码,因此Java中常用的FileInputStream/FileOutputStream/ParcelFileDescriptor等封装形式也有相应的type。
详细列表如下。
enum android_fdsan_owner_type {
ANDROID_FDSAN_OWNER_TYPE_GENERIC_00 = 0,
ANDROID_FDSAN_OWNER_TYPE_GENERIC_FF = 255,
ANDROID_FDSAN_OWNER_TYPE_FILE = 1,
ANDROID_FDSAN_OWNER_TYPE_DIR = 2,
ANDROID_FDSAN_OWNER_TYPE_UNIQUE_FD = 3,
ANDROID_FDSAN_OWNER_TYPE_SQLITE = 4,
ANDROID_FDSAN_OWNER_TYPE_FILEINPUTSTREAM = 5,
ANDROID_FDSAN_OWNER_TYPE_FILEOUTPUTSTREAM = 6,
ANDROID_FDSAN_OWNER_TYPE_RANDOMACCESSFILE = 7,
ANDROID_FDSAN_OWNER_TYPE_PARCELFILEDESCRIPTOR = 8,
ANDROID_FDSAN_OWNER_TYPE_ART_FDFILE = 9,
ANDROID_FDSAN_OWNER_TYPE_DATAGRAMSOCKETIMPL = 10,
ANDROID_FDSAN_OWNER_TYPE_SOCKETIMPL = 11,
ANDROID_FDSAN_OWNER_TYPE_ZIPARCHIVE = 12,
};
tag中的value主要用作唯一性标识。对于native层的封装(譬如unique_fd)而言,value为封装对象的指针值;对于Java层的封装(譬如FileInputStream)而言,value为封装对象的hash code。这两种值在同一个进程里都具有唯一性。
tag的创建和检验过程
了解完tag的构成后,接下来便是tag的创建和检验过程。
通过封装对象创建相应fd时(通常在对象的构造函数内),tag便会创建。而所有的close都会去进行tag检验,不论该close是通过封装对象的析构调用还是直接调用。举个例子,当一个fd通过FileInputStream创建时,对象回收期间便会close fd。可是如果我们不小心将此fd传到了JNI函数中,通过close函数直接去操作它,那么检测时我们就会发现:期望的tag是一个非零值(由type和value构成),实际的tag为0(通过原生close函数进行操作时tag为0),继而报错。
典型的报错log如下(与上述例子无关),一个通过opendir打开的fd,最终却直接通过close关闭,而不是closedir。
pid: 610, tid: 610, name: lmkd >>> /system/bin/lmkd <<<
uid: 1069
signal 6 (SIGABRT), code -1 (SI_QUEUE), fault addr --------
Abort message: 'fdsan: attempted to close file descriptor 15, expected to be unowned, actually owned by DIR* 0x745b003c00'
x0 0000000000000000 x1 0000000000000262 x2 0000000000000006 x3 0000007fc8696f90
x4 0000000000000000 x5 0000000000000000 x6 0000000000000000 x7 0000000000000010
x8 00000000000000f0 x9 643eeef5a527dc04 x10 0000000000000001 x11 0000000000000000
x12 0000000000000008 x13 0000000060345fc8 x14 000216096eb6f000 x15 000024dad07fc39e
x16 000000745b715948 x17 000000745b6f4390 x18 000000745c0bc000 x19 0000000000000262
x20 0000000000000262 x21 000000745bc34000 x22 000000745cfdeb5c x23 0000000000000003
x24 0000007fc8697080 x25 ffffff80ffffffc8 x26 0000007fc8696d00 x27 0000007fc8696cc0
x28 0000000000000000 x29 0000007fc8697020
lr 000000745b6abf4c sp 0000007fc8696c40 pc 000000745b6abf6c pst 0000000000001000
backtrace:
#00 pc 000000000008df6c /apex/com.android.runtime/lib64/bionic/libc.so (fdsan_error(char const*, ...)+588)
#01 pc 000000000008dc68 /apex/com.android.runtime/lib64/bionic/libc.so (android_fdsan_close_with_tag+740)
#02 pc 000000000008e3d0 /apex/com.android.runtime/lib64/bionic/libc.so (close+16)
#03 pc 000000000000b110 /system/bin/lmkd (start_wait_for_proc_kill(int)+184)
#04 pc 000000000000a45c /system/bin/lmkd (find_and_kill_process(int, int, char const*, meminfo*, timespec*, bool)+744)
#05 pc 00000000000098a0 /system/bin/lmkd (mp_event_common(int, unsigned int, polling_params*)+2140)
#06 pc 000000000000ba1c /system/bin/lmkd (call_handler(event_handler_info*, polling_params*, unsigned int)+64)
#07 pc 00000000000053a8 /system/bin/lmkd (main+2440)
#08 pc 000000000008506c /apex/com.android.runtime/lib64/bionic/libc.so (__libc_init+108)
且慢,讨论了这么久的tag到底存在什么地方?可以肯定的是存在一个全局的table中,由于fd是一个32768以内的整数,因此可以将fd作为table的索引。此外,Josh将这个table设计成可扩容的,初始大小为128,扩容后可以存放下所有fd的tag。这样对于大多数fd创建数量较少的进程,便可以使用较小的table来节省内存。
fdsan自引入之际便全局打开,对于保障系统的安全至关重要。但受限于它的原理,仍然有一些fd的use-after-close和double-close错误无法被检出。它们是:
- 不使用任何封装形式,而只是通过open和close来操作fd。这样即便对同一个fd double-close,由于期望的tag和实际的tag都为0,因此错误无法检出。
- 使用自定义的封装形式来操作fd,而没有在自定义的形式中接入fdsan。无法检出的理由同上。
- 单纯的use-after-close,而并非由double-close引发的use-after-close也无法检出。严格意义上来说,我认为这个工具只能检出fd的double-close。原因是检测代码只插入在close函数里,对于fd的其他操作并无检测。
除了检测fd的错误外,存在全局table中的tag数据在生成tombstone时也会输出,表示每个fd的ownership。unowned
表明该fd的创建未接入fdsan。
open files:
...
fd 47: /data/app/~~TPUHOtvHOT-iL2f4uUrFSg==/com.tencent.mm-FKuS1oTvzRpPMOXX-SE_ZQ==/base.apk (owned by ZipArchive 0xfffd2ae7dd90)
fd 48: /data/app/~~TPUHOtvHOT-iL2f4uUrFSg==/com.tencent.mm-FKuS1oTvzRpPMOXX-SE_ZQ==/split_config.arm64_v8a.apk (owned by ZipArchive 0xfffd2ae82390)
fd 49: /data/app/~~TPUHOtvHOT-iL2f4uUrFSg==/com.tencent.mm-FKuS1oTvzRpPMOXX-SE_ZQ==/split_config.en.apk (owned by ZipArchive 0xfffd2ae83ac0)
fd 50: /data/app/~~TPUHOtvHOT-iL2f4uUrFSg==/com.tencent.mm-FKuS1oTvzRpPMOXX-SE_ZQ==/split_config.hdpi.apk (owned by ZipArchive 0xfffd2ae84e00)
fd 51: /data/data/com.tencent.mm/files/splitcompat/2103/verified-splits/delivery.config.arm64_v8a.apk (owned by ZipArchive 0xfffd2ae80b10)
fd 52: /data/data/com.tencent.mm/files/splitcompat/2103/verified-splits/delivery.apk (owned by ZipArchive 0xfffd2ae803a0)
fd 53: anon_inode:[eventfd] (owned by unique_fd 0xfffd7ae85e84)
fd 54: anon_inode:[eventpoll] (owned by unique_fd 0xfffd7ae85edc)
fd 55: anon_inode:[eventfd] (owned by unique_fd 0xfffd7ae99ce4)
fd 56: anon_inode:[eventpoll] (owned by unique_fd 0xfffd7ae99d3c)
fd 57: /data/app/~~TPUHOtvHOT-iL2f4uUrFSg==/com.tencent.mm-FKuS1oTvzRpPMOXX-SE_ZQ==/base.apk (unowned)
fd 58: /data/app/~~TPUHOtvHOT-iL2f4uUrFSg==/com.tencent.mm-FKuS1oTvzRpPMOXX-SE_ZQ==/split_config.arm64_v8a.apk (unowned)
最后值得注意的一点是,fdsan在vfork得到的子进程里是不工作的。原因是vfork得到的子进程虽然会拷贝父进程的fd,但是使用的地址空间仍然属于父进程。因此fd和存储fd tag的内存产生了错配,在子进程中操作fd不会被父进程感知到,而tag的修改则会直接影响父进程的table。
【参考文档】 android.googlesource.com/platform/bi…
以上就是Android Native fdsan检测工具介绍的详细内容,更多关于Android Native fdsan检测工具的资料请关注编程网其它相关文章!