В ядре Linux устранена следующая уязвимость:
fs/proc/task_mmu: исправлена самоблокировка огромного tlb в pagemap_scan_pte_hole().
ioctl PAGEMAP_SCAN, запрашивающий PM_SCAN_WP_MATCHING на VMA огромного размера, зависает
вызывающий поток, неуничтожимо, как только сканирование достигает незаполненного
часть диапазона:
do_pagemap_scan()
walk_page_range()
walk_hugetlb_range()
Hugetlb_vma_lock_read() # принимаем блокировку vma для чтения ...
pagemap_scan_pte_hole() # ... ->pte_hole() для дыры
uffd_wp_range()
изменение_защита()
Hugetlb_change_protection()
Hugetlb_vma_lock_write() # ... и блокируем запись на запись
walk_hugetlb_range() удерживает блокировку Hugettlb VMA для чтения по всему
ходить. Текущая запись идет в ->hugetlb_entry(); незаселенный идет
в ->pte_hole(), т.е. pagemap_scan_pte_hole(). Чтобы защитить дыру от записи
этот обработчик вызывает uffd_wp_range(), который на огромных VMA достигает
Hugetlb_change_protection() и использует ту же блокировку vma для записи.
затем поток блокируется в down_write(), ожидая блокировки чтения, которой он сам является
холдинг.
Заполненный путь позволяет избежать этого: pagemap_scan_hugetlb_entry()
защищает запись от записи при блокировке таблицы страниц и никогда не вводит
Hugetlb_change_protection(). Сделайте то же самое с отверстиями. Ошибка в таблице страниц и установите uffd-wp
маркер непосредственно с помощью make_uffd_wp_huge_pte() под блокировкой таблицы страниц,
вместо маршрутизации через uffd_wp_range().
Это та самая последовательность
Hugetlb_change_protection() запускается для незаполненной записи, за вычетом vma
блокировка записи – которую можно пропустить, поскольку совместное использование PMD отключено
VMA uffd-wp (hugetlb_unshare_all_pmds() запускается при регистрации), оставляя
нет ничего, против чего можно было бы сериализовать эту блокировку.
Показать оригинальное описание (EN)
In the Linux kernel, the following vulnerability has been resolved: fs/proc/task_mmu: fix hugetlb self-deadlock in pagemap_scan_pte_hole() A PAGEMAP_SCAN ioctl requesting PM_SCAN_WP_MATCHING on a hugetlb VMA hangs the calling thread, unkillably, as soon as the scan reaches an unpopulated part of the range: do_pagemap_scan() walk_page_range() walk_hugetlb_range() hugetlb_vma_lock_read() # take the vma lock for read ... pagemap_scan_pte_hole() # ... ->pte_hole() for a hole uffd_wp_range() change_protection() hugetlb_change_protection() hugetlb_vma_lock_write() # ... and block taking it for write walk_hugetlb_range() holds the hugetlb vma lock for read across the whole walk. A present entry goes to ->hugetlb_entry(); an unpopulated one goes to ->pte_hole(), i.e. pagemap_scan_pte_hole(). To write-protect the hole that handler calls uffd_wp_range(), which on a hugetlb VMA reaches hugetlb_change_protection() and takes the same vma lock for write. The thread then blocks in down_write() waiting for the read lock it is itself holding. The populated path avoids this: pagemap_scan_hugetlb_entry() write-protects the entry inline under the page-table lock and never enters hugetlb_change_protection(). Do the same for holes. Fault in the page table and install the uffd-wp marker directly with make_uffd_wp_huge_pte() under the page-table lock, rather than routing through uffd_wp_range(). That is the same sequence hugetlb_change_protection() runs for an unpopulated entry, minus the vma write lock -- which is safe to skip because PMD sharing is disabled on uffd-wp VMAs (hugetlb_unshare_all_pmds() runs at registration), leaving nothing for that lock to serialise against.