导语:自内核版本3.16以来,Linux内存管理中存在一个缓存失效漏洞(CVE-2018-17182),本文是对该漏洞的分析。
概述
自内核版本3.16以来,Linux内存管理中存在一个缓存失效漏洞(CVE-2018-17182),本文是对该漏洞的分析。尽管这一漏洞所在的代码可以被比较强大的沙盒上下文所访问,但在本文中我们介绍了一种在未配置增强安全性的Linux内核环境中利用漏洞的方式(特别是内核为linux-image-4.15.0-34-generic,版本在4.15.0-4.34.37之间的Ubuntu 18.04操作系统)。在文章中,还展现了对内核的配置是如何影响内核漏洞利用难度的。
错误报告和漏洞利用方法已经提交,编号为Issue 1664( https://bugs.chromium.org/p/project-zero/issues/detail?id=1664 )。
该漏洞已经在4.18.9、4.14.71、4.9.128、4.4.157和3.16.58版本中实现修复。
漏洞详情
当用户空间发生页错误时(例如必须根据需要进行分页),Linux内核需要查找包含错误地址的虚拟内存区域(VMA,vm_area_struct),以确定如何处理故障。查找VMA的慢速路径(Slowpath)必须要经历VMA的红黑树。为了进一步提高性能,Linux还有一个快速路径(Fastpath),如果这个VMA最近被使用过,那么就可以不再经过红黑树。
随着时间的推移,快速路径的实现方法发生了变化。从3.15版本( https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=615d6e8756c87149f2d4c1b93d471bca002bd849 )开始,Linux使用具有4个Slot的逐线程VMA缓存,在mm/vmacache.c和include/linux/vmacache.h中实现。每当通过慢速路径成功找到时,vmacache_update()会在数组current->vmacache.vmas的条目中存储指向VMA的指针,从而允许下一次查找时能使用快速路径。
需要注意的是,VMA缓存是逐线程进行的,但VMA会与整个进程相关联。更准确的说,是使用mm_struct结构,从现在开始,这一点将在我们的分析中被忽略,因为它与漏洞无关。因此,当VMA被释放时,必须清空所有线程的VMA高速缓存,否则下一次VMA查找将会找到一个悬垂指针(Dangling Pointer)。但是,由于进程中可以包含许多线程,所以需要遍历所有线程的VMA缓存会出现性能问题。
为了解决这个问题,mm_struct结构和每个线程的vmacache结构都标有序列号。当VMA在vmacache_valid()中进行快速路径查找时,如果它发现current->vmacache.seqnum与current->mm->vmacache_seqnum不匹配,它就会擦除当前线程的VMA缓存的内容,并更新其序列号。
mm_struct和VMA缓存的序列号只有32位长度,这意味着它们可能会有溢出的风险。为了确保当current->mm->vmacache_seqnum实际增加232次时VMA缓存不会认为其有效,vmacache_invalidate()(负责递增current->mm->vmacache_seqnum的函数)中存在一个特殊情况:当current->mm->vmacache_seqnum重新回到0时,它会调用vmacache_flush_all()来擦除与current->mm关联的所有VMA缓存的内容。执行vmacache_flush_all()所消耗的性能非常高,它会遍历主机上的所有线程,检查与mm_struct结构是否相关联,然后在必要时刷新线程的VMA缓存。
在3.16版本中,对其进行了一次优化( https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=6b4ebc3a9078c5b7b8c4cf495a0b1d2d0e0bfe7a ):如果mm_struct结构仅与单个线程相关联,那么vmacache_flush_all()将不会执行任何操作,具体取决于每个VMA高速缓存失效之前是否进行了VMA查找。因此,在单线程进程中,VMA缓存的序列号始终近似于mm_struct的序列号:
/* * Single threaded tasks need not iterate the entire * list of process. We can avoid the flushing as well * since the mm's seqnum was increased and don't have * to worry about other threads' seqnum. Current's * flush will occur upon the next lookup. */ if (atomic_read(&mm->mm_users) == 1) return;
然而,这种优化方式是不正确的,因为它没有考虑如果先前的单线程进程在mm_struct的序列号已经回到0之后立即创建新线程的这种情况。在这种情况下,第一个线程的VMA缓存的序列号仍然是0xffffffff,第二个线程可以再次使得mm_struct的序列号变为0xffffffff。此时,第一个线程的VMA缓存(其中包含着悬垂指针)将再次被视为有效,从而允许在第一个线程的VMA缓存中使用释放的VMA指针。
针对这一漏洞,修复方式( https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/mm/vmacache.c?id=7a9cdebdcc17e426fb5287e4a82db1dfe86339b2 )是将序列号更改为64位,从而使溢出无法发生,同时删去原有的溢出处理逻辑。
漏洞影响
从根本上来说,如果一个进程能够运行足够长的时间,并能够进行一些指定的系统调用,那么它就可以使引用计数器溢出,使用mmap()/munmap()管理内存映射,再使用clone()创建一个线程,从而实现漏洞利用。在MAP_FIXED可用的情况下,让引用计数器溢出大约需要一个小时的时间。这些系统调用不需要任何权限,即使是在seccomp-sandboxed的上下文中也经常允许使用它们,例如Chrome Renderer Sandbox(mmap、munmap、clone)、gVisor沙箱组件以及Docker的seccomp策略。
为了简单起见,我的漏洞利用方法中使用了多种其他的内核接口,因此实际上不仅仅在这些沙箱的内部工作。特别是,它使用/dev/kmsg读取dmesg日志,并使用eBPF数组影响内核中由用户控制的页分配器以及可变单页分配器(Mutable Single-page Allocation)。如果攻击者愿意花费更长的时间,他们可能会避免使用这些接口。
有趣的是,如果内核允许普通用户访问dmesg,Docker的默认配置也不会阻止对dmesg日志的访问。尽管/dev/kmsg并不存在,但由于某种原因,seccomp策略将syslog()系统调用列入了白名单。
对BUG_ON()、WARN_ON_ONCE()和dmesg的分析
第一次UAF(Use-After-Free)发生的函数是vmacache_find()。在首次添加此函数后,在漏洞产生前,它按照以下方式访问VMA缓存:
for (i = 0; i < VMACACHE_SIZE; i++) { struct vm_area_struct *vma = current->vmacache[i]; if (vma && vma->vm_start <= addr && vma->vm_end > addr) { BUG_ON(vma->vm_mm != mm); return vma; } }
当这段代码遇到缓存的VMA,其边界包含提供的地址addr时,它会检查VMA的->vm_mm指针是否与预期的mm_struct相匹配。除非发生了内存安全问题,否则二者始终应该匹配。如果发现不匹配,那么将会使用BUG_ON()终止这一过程。BUG_ON()用于处理内核线程检测到严重问题的情况,这些问题往往无法在上下文中得到有效的解决。在默认的内核配置中,BUG_ON()通常会将带有寄存器转储的回溯内容打印到dmesg日志缓冲区,然后强制终止当前线程。这样一来,有时会影响系统其他部分的正常工作。举例来说,如果崩溃的代码带有一个重要的锁,那么其他任何试图获取该锁的线程都会被死锁,但这一过程中能够成功保持其他的系统处于可用的状态。只有当内核监测到崩溃发生在关键环境中(例如中断处理程序)时,才会导致整个系统崩溃。
相同的处理程序代码用于处理内核代码中的意外崩溃,例如页错误和非白名单地址的一般保护错误。默认情况下,如果可能,内核只会尝试终止有问题的线程。
内核崩溃的处理过程其实是可用性、可靠性和安全性这三方面之间的权衡。系统所有者可能会希望系统能尽可能长时间的运行,即使是系统的某些部分崩溃,也比发生内核崩溃(Kernel Panic)造成重要服务的数据丢失或停止服务要好。同样,系统所有者可能更希望在没有外部调试器的情况下实时调试系统上的内核错误,因为如果在触发错误后整个系统终止了,那么可能会更难以有效调试。
另一方面,如果是攻击者想要利用内核漏洞实现攻击,可能会更想要能在不触发系统重启的前提下实现多次攻击的能力,并且希望能够读取第一次尝试过程中产生的崩溃日志,并使用该信息来进行更复杂的第二次攻击。
内核提供了两个可用于调整此类行为的sysctl,具体要取决于系统所有者所做的权衡:
1、kernel.panic_on_oops将会在BUG_ON()被触发或内核崩溃时,自动导致内核崩溃。可以通过配置变量CONFIG_PANIC_ON_OOPS来配置其初始值。在默认情况下,它在内核中是关闭的,在发布版本中启用它可能是一个坏主意,但在Android中默认启用。
2、kernel.dmesg_restrict控制非root用户是否可以访问dmesg日志,其中包括内核崩溃的寄存器转储内容和堆栈跟踪内容,可以使用配置变量CONFIG_SECURITY_DMESG_RESTRICT对其初始值进行配置。在默认情况下,它在内核中是关闭的,但在Debian中默认启用,Android是依靠SELinux来阻止访问dmesg。
举例来说,Ubuntu无法实现这些功能。
上面的代码片段,在提交后的同一个月内,进行了修改:
for (i = 0; i < VMACACHE_SIZE; i++) { struct vm_area_struct *vma = current->vmacache[i]; - if (vma && vma->vm_start <= addr && vma->vm_end > addr) { - BUG_ON(vma->vm_mm != mm); + if (!vma) + continue; + if (WARN_ON_ONCE(vma->vm_mm != mm)) + break; + if (vma->vm_start <= addr && vma->vm_end > addr) return vma; - } }
目前,Ubuntu等发行版本中使用的都是修改后的代码。
这里的第一个变化,就是将悬垂指针的完整性检查放在了地址比较之前。第二个变化更加有趣,是将BUG_ON()换为了WARN_ON_ONCE()。
WARN_ON_ONCE()会将调试信息输出到dmesg,类似于BUG_ON()打印的内容。与BUG_ON()的区别在于,WARN_ON_ONCE()仅会在第一次触发时打印调试信息,并且继续执行。现在,当内核在VMA缓存快速路径查找过程中检测到悬垂指针时,也就是当它检测到一次UAE时,它只会从快速路径中退出,并回到红黑树的查找,而这一功能还可以正常进行。
这样也符合内核的策略,默认情况下尽可能保证系统的运行。如果由于某种原因,在这里发生了Use-After-Free漏洞,内核可能会启发式的减轻漏洞的影响,并保持该进程正常工作。
但是,在内核发现发生内存损坏后,仅仅打印警告的策略也是存在问题的。正常来讲,当内核注意到内存损坏等安全事件后,理论上应该发生内核崩溃(Kernel Panic)。简单的触发一个WARN()并不是一个好选择,因为WARN()同样也适用于与内核安全性无关的各种事件。因此,在安全相关的地方,WARN_ON()的一些用法已经替换为CHECK_DATA_CORRUPTION(),它允许在内核配置时切换BUG()与WARN()的行为。但是,CHECK_DATA_CORRUPTION()又仅仅适用于链表操作代码和addr_limit_user_check()。在VMA缓存中进行的检查,仍然使用经典的WARN_ON_ONCE()。
考虑到这一点,针对这个函数,进行了第三次重要的调整。然而,这次调整才刚刚进行,并且将在尚未发布的4.19内核中发布。因此,这部分修改的内容不会影响我们在当前环境中的攻击尝试。
for (i = 0; i < VMACACHE_SIZE; i++) { - struct vm_area_struct *vma = current->vmacache.vmas[i]; + struct vm_area_struct *vma = current->vmacache.vmas[idx]; - if (!vma) - continue; - if (WARN_ON_ONCE(vma->vm_mm != mm)) - break; - if (vma->vm_start <= addr && vma->vm_end > addr) { - count_vm_vmacache_event(VMACACHE_FIND_HITS); - return vma; + if (vma) { +#ifdef CONFIG_DEBUG_VM_VMACACHE + if (WARN_ON_ONCE(vma->vm_mm != mm)) + break; +#endif + if (vma->vm_start <= addr && vma->vm_end > addr) { + count_vm_vmacache_event(VMACACHE_FIND_HITS); + return vma; + } } + if (++idx == VMACACHE_SIZE) + idx = 0; }
在经过这一修改之后,除非使用调试选项CONFIG_DEBUG_VM_VMACACHE构建内核,否则将会完全跳过完整性检查。
漏洞利用:增加序列号
要利用该漏洞,必须将序列号增加约233次。因此,用于递增序列号的原语必须要确保效率,这样才能提升漏洞利用的整体效率。
每个系统调用可以产生两个序列号增量,方法如下:创建一个跨越3个页的匿名VMA,然后重复使用带有MAP_FIXED的mmap()将中间页替换为等效的VMA。这样一来,会导致mmap()首先将VMA拆分成3个VMA,然后替换掉中间的VMA,然后再将3个VMA合并到一起,从而导致在合并VMA时删除掉的两个VMA所对应的缓存失效。
漏洞利用:替换VMA
如果我们希望在不释放Slab Backing Page的情况下,进行Use-After-Free攻击,那么需要将目标放在分配器/页分配器上:
1、获取在同一进程中重用的vm_area_struct,该进程将能够使用这一VMA,但是该进程的VMA缓存将被允许包含指向VMA的指针。
2、释放vm_area_struct,使其位于Slab分配器的Freelist上,然后尝试访问它。但是,Ubuntu使用的SLUB分配器至少会用内核地址替换vm_area_struct的前8个字节,这使得VMA缓存查找功能无法返回这一内容,因为条件vma->vm_start <= addr && vma->vm_end > addr不能成立。
3、释放vm_area_struct,使其位于Slab分配的Freelist上,然后在另一个进程中分配它。这将导致命中WARN_ON_ONCE(),因此VMA缓存查找功能不会返回VMA。
4、释放vm_area_struct,使其位于Slab分配的Freelist上,然后从已经和vm_area_struct合并的Slab进行分配。这需要存在一个混合的Slab,而在Ubuntu 18.04上,似乎是不存在的。
因此,要利用此漏洞,就必须将Backing Page释放回页分配器,然后通过某种方式重新对页面进行分配,以允许在其中放置受控制的数据。我们可以使用各种内核接口,例如:
(1)Pipe Page
优点:不会擦除分配的内容,如果splice()可用,就允许使用任意页内偏移量进行写入操作,并且支持页对齐(Page-Aligned)。
缺点:如果没有首先释放页面,就无法进行多次写入和重新分配。
(2)BPF地图
优先:可以反复读取和写入用户空间的内容、支持页对齐。
缺点:会擦除分配的内容。
我们在实际的漏洞利用中,选择的是第二种方法。
漏洞利用:从dmesg泄漏指针
我们在漏洞利用过程中,希望获得以下信息:
mm_struct的地址 Use-After-Free的VMA地址 加载内核代码的地址
至少在Ubuntu 18.04内核中,前两个在WARN_ON_ONCE()出发的寄存器转储内容中可以直接看到,因此可以很容易地从dmesg中提取。mm_struct的地址在RDI中,VMA的地址在RAX中。但是,指令指针是无法直接找到的,因为RIP和栈经过了符号化,通用寄存器中也都不包含指令指针。
在内核回溯中,可以包含多组寄存器:当栈回溯逻辑遇到中断帧时,会生成另一个寄存器转储。由于我们可以通过用户空间地址上的页错误触发WARN_ON_ONCE(),并且用户空间地址上的页错误可能发生在syscall上下文中任何用户控件访问中(通过copy_from_user()/copy_to_user()/…),我们可以从中选择一个包含相关信息的调用节点。事实证明,如果写入eventfd将会触发usercopy,此时R8仍然包含指向eventfd_fops结构的指针。
当漏洞利用开始时,它将VMA替换为零内存,然后针对损坏的VMA缓存触发VMA查找过程,故意触发WARN_ON_ONCE()。这会生成一个警告,如下所示:
[ 3482.271265] WARNING: CPU: 0 PID: 1871 at /build/linux-SlLHxe/linux-4.15.0/mm/vmacache.c:102 vmacache_find+0x9c/0xb0 [...] [ 3482.271298] RIP: 0010:vmacache_find+0x9c/0xb0 [ 3482.271299] RSP: 0018:ffff9e0bc2263c60 EFLAGS: 00010203 [ 3482.271300] RAX: ffff8c7caf1d61a0 RBX: 00007fffffffd000 RCX: 0000000000000002 [ 3482.271301] RDX: 0000000000000002 RSI: 00007fffffffd000 RDI: ffff8c7c214c7380 [ 3482.271301] RBP: ffff9e0bc2263c60 R08: 0000000000000000 R09: 0000000000000000 [ 3482.271302] R10: 0000000000000000 R11: 0000000000000000 R12: ffff8c7c214c7380 [ 3482.271303] R13: ffff9e0bc2263d58 R14: ffff8c7c214c7380 R15: 0000000000000014 [ 3482.271304] FS: 00007f58c7bf6a80(0000) GS:ffff8c7cbfc00000(0000) knlGS:0000000000000000 [ 3482.271305] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 [ 3482.271305] CR2: 00007fffffffd000 CR3: 00000000a143c004 CR4: 00000000003606f0 [ 3482.271308] DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000 [ 3482.271309] DR3: 0000000000000000 DR6: 00000000fffe0ff0 DR7: 0000000000000400 [ 3482.271309] Call Trace: [ 3482.271314] find_vma+0x1b/0x70 [ 3482.271318] __do_page_fault+0x174/0x4d0 [ 3482.271320] do_page_fault+0x2e/0xe0 [ 3482.271323] do_async_page_fault+0x51/0x80 [ 3482.271326] async_page_fault+0x25/0x50 [ 3482.271329] RIP: 0010:copy_user_generic_unrolled+0x86/0xc0 [ 3482.271330] RSP: 0018:ffff9e0bc2263e08 EFLAGS: 00050202 [ 3482.271330] RAX: 00007fffffffd008 RBX: 0000000000000008 RCX: 0000000000000001 [ 3482.271331] RDX: 0000000000000000 RSI: 00007fffffffd000 RDI: ffff9e0bc2263e30 [ 3482.271332] RBP: ffff9e0bc2263e20 R08: ffffffffa7243680 R09: 0000000000000002 [ 3482.271333] R10: ffff8c7bb4497738 R11: 0000000000000000 R12: ffff9e0bc2263e30 [ 3482.271333] R13: ffff8c7bb4497700 R14: ffff8c7cb7a72d80 R15: ffff8c7bb4497700 [ 3482.271337] ? _copy_from_user+0x3e/0x60 [ 3482.271340] eventfd_write+0x74/0x270 [ 3482.271343] ? common_file_perm+0x58/0x160 [ 3482.271345] ? wake_up_q+0x80/0x80 [ 3482.271347] __vfs_write+0x1b/0x40 [ 3482.271348] vfs_write+0xb1/0x1a0 [ 3482.271349] SyS_write+0x55/0xc0 [ 3482.271353] do_syscall_64+0x73/0x130 [ 3482.271355] entry_SYSCALL_64_after_hwframe+0x3d/0xa2 [ 3482.271356] RIP: 0033:0x55a2e8ed76a6 [ 3482.271357] RSP: 002b:00007ffe71367ec8 EFLAGS: 00000202 ORIG_RAX: 0000000000000001 [ 3482.271358] RAX: ffffffffffffffda RBX: 0000000000000000 RCX: 000055a2e8ed76a6 [ 3482.271358] RDX: 0000000000000008 RSI: 00007fffffffd000 RDI: 0000000000000003 [ 3482.271359] RBP: 0000000000000001 R08: 0000000000000000 R09: 0000000000000000 [ 3482.271359] R10: 0000000000000000 R11: 0000000000000202 R12: 00007ffe71367ec8 [ 3482.271360] R13: 00007fffffffd000 R14: 0000000000000009 R15: 0000000000000000 [ 3482.271361] Code: 00 48 8b 84 c8 10 08 00 00 48 85 c0 74 11 48 39 78 40 75 17 48 39 30 77 06 48 39 70 08 77 8d 83 c2 01 83 fa 04 75 ce 31 c0 5d c3 <0f> 0b 31 c0 5d c3 90 90 90 90 90 90 90 90 90 90 90 90 90 90 0f [ 3482.271381] ---[ end trace bf256b6e27ee4552 ]---
此时,可以创建一个虚假的VMA,其中包含正确的mm_struct指针(从RDI的位置泄漏)。它通过引用伪数据结构来填充其他字段(通过使用RAX泄漏的WMA指针,创建指向伪VMA的指针)以及指向内核代码的指针(通过使用页错误异常帧泄漏的R8来绕过KASLR)。
漏洞利用:JOP
其实,有一些非常优雅的漏洞利用方式,比如将伪造的可写VMA覆盖在已经存在的只读页上,或者类似这样的操作。但是,这里的漏洞利用方式只使用经典的跳转方法。
为了触发第二次Use-After-Free,我们对没有页表项的地址执行了写入内存访问。此时,内核的页错误处理程序通过page_fault -> do_page_fault -> __do_page_fault -> handle_mm_fault -> __handle_mm_fault -> handle_pte_fault -> do_fault -> do_shared_fault -> __do_fault进入,此时它会执行间接调用:
static int __do_fault(struct vm_fault *vmf) { struct vm_area_struct *vma = vmf->vma; int ret; ret = vma->vm_ops->fault(vmf);
其中,vma是我们控制的VMA结构,因此我们可以获得对指令指针的控制。R13中包含指向vma的指针。我们使用的JOP链如下,它非常简陋(在完成工作之后就会崩溃),但确实有效。
首先,将VMA指针移动到RDI:
ffffffff810b5c21: 49 8b 45 70 mov rax,QWORD PTR [r13+0x70] ffffffff810b5c25: 48 8b 80 88 00 00 00 mov rax,QWORD PTR [rax+0x88] ffffffff810b5c2c: 48 85 c0 test rax,rax ffffffff810b5c2f: 74 08 je ffffffff810b5c39 ffffffff810b5c31: 4c 89 ef mov rdi,r13 ffffffff810b5c34: e8 c7 d3 b4 00 call ffffffff81c03000 <__x86_indirect_thunk_rax>
然后,要完全控制RDI:
ffffffff810a4aaa: 48 89 fb mov rbx,rdi ffffffff810a4aad: 48 8b 43 20 mov rax,QWORD PTR [rbx+0x20] ffffffff810a4ab1: 48 8b 7f 28 mov rdi,QWORD PTR [rdi+0x28] ffffffff810a4ab5: e8 46 e5 b5 00 call ffffffff81c03000 <__x86_indirect_thunk_rax>
此时,我们可以调用run_cmd(),它使用空格分隔的路径和参数列表作为唯一参数,生成root权限的用户模式帮助程序。这使我们能够以root权限运行二进制文件。(感谢Mark指出,如果已经控制了RDI和RIP,那么就不用进行在CR4中翻转SM*P位这样疯狂的操作,只需要生成一个Usermode Helper)。
启动Usermode Helper后,内核由于页错误而发生崩溃,因为JOP链没有正常终止。但是,由于这只会终止导致错误发生的进程,因此并不重要。
修复时间表
这个漏洞在2018年9月12日被报告。两天后,在上游的内核树中就完成了这一漏洞的修复。与其他厂商相比,这样的修复速度非常快。下游的厂商理论上可以使用修复后的版本或及时安装补丁。
然而,上游的内核中实现修复并不意味着用户系统中的漏洞也已经被修复。这些修复程序的发布流程大致如下:
1、在上游内核中实现修复;
2、该补丁被反向一直到上游支持的稳定内核中;
3、发行版本操作系统将来自上游的稳定内核中的更改,合并到其内核中;
4、用户安装新的发行版本内核。
请注意,由于在进行步骤1之后,这一补丁就转为公开,可能攻击者会借助公开的内容进行漏洞利用,但用户只有在完成全部4个步骤之后,其操作系统才能受到保护。
上游支持的稳定内核4.18、4.14、4.9和4.4的补丁于2018年9月19日发布,在补丁程序发布后的5天,发行版本陆续加入了这一补丁。
上游稳定版本的内核更新非常频繁,以4.14版本为例,下面是最新的长期维护版本:
4.14.72(2018-09-26) 4.14.71(2018-09-19) 4.14.70(2018-09-15) 4.14.69(2018-09-09) 4.14.68(2018-09-05) 4.14.67(2018-08-24) 4.14.66(2018-08-22)
4.9和4.4长期维护内核版本的更新频率非常相似,只有3.16长期维护内核在2018年9月15日(3.16.58)和2018年6月16日(3.16.57)之间没有任何更新。
但是,Linux发行版通常不会频繁的发布内核更新。例如,Debian稳定版基于4.9版本的内核,但截止到9月26日,其内核的最后更新日期仍然为8与21日。同样,Ubuntu 16.04版本的内核最后更新日期为8月27日。Android每月只会发布一次安全更新。因此,当上游稳定内核提供关键安全修复程序后,用户还需要等到几周的时间才能获取到修复程序,特别是在没有公开发布漏洞影响范围的情况下。
这一漏洞于9月18日在OSS-Security邮件列表上公布,并在9月19日分配了CVE编号,然而截至9月26日,Debian和Ubuntu(在16.04和18.04版本中)都还将这个漏洞跟踪为未修复:
https://security-tracker.debian.org/tracker/CVE-2018-17182
https://people.canonical.com/~ubuntu-security/cve/2018/CVE-2018-17182.html
Fedora在9月22日推送了用户更新:
https://bugzilla.redhat.com/show_bug.cgi?id=1631206#c8
结论
这一漏洞展现了内核的配置对于针对内核漏洞编写漏洞利用程序的难度可以产生较大的影响。由此我们知道,随意调整与安全相关的内核配置选项是一个不太稳妥的行为,因为其中的一些配置,例如kernel.dmesg_restrict sysctl,在启用时有效平衡了可用性、可靠性与安全性。
根据修复时间表,我们发现从发布上游修复程序到用户可使用修复程序之间,存在一个足够大的窗口期,攻击者可能会在此期间编写内核漏洞的利用程序,实现对受漏洞影响主机的攻击。