当前位置: 首页 > 科技观察

分歧还是共存?Android内核安全详解

时间:2023-03-16 15:49:46 科技观察

一、知识背景随着AndyRubin和几个朋友在2003年10月创立了Android公司,后来影响大家的智能设备操作系统公司就此诞生(2005年被谷歌收购)。如今,全球越来越多的智能终端,包括手机、电视、SmartBoxes和IoT、汽车、多媒体设备等都在深度使用Android系统,而Android的底层是Linux内核,这也使得Linux内核的安全性对Android影响很大。但是,Android因为要绕过商业授权的问题,所以开发了类似的解决方案,比如用bionic代替Glibc,用Skia代替Cairo,而不是使用标准内核和GNU/Linux。由于这些原因,Google在Android内核开源方面的理念与Linux内核社区并不完全吻合。这也导致Android对内核做了很多有针对性的修改,却无法集成到上游。这也导致Android内核在安全方面与Linux内核存在一定的差异,侧重点也不一样。在操作系统层面,Android平台不仅提供了Linux内核的安全特性,还提供了安全的进程间通信(IPC)机制,用于运行在不同进程中的应用程序之间的安全通信。这些操作系统级别的安全功能旨在确保即使是本机代码也受制于应用程序沙箱。该系统可防止违规应用程序损害其他应用程序、Android系统或设备本身,无论代码是本机应用程序行为的结果还是利用应用程序漏洞。以下配置设置用作Android内核配置的基础。设置会组织android-base和android-recommended.cfg文件,android-base.cfg和android-recommended.cfg文件位于android-commonkernelrepo:https://android.googlesource.com/内核/通用/。android-base这些选项启用核心Android功能,应在所有设备上启用。android-recommended这些选项启用高级Android功能,设备可以选择启用这些功能。上游Linux内核版本4.8为内核配置片段指定了一个新位置(kernel/configs)。对于基于4.8或更高版本的分支,Android基础和推荐的配置片段位于此目录中。对于基于4.8之前版本的内核分支,配置片段位于android/目录中。2.生成内核配置对于具有最小defconfig的设备,您可以使用以下命令启用选项,生成.config文件,使用此文件保存新的defconfig或编译启用Android功能的新内核:ARCH=archscripts/kconfig/merge_config.shpath/device_defconfigandroid/configs/android-base.cfgandroid/configs/android-recommended.cfg3.Seccomp-BPF和TSYNCSeccomp-BPF是一种内核安全技术,支持创建沙箱来限制进程可以进行的系统调用。TSYNC函数允许在多线程程序中使用Seccomp-BPF。此功能仅限于具有上游seccomp支持的架构:ARM、ARM64、x86和x86_64。ARM-32、X86、X86_64的内核3.10向后移植,确保在Kconfig中启用CONFIG_SECCOMP_FILTER=y(从Android5.0CTS开始验证),然后从AOSP内核/通用:android-3.10商店中挑选以下更改:9499cd23f9d05ba159fac6d55dc35a7f49f9ce76。..a9ba4285aa5722a3b4d84888e78ba8adc0046b281.cfc7e99e9arm64:Add_NR*definitionsforcompatsyscalls(arm64:Add_NR*definitionsforcompatibilitysystemcalls),作者:JPAbgrall2。bf11863arm64:Addauditsupport(arm64:Addauditsupport),作者:AKASHITakahiro3.3e21c0barm64:audit:Addaudithookinsyscall_trace_enter/exit()(arm64:audit:addaudithookinsyscall_trace_enter/exit()),作者:JPAbgrall4.9499cd2syscall_get_arch:removeuselessfunctionarguments(syscall_get_arch:removeuselessfunctionparameters),作者:EricParis5.2a30a43seccomp:createinternalmode-settingfunction(seccomp:createinternalmode-settingfunction),作者:KeesCook6.b8a9cffseccomp:extractcheck/assignmodehelpers(seccomp:extractcheck/assignmodehelper),作者:KeesCook7.8908ddeseccomp:分离模式设置例程(seccomp:splitmodesettingroutine),作者:KeesCook8。e985fd4seccomp:添加KeesCook的“seccomp”系统调用9.9d0ff69sched:moveno_new_privsintonewatomicflags(sched:moveno_new_privsintonewatomicflags),作者:KeesCook10。b6a12bfseccomp:从检查和应用中拆分过滤器准备,作者:KeesCook11.61b6b88seccomp:引入编写器锁定,作者:KeesCook12。c852ef7seccomp:allowmodesettingacrossthreads(seccomp:allowcross-threadmodesetting),作者:KeesCook13。f14a5dbseccomp:implementSECCOMP_FILTER_FLAG_TSYNC(seccomp:implementSECCOMP_FILTER_FLAG_TSYNC),作者:KeesCook14.9ac8600seccomp:ReplaceBUG(!spin_is_locked())withassert_spin_lock(seccomp:replaceBUG(!spin_is_locked())withassert_spin_lock作者:locked1(.Gu)_er_)900e9fdseccomp:fixsyscallnumbersforx86andx86_64(seccomp:fixsyscallnumbersforx86andx86_64),作者:LeeCampbell16。a9ba428ARM:addseccompsyscall(ARM:addseccompsyscall),作者:KeesCook17.4190090ARM:8087/1:ptrace:reloadsyscallnumberaftersecure_computing()check(ARM:8087/1:ptrace:aftersecure_computing()Reload检查后的系统调用号),作者WillDeacon18。abbfed9arm64:ptrace:添加PTRACE_SET_SYSCALL(arm64:ptrace:添加PTRACE_SET_SYSCALL),作者AKASHITakahiro19。feb2843arm64:ptrace:allowtracertoskipasystemcall(arm64:ptrace:allowtracerprocesstoskipasystemcall),作者:AKASHITakahiro20.dab1073asm-generic:addgenericseccomp.hforsecurecomputingmode1(asm-generic:addgenericseccomp.hforsecurecomputingmode1),作者:AKASHITakahiro21.4f12b53addseccompsyscallforcompattask(addforcompatibilitytasksseccomp系统调用),作者:AKASHITakahiro22.7722723arm64:addSIGSYSsiginfoforcompattask(arm64:addSIGSYSsiginfoforcompatibilitytasks),作者:AKASHITakahiro23.210957carm64:addseccompsupport(arm64:addseccompsupport),作者:AKASHITakahiro4.HWAddressSanitizerHardware-assistedAddressSanitizer(HWASan)是一个内存错误检测工具,类似于AddressSanitizer与ASan相比,HWASan使用的内存少得多,因此更适合清理整个系统。HWASan仅适用于Android10及更高版本,并且仅适用于AArch64硬件。具体可以检测以下异常情况:stackandheapbufferoverflow/underflowheapusageafterfreestackusageoutofrangestackusageafterrepeatedfree/errorfreereturnHWASan是基于内存标记的方法,在这种方法中,小随机标记值与指针和内存地址范围都相关联。要使内存访问有效,指针和内存标记必须匹配。HWASan依靠ARMv8特性Top-Byte-Ignore(TBI,也称为虚拟地址标签)将指针标签存储在地址的最高位。HWASan要求Linux内核在系统调用参数中接受标记的指针。在以下上游补丁集中实现了对这一要求的支持:arm64标记地址ABIarm64:取消标记mm用于传递给内核的用户指针:避免在brk()/mmap()/mremap()中创建虚拟地址别名arm64:验证标记从内核线程调用的access_ok()中的地址Android-4.14及更高版本的通用Android内核将这些补丁作为backports提供,但Android10特定分支(例如android-4.14-q)不提供这些补丁作为backports。五、KASANAndroid包含内核地址故障排除程序(KASAN)。KASAN是内核和编译时修改的组合,以形成一个检测系统,可以更轻松地发现错误和分析根本原因。KASAN可以检测内核中多种类型的内存违规。它还检测堆栈、堆和全局变量上的越界读取和写入,并检测释放后使用和双重释放错误。KASAN将编译时内存函数检测与影子内存相结合,以跟踪运行时的内存访问。内核内存空间的八分之一专门用于影子内存,以确定内存访问是否有效。目前支持x86_64和arm64架构。自4.0以来,它一直是上游内核的一部分,并已向后移植到基于Android3.18的内核。KASAN已经在基于内核4.9.2的gcc编译的Android内核上进行了测试。除了KASAN,kcov是另一个对测试非常有用的内核修改。kcov旨在允许在内核中进行覆盖引导的模糊测试。Itmeasurescoverageonsystemcallinputsandisusefulforfuzzingsystemssuchassyzkaller.如需在启用KASAN和kcov的情况下编译内核,请将以下构建标志添加到内核构建配置:CONFIG_KASANCONFIG_KASAN_INLINECONFIG_TEST_KASANCONFIG_KCOVCONFIG_SLUBCONFIG_SLUB_DEBUGCONFIG_CC_OPTIMIZE_FOR_SIZE并移除以下内容:CONFIG_SLUB_DEBUG_ONCONFIG_SLUB_DEBUG_PANIC_ONCONFIG_KASAN_OUTLINECONFIG_KERNEL_LZ4然后照常构建和刷写内核。KASAN内核比原始内核大得多。考虑到这一点,修改任何引导参数和引导加载程序设置(如果适用)。刷新内核后,检查内核启动日志以查看KASAN是否已启用并正在运行。内核将启动并显示KASAN的内存映射信息,例如:...[0.000000]c00Virtualkernelmemorylayout:[0.000000]c00kasan:0xffffff8000000000-0xffffff9000000000(64GB)[0.000000]c00vmalloc:0xffffff9000010000-0xffffffbdbfff0000(182GB)[0.000000]c00vmemmap:0xffffffbdc0000000-0xffffffbfc0000000(8GBmaximum)[0.000000]c000xffffffbdc0000000-0xffffffbdc3f95400(63MBactual)[0.000000]c00PCII/O:0xffffffbffa000000-0xffffffbffb000000(16MB)[0.000000]c00fixed:0xffffffbffbdfd000-0xffffffbffbdff000(8KB)[0.000000]c00modules:0xffffffbffc000000-0xffffffc000000000(64MB)[0.000000]c00memory:0xffffffc000000000-0xffffffc0fe550000(4069MB)[0.000000]c00.init:0xffffffc001d33000-0xffffffc001dce000(620KB)[0.000000]c00.text:0xffffffc000080000-0xffffffc001d32284(29385KB)...错误将如下所示:[18.539668]c31==================================================================[18.547662]c31BUG:KASAN:null-ptr-derefonaddress0000000000000008[18.554689]c31任务swapper/0/1[18.559988]c31CPU:3PID:1Comm:swapper/0Trainted:GW3.18.24-xxx#1[18.569275]c31硬件名称:Android设备[18.577433]c31调用跟踪:[18.580739]c31[]dump_backtrace+0x0/0x2c4[18.586985]c31[]show_stack+0x10/0x19239[818[]dump_stack+0x74/0xc8[18.598792]C31[]android_verity_ctr+0x8cc/0x1024[18.617976]c31[<ffffffc000bcaa2c>]dm_table_add_target+0x3dc/0x50c[18.624832]c31[]dm_run_setup+0x50c/0x678[18.631082]c31[]prepare_namespace+0x44/0x1ac[18.637676]c31[]kernel_init_freeable+0x328/0x364[18.644625]c31[]kernel_init+0x10/0xd8[18.650613]c31==========================================================================六,从Android11开始的Top-bytelgnore从64位进程开始,在内核支持ARMTop-byteIgnore(TBI)的设备上,所有堆分配都在指针顶部字节中设置了一个实现定义的标志,该标志在收集标志期间进行检查,任何修改此标志的应用程序都将被终止。这是支持ARM内存标签扩展(MTE)的未来硬件所必需的。ARM的最高字节忽略功能适用于所有Armv8AArch64硬件中的64位代码。此功能意味着硬件在访问内存时会忽略指针的最高字节。TBI需要兼容的内核才能正确处理从用户空间传递的标记指针。Android4.14(Pixel4)及更高版本的通用内核具有所需的TBI补丁。在内核中支持TBI的设备在进程启动时被动态检测,并且对于所有堆分配,一个依赖于实现的标记被插入到指针的最高字节中。之后,运行检查以确保回收内存时令牌未被截断。ARM的内存标签扩展(MTE)可以帮助解决内存安全问题。MTE通过标记堆栈、堆和全局变量上每个内存分配的第56到第59个地址位来工作。每次访问内存时,硬件和指令集会自动检查是否使用了正确的标志。错误地将信息存储在指针最高字节中的Android应用程序必然会在支持MTE的设备上中断。使用标记指针,在MTE设备可用之前更容易检测和拒绝对指针最高字节的错误使用。7.FlowControlIntegrity(CFI)自2016年以来,Android上约86%的漏洞与内存安全相关。大多数漏洞都被攻击者利用,他们改变了应用程序的正常控制流程,获得了被利用应用程序的全部权限以执行任意恶意活动。控制流完整性(CFI)是一种安全机制,不允许更改已编译二进制文件的原始控制流图,这使得执行此类攻击变得极其困难。LLVM的CFI实现在Android8.1媒体堆栈中启用。CFI在Android9中的更多组件以及内核中启用。系统CFI默认开启,但内核CFI需要手动开启。LLVM的CFI需要使用链接时优化(LTO)进行编译。LTO保留目标文件的LLVM位码表示直到链接时间,以便编译器可以更好地推断可以执行哪些优化。启用LTO可减小最终二进制文件的大小并提高性能,但会增加编译时间。在Android上进行测试时,同时使用LTO和CFI对代码大小和性能开销的影响可以忽略不计;在少数情况下,两者都有所改善。如果要在模块中启用CFI,需要在makefile(如/platform/frameworks/av/cmds/stagefright/Android.mk)中添加如下几行代码:buildprocessLOCAL_SANITIZE:=cfi#启用CFI的诊断模式。诊断模式将在崩溃期间在logcat中输出额外的调试信息,这在开发和测试构建时很有用在所有受支持的Android内核版本中,CONFIG_CFI_CLANG选项启用kCFI,并且在GKI中具有默认设置。启用kCFI后,修复其驱动程序可能存在的任何类型不匹配错误。通过不兼容的函数指针间接调用函数将导致CFI错误。当检测到CFI故障时,内核会输出警告,其中包括调用的函数和导致故障的堆栈跟踪。这可以通过确保函数指针始终与被调用函数的类型相同来解决。为了帮助调试CFI故障,启用CONFIG_CFI_PERMISSIVE,它会输出警告(而不是导致内核崩溃)。八、ShadowCallStackShadowCallStack(SCS)是一种LLVM存根模式,可以将函数的返回地址保存到非叶函数的函数prolog中单独分配的ShadowCallStack中,并在函数epilog中从ShadowCallStack中加载返回地址,从而防止返回地址被覆盖(如堆栈缓冲区溢出)。返回地址也存储在常规堆栈中以与展开程序兼容,但除此之外没有任何用处。这确保了攻击(修改正常堆栈上的返回地址)不会对程序控制流产生任何影响。在aarch64上,这种检测机制使用x18寄存器来引用ShadowCallStack,这意味着对ShadowCallStack的引用不必存储在内存中。因此,实施的运行时避免将ShadowCallStack地址暴露给能够读取任意内存的攻击者。要为内核启用ShadowCallStack,请在内核配置文件中添加如下一行代码:CONFIG_SHADOW_CALL_STACK=y9.总结除了以上内核安全特性,Android还提供了一些关键的安全特性,包括:基于用户的权限模式进程isolation一种实现安全IPC的可扩展机制去除内核中不必要的和可能不安全的部分我们还可以看到,上面的很多功能都是基于LLVM编译器实现的。在实际工作中,LLVM不仅仅是一个Compiler,使用它对优化程序性能和增加安全检测非常有帮助,包括safestack、CFI、LeakSanitizer、MemorySanitizer等程序。随着Android的演进,Android内核的优势在于集成了Linux内核的主线版本。在海量设备的基础上,开发适合自身生态的内核安全解决方案既是挑战也是机遇。我们期待安卓给出一个完美的答案。作者简介:许庆伟:龙蜥社区eBPF技术探索SIG组Maintainer&LinuxKernelSecurityResearcher