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(ofollow,noindex">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 70mov rax,QWORD PTR [r13+0x70] ffffffff810b5c25: 48 8b 80 88 00 00 00mov rax,QWORD PTR [rax+0x88] ffffffff810b5c2c: 48 85 c0test rax,rax ffffffff810b5c2f: 74 08je ffffffff810b5c39 ffffffff810b5c31: 4c 89 efmov rdi,r13 ffffffff810b5c34: e8 c7 d3 b4 00call ffffffff81c03000 <__x86_indirect_thunk_rax>
然後,要完全控制RDI:
ffffffff810a4aaa: 48 89 fbmov rbx,rdi ffffffff810a4aad: 48 8b 43 20mov rax,QWORD PTR [rbx+0x20] ffffffff810a4ab1: 48 8b 7f 28mov rdi,QWORD PTR [rdi+0x28] ffffffff810a4ab5: e8 46 e5 b5 00call 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,在啟用時有效平衡了可用性、可靠性與安全性。
根據修復時間表,我們發現從釋出上游修復程式到使用者可使用修復程式之間,存在一個足夠大的視窗期,攻擊者可能會在此期間編寫核心漏洞的利用程式,實現對受漏洞影響主機的攻擊。