В ядре Linux устранена следующая уязвимость:
net/9p: исправлен бесконечный цикл в p9_client_rpc при фатальном сигнале. Когда p9_client_rpc() вызывается с типом P9_TFLUSH и транспортный
не имеет однорангового узла (например, транспорт fd, поддерживаемый каналами без сервера 9p),
фатальный сигнал вызывает бесконечный цикл:
еще раз:
err = io_wait_event_killable(req->wq, ...)
/* SIGKILL пробуждает задачу и возвращает -ERESTARTSYS */
if (err == -ERESTARTSYS && c->status == Подключено &&
тип == P9_TFLUSH) {
ожидание = 1;
Clear_thread_flag (TIF_SIGPENDING);
перейти еще раз;
}
clear_thread_flag() очищает TIF_SIGPENDING перед возвратом к
io_wait_event_killable(). signal_pending_state() проверяет TIF_SIGPENDING,
находит его нулевым, и задача снова переходит в режим сна. Задача может только разбудить
при следующей доставке сигнала, который вызывает signal_wake_up() и устанавливает
TIF_SIGPENDING еще раз.
Когда это происходит, цикл повторяется, очищается
TIF_SIGPENDING и снова переходит в режим ожидания на неопределенный срок. На практике это вызывается функцией coredump_wait(): когда поток в
многопоточный процесс вызывает дамп памяти (например, через SIGSYS из Syscall
User Dispatch), coredump_wait() отправляет SIGKILL всем остальным потокам и
ждет, пока они вызовут mm_release(). Если один из этих потоков заблокирован
в p9_client_rpc() через транспорт fd без однорангового узла он входит в
Цикл P9_TFLUSH и никогда не вызывает mm_release(), поэтому coredump_wait() останавливается.
навсегда:
ИНФО: задача syz.0.18:676 заблокирована более чем на 143 секунды.
Не испорчен 6.12.77+ #1
задача: syz.0.18 состояние: D стек: 27600 pid: 676 tgid: 673 ppid: 630 флаги: 0x00000004
Отслеживание вызова:
<ЗАДАЧА>
context_switch kernel/sched/core.c:5344 [встроенный]
__schedule+0xcb4/0x5d50 ядро/sched/core.c:6724
__schedule_loop ядро/sched/core.c:6801 [встроенный]
расписание+0xe5/0x350 ядро/sched/core.c:6816
Schedule_timeout+0x253/0x290 ядро/время/timer.c:2593
do_wait_for_common kernel/sched/completion.c:95 [встроенный]
__wait_for_common+0x409/0x600 ядро/sched/completion.c:116
wait_for_common kernel/sched/completion.c:127 [встроенный]
wait_for_completion_state+0x1d/0x40 ядро/sched/completion.c:264
coredump_wait fs/coredump.c:448 [встроенный]
do_coredump+0x854/0x4350 фс/coredump.c:629
get_signal+0x1425/0x2730 ядро/signal.c:2903
Arch_do_signal_or_restart+0x81/0x880 Arch/x86/kernel/signal.c:337
exit_to_user_mode_loop kernel/entry/common.c:111 [встроенный]
exit_to_user_mode_prepare include/linux/entry-common.h:328 [встроенный]
__syscall_exit_to_user_mode_work kernel/entry/common.c:207 [встроенный]
syscall_exit_to_user_mode+0xf9/0x160 kernel/entry/common.c:218
do_syscall_64+0x102/0x220 Arch/x86/entry/common.c:84
запись_SYSCALL_64_after_hwframe+0x77/0x7f
</TASK>
Исправлено: проверьте Fatal_signal_pending() перед очисткой TIF_SIGPENDING в
P9_TFLUSH цикл повтора. В этот момент TIF_SIGPENDING все еще установлен, поэтому
Fatal_signal_pending() работает правильно. Если ожидается фатальный сигнал,
перейдите к Recalc_sigpending, чтобы восстановить TIF_SIGPENDING и вернуться
-ERESTARTSYS вызывающему абоненту.
Тот же дефект присутствует в стабильных ядрах начиная с версии 5.4. На тех
ядрах бесконечный цикл прерывается ранее вторым сигналом SIGKILL от
родительский процесс (например, kill_and_wait() повторяет попытку после таймаута),
что приводит к процессу-зомби и задержке завершения работы, а не к
постоянное зависание D-состояния, но основной недостаток тот же. Найден Центром проверки Linux (linuxtesting.org) с помощью Syzkaller.
Показать оригинальное описание (EN)
In the Linux kernel, the following vulnerability has been resolved: net/9p: fix infinite loop in p9_client_rpc on fatal signal When p9_client_rpc() is called with type P9_TFLUSH and the transport has no peer (e.g. fd transport backed by pipes with no 9p server), a fatal signal causes an infinite loop: again: err = io_wait_event_killable(req->wq, ...) /* SIGKILL wakes the task, returns -ERESTARTSYS */ if (err == -ERESTARTSYS && c->status == Connected && type == P9_TFLUSH) { sigpending = 1; clear_thread_flag(TIF_SIGPENDING); goto again; } clear_thread_flag() clears TIF_SIGPENDING before jumping back to io_wait_event_killable(). signal_pending_state() checks TIF_SIGPENDING, finds it zero, and the task goes to sleep again. The task can only wake on the next signal delivery that calls signal_wake_up() and sets TIF_SIGPENDING again. When that happens the loop repeats, clears TIF_SIGPENDING, and sleeps again indefinitely. This is triggered in practice by coredump_wait(): when a thread in a multi-threaded process causes a coredump (e.g. via SIGSYS from Syscall User Dispatch), coredump_wait() sends SIGKILL to all other threads and waits for them to call mm_release(). If one of those threads is blocked in p9_client_rpc() over an fd transport with no peer, it enters the P9_TFLUSH loop and never calls mm_release(), so coredump_wait() stalls forever: INFO: task syz.0.18:676 blocked for more than 143 seconds. Not tainted 6.12.77+ #1 task:syz.0.18 state:D stack:27600 pid:676 tgid:673 ppid:630 flags:0x00000004 Call Trace: <TASK> context_switch kernel/sched/core.c:5344 [inline] __schedule+0xcb4/0x5d50 kernel/sched/core.c:6724 __schedule_loop kernel/sched/core.c:6801 [inline] schedule+0xe5/0x350 kernel/sched/core.c:6816 schedule_timeout+0x253/0x290 kernel/time/timer.c:2593 do_wait_for_common kernel/sched/completion.c:95 [inline] __wait_for_common+0x409/0x600 kernel/sched/completion.c:116 wait_for_common kernel/sched/completion.c:127 [inline] wait_for_completion_state+0x1d/0x40 kernel/sched/completion.c:264 coredump_wait fs/coredump.c:448 [inline] do_coredump+0x854/0x4350 fs/coredump.c:629 get_signal+0x1425/0x2730 kernel/signal.c:2903 arch_do_signal_or_restart+0x81/0x880 arch/x86/kernel/signal.c:337 exit_to_user_mode_loop kernel/entry/common.c:111 [inline] exit_to_user_mode_prepare include/linux/entry-common.h:328 [inline] __syscall_exit_to_user_mode_work kernel/entry/common.c:207 [inline] syscall_exit_to_user_mode+0xf9/0x160 kernel/entry/common.c:218 do_syscall_64+0x102/0x220 arch/x86/entry/common.c:84 entry_SYSCALL_64_after_hwframe+0x77/0x7f </TASK> Fix: check fatal_signal_pending() before clearing TIF_SIGPENDING in the P9_TFLUSH retry loop. At that point TIF_SIGPENDING is still set, so fatal_signal_pending() works correctly. If a fatal signal is pending, jump to recalc_sigpending to restore TIF_SIGPENDING and return -ERESTARTSYS to the caller. The same defect is present in stable kernels back to 5.4. On those kernels the infinite loop is broken earlier by a second SIGKILL from the parent process (e.g. kill_and_wait() retrying after a timeout), resulting in a zombie process and a shutdown delay rather than a permanent D-state hang, but the underlying flaw is the same. Found by Linux Verification Center (linuxtesting.org) with Syzkaller.