<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          谷歌給內核提的這個補丁,

          共 8791字,需瀏覽 18分鐘

           ·

          2024-05-28 08:12



          作者:J.Zhou

          Linux內核中內存損壞一直是極難定位但又較為常見的一類問題。在內核中已經有較多的機制來攔截此類問題。比如Kasan/Kfence等等。而內核自5.17版本起又引入了Page Table Check機制,用來檢測某些page計數(shù)異常導致的內存損壞問題。

          一、 為何引入Page Table Check機制:

          Google 的工程師在分析一個進程的dump時,無意間發(fā)現(xiàn)了一頁不屬于該進程的內存。進一步研究發(fā)現(xiàn)了內核自4.14起就存在的內存page引用計數(shù)的bug。為化解此類內存缺陷,Google 提出了一個全新的“頁表檢查”(Page Table Check)解決方案。

          我們看看 Google 的修復patch及問題發(fā)生的原因:


          --- kernel/events/core.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-)
          diff --git a/kernel/events/core.c b/kernel/events/core.cindex 236e7900e3fc..0736508d595b 100644--- a/kernel/events/core.c+++ b/kernel/events/core.c@@ -6110,7 +6110,6 @@ void perf_output_sample(struct perf_output_handle *handle, static u64 perf_virt_to_phys(u64 virt) { u64 phys_addr = 0;- struct page *p = NULL;
          if (!virt) return 0;@@ -6129,14 +6128,15 @@ static u64 perf_virt_to_phys(u64 virt) * If failed, leave phys_addr as 0. */ if (current->mm != NULL) {+ struct page *p;+ pagefault_disable();- if (__get_user_pages_fast(virt, 1, 0, &p) == 1)+ if (__get_user_pages_fast(virt, 1, 0, &p) == 1) { phys_addr = page_to_phys(p) + virt % PAGE_SIZE;+ put_page(p);+ } pagefault_enable(); }-- if (p)- put_page(p); }
          return phys_addr;--


          問題發(fā)生的根因就在于__get_user_pages_fast函數(shù)可能存在先對page指針p賦值了,但是后續(xù)因為某個錯誤直接返回。在此場景下__get_user_pages_fast中是沒有調用get_page來增加引用計數(shù)的,因此后續(xù)的put_page是多余的,會導致引用計數(shù)下溢。修復方式其實比較簡單,只有在__get_user_pages_fast成功時,才put_page。

          此問題是很隱秘的,在內核中存在了很長時間。正因為如此,google才推出了Page Table Check機制,希望在第一時間攔截此類問題。

          二、Page Table Check 機制的實現(xiàn):

          新增一個page_ext記錄當前page的映射是匿名或者文件映射。在每次映射關系改變時,會判斷當前的映射標記,如果出現(xiàn)不允許的情況就會主動panic,保留第一現(xiàn)場。

          具體規(guī)則如下:

          當前映射

          新映射

          映射權限

          規(guī)則

          匿名

          匿名

          允許

          匿名

          匿名

          讀/寫

          禁止

          匿名

          文件

          任何

          禁止

          文件

          匿名

          任何

          禁止

          文件

          文件

          任何

          允許

          我們來看看這個規(guī)則的代碼實現(xiàn)。在有新的映射發(fā)生時,會根據(jù)page現(xiàn)有的file_map_count/anon_map_count的標志來判斷映射是否合法,并且會修改標志值。

          代碼的調用邏輯如下:


          缺頁中斷  → handle_mm_fault    → handle_pte_fault      → do_anonymous_page / do_fault        → set_pte_at          → page_table_check_set            ? 判斷是否合法


          同樣在unmap時,也會調用page_table_check_clear來判斷當前的標志位。


          static void page_table_check_set(struct mm_struct *mm, unsigned long addr,                                 unsigned long pfn, unsigned long pgcnt,                                 bool rw){        struct page_ext *page_ext;        struct page *page;        unsigned long i;        bool anon;
          if (!pfn_valid(pfn)) return;
          page = pfn_to_page(pfn); page_ext = page_ext_get(page); anon = PageAnon(page);
          for (i = 0; i < pgcnt; i++) { struct page_table_check *ptc = get_page_table_check(page_ext);
          if (anon) { BUG_ON(atomic_read(&ptc->file_map_count)); BUG_ON(atomic_inc_return(&ptc->anon_map_count) > 1 && rw); } else { BUG_ON(atomic_read(&ptc->anon_map_count)); BUG_ON(atomic_inc_return(&ptc->file_map_count) < 0); } page_ext = page_ext_next(page_ext); } page_ext_put(page_ext);}


          而在分配 alloc_pages() 和釋放 free_pages_prepare() 內存的時候也會調用__page_table_check_zero,保證當前內存沒有被映射。


          void __page_table_check_zero(struct page *page, unsigned int order){        struct page_ext *page_ext;        unsigned long i;
          page_ext = page_ext_get(page); BUG_ON(!page_ext); for (i = 0; i < (1ul << order); i++) { struct page_table_check *ptc = get_page_table_check(page_ext);
          BUG_ON(atomic_read(&ptc->anon_map_count)); BUG_ON(atomic_read(&ptc->file_map_count)); page_ext = page_ext_next(page_ext); } page_ext_put(page_ext);}


          三、Page Table Check 機制的配置:

          要使用Page Table Check機制,需要在編譯的時候使能PAGE_TABLE_CHECK=y。并且需要在內核的cmdline中增加'page_table_check=on'或者在kconfig中使能CONFIG_PAGE_TABLE_CHECK_ENFORCED=y。

          比如,我們的配置如下:


          CONFIG_PAGE_TABLE_CHECK=yCONFIG_PAGE_TABLE_CHECK_ENFORCED=y


          四、測試驗證:

          前面提到引入page table check的起因是因為異常調用put_page導致的。那么我們人為構建一個多次調用put_page的測試程序來看看page table check如何生效的吧。

          測試程序的代碼如下:


          void test_page_table_check(void){        unsigned long addr;        struct task_struct *task =  find_task_by_vpid(1);        struct vm_area_struct *vma;        struct mm_struct *mm =  task->mm;
          struct page *page;
          addr = mm->mmap_base - PAGE_SIZE; mmap_read_lock(mm); vma = find_vma(mm, addr); page = follow_page(vma, addr, FOLL_GET); put_page(page); mmap_read_unlock(mm);
          put_page(page);}


          首先獲取init進程中mmap分配的一頁page。我們故意在最后多操作了一遍put_page。從而導致page的引用計數(shù)為0,因此會釋放掉此內存,而在釋放內存時,__page_table_check_zero檢查到anon_map_count不為0,因此panic了。

          具體的調用過程可以參考下面的堆棧:


          [  132.032451][2:3990:sh] ------------[ cut here ]------------[  132.032453][2:3990:sh] kernel BUG at mm/page_table_check.c:143![  132.032458][2:3990:sh] Internal error: Oops - BUG: 00000000f2000800 [#1] PREEMPT SMP[  132.032822][2:3990:sh] CPU: 2 PID: 3990 Comm: sh Tainted: G S      W  OE      6.1.25-android14-11-maybe-dirty-qki-consolidate #1[  132.032829][2:3990:sh] pstate: 82400005 (Nzcv daif +PAN -UAO +TCO -DIT -SSBS BTYPE=--)[  132.032833][2:3990:sh] pc : __page_table_check_zero+0xcc/0xdc[  132.032843][2:3990:sh] lr : __page_table_check_zero+0x30/0xdc[  132.032846][2:3990:sh] sp : ffffffc03094bb10[  132.032848][2:3990:sh] x29: ffffffc03094bb10 x28: ffffff88c4a00000 x27: 0000000000000000[  132.032854][2:3990:sh] x26: ffffffe31e256000 x25: ffffffe31e256000 x24: 0000000000000001[  132.032858][2:3990:sh] x23: 0000000000000000 x22: ffffffe31d36b523 x21: ffffffe31d3cac1c[  132.032862][2:3990:sh] x20: ffffff8023a81760 x19: fffffffe01baba40 x18: ffffffe31e18b240[  132.032866][2:3990:sh] x17: 00000000ad6b63b6 x16: 00000000ad6b63b6 x15: ffffffe31c2ad328[  132.032870][2:3990:sh] x14: ffffffe31b7466fc x13: ffffffc030948000 x12: ffffffc03094c000[  132.032874][2:3990:sh] x11: 0000000000000060 x10: ffffffe31e178720 x9 : 0000000000000001[  132.032878][2:3990:sh] x8 : ffffff8023a817b8 x7 : ffffffe31c18cda4 x6 : ffffffe31c1e5ee8[  132.032882][2:3990:sh] x5 : 0000000000000000 x4 : 0000000000000000 x3 : 0000000000000002[  132.032886][2:3990:sh] x2 : 0000000000000000 x1 : ffffffe31d3bf383 x0 : ffffff8023a81760[  132.032890][2:3990:sh] Call trace:[  132.032892][2:3990:sh]  __page_table_check_zero+0xcc/0xdc[  132.032896][2:3990:sh]  free_unref_page_prepare+0x36c/0x42c[  132.032903][2:3990:sh]  free_unref_page+0x58/0x268[  132.032907][2:3990:sh]  __folio_put+0x54/0x80[  132.032917][2:3990:sh]  test_page_table_check+0x114/0x1f8 [mz_stability_test][  132.032930][2:3990:sh]  proc_generate_oops_write+0x960/0xa18 [mz_stability_test][  132.032939][2:3990:sh]  proc_reg_write+0xfc/0x170[  132.032949][2:3990:sh]  vfs_write+0x110/0x2d0[  132.032956][2:3990:sh]  ksys_write+0x80/0xf0[  132.032960][2:3990:sh]  __arm64_sys_write+0x24/0x34[  132.032965][2:3990:sh]  invoke_syscall+0x60/0x124[  132.032975][2:3990:sh]  el0_svc_common+0xcc/0x118[  132.032980][2:3990:sh]  do_el0_svc+0x34/0xb8[  132.032984][2:3990:sh]  el0_svc+0x30/0xb0[  132.032992][2:3990:sh]  el0t_64_sync_handler+0x68/0xb4[  132.032996][2:3990:sh]  el0t_64_sync+0x1a0/0x1a4


          五、小結

          在page table操作時增加校驗,從而檢查是否存在非法共享等人為軟件漏洞,提前發(fā)現(xiàn)問題,確保防止某些內存損壞。在生產環(huán)境和研發(fā)階段,對硬件和工藝原因導致的隨機內存跳變問題,也會有所幫助。

          六、參考

          https://lwn.net/Articles/876264/

          https://lore.kernel.org/all/[email protected]/


          瀏覽 174
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  91麻豆精品成一区二区 | 特黄A级片 | swagArielbb在线播放 | 六月激情婷婷 | 人妻第一页大香蕉 |