В ядре Linux устранена следующая уязвимость:
ceph: исправлен UAF в __kick_flushing_caps() для записи cf, освобождаемой во время разблокировки
list_for_each_entry() выполняет итерацию ci->i_cap_flush_list, но удаляет
i_ceph_lock для отправки сообщений ограничения. В окне разблокировки
handle_cap_flush_ack() может получать i_ceph_lock, отсоединять записи cf
с tid <=lush_tid из списка, отпустите i_ceph_lock и освободите
их через ceph_free_cap_flush() вне любой блокировки. Когда оригинал
поток повторно получает i_ceph_lock, и макрос цикла for продвигается через
cf = list_next_entry(cf, i_list), он разыменовывает cf->i_list.next
на освобожденной памяти.
Хронология гонки:
__kick_flushing_caps() handle_cap_flush_ack()
----------------------- -----------------------
содержит i_ceph_lock <---
выполняет итерацию до cf (tid=10)
готовит сообщение FLUSH
удаляет i_ceph_lock <---
__send_cap() ── FLUSH(tid=10)
MDS отправляет FLUSH_ACK(tid=10)
---> получает i_ceph_lock
cf->tid(10) <=lush_tid(10),
отсоединяет cf от i_cap_flush_list
сбрасывает i_ceph_lock
ceph_free_cap_flush(cf) <- освобождает!
получает i_ceph_lock <---
Продвижение цикла for:
cf = list_next_entry(cf, i_list)
-- UAF при освобождении cf->i_list.next
Cf был только что отправлен самим __kick_flushing_caps через __send_cap(). MDS может ответить FLUSH_ACK достаточно быстро,
handle_cap_flush_ack() освобождает cf до того, как это сможет сделать __kick_flushing_caps
закончить итерацию. Исправьте путем преобразования в ручной цикл while: сохраните следующий указатель.
под i_ceph_lock, прежде чем удалить его, затем используйте сохраненный указатель
после повторного получения, поэтому к потенциально освобожденному cf больше никогда не будет доступа.
Показать оригинальное описание (EN)
In the Linux kernel, the following vulnerability has been resolved: ceph: fix UAF in __kick_flushing_caps() on cf entry freed during unlock list_for_each_entry() iterates ci->i_cap_flush_list but drops i_ceph_lock to send cap messages. During the unlock window, handle_cap_flush_ack() can acquire i_ceph_lock, detach cf entries with tid <= flush_tid from the list, release i_ceph_lock, and free them via ceph_free_cap_flush() outside any lock. When the original thread reacquires i_ceph_lock and the for-loop macro advances via cf = list_next_entry(cf, i_list), it dereferences cf->i_list.next on freed memory. The race timeline: __kick_flushing_caps() handle_cap_flush_ack() ----------------------- ----------------------- holds i_ceph_lock <--- iterates to cf (tid=10) prepares FLUSH message drops i_ceph_lock <--- __send_cap() ── FLUSH(tid=10) MDS sends FLUSH_ACK(tid=10) ---> acquires i_ceph_lock cf->tid(10) <= flush_tid(10), detaches cf from i_cap_flush_list drops i_ceph_lock ceph_free_cap_flush(cf) <- frees it! acquires i_ceph_lock <--- for-loop advances: cf = list_next_entry(cf, i_list) -- UAF on freed cf->i_list.next The cf was just sent by __kick_flushing_caps itself via __send_cap(). The MDS may respond with FLUSH_ACK quickly enough that handle_cap_flush_ack() frees cf before __kick_flushing_caps can finish the iteration. Fix by converting to a manual while loop: save the next pointer under i_ceph_lock before dropping it, then use the saved pointer after reacquiring, so the potentially-freed cf is never accessed again.
Характеристики атаки
Последствия
Строка CVSS v3.1