CVE-2026-23394

NONE
Обновлено 25 марта 2026
Linux
Параметр Значение
Поставщик Linux
Публичный эксплойт Нет

В ядре Linux устранена следующая уязвимость: af_unix: отказаться от GC, если вмешался MSG_PEEK. Игорь Ушаков сообщил, что GC очистил очередь приема живой сокет из-за гонки с MSG_PEEK с хорошим воспроизведением. Это та же самая проблема, ранее исправленная коммитом. cbcf01128d0a («af_unix: исправлен сбор мусора против MSG_PEEK»).

После замены GC на текущий алгоритм указанный commit удалил танец блокировки в unix_peek_fds() и вновь представил ту же проблему. Проблема в том, что MSG_PEEK увеличивает счетчик ссылок на файл без взаимодействие с GC. Рассмотрим SCC, содержащий sk-A и sk-B, где sk-A — close()d, но его можно получить через sk-B.

Плохая вещь произойдет, если sk-A будет получен с помощью MSG_PEEK из sk-B и sk-B закрываются(), пока GC проверяет unix_vertex_dead() для ск-А и ск-Б. Поток GC Пользовательский поток --------- ----------- unix_vertex_dead (ск-А) -> правда <------. \ `------ получение (sk-B, MSG_PEEK) признать недействительным!! -> счетчик ссылок на файл sk-A: 1 -> 2 закрыть(sk-B) -> счетчик ссылок на файл sk-B: 2 -> 1 unix_vertex_dead (ск-Б) -> правда Первоначально счетчик ссылок на файл sk-A равен 1 по летному fd в sk-B. Recvq.

GC считает, что sk-A мертв, потому что счетчик ссылок на файл является столько же, сколько его летающих fds. Однако счетчик ссылок на файл sk-A автоматически увеличивается с помощью MSG_PEEK, что делает недействительной предыдущую оценку. В этот момент счетчик ссылок на файл sk-B равен 2; один у открытого FD, и один у бортового ФД в ск-А.

Последующее закрытие() публикует один рефсчет от первого. Наконец, GC ошибочно приходит к выводу, что и sk-A, и sk-B мертвы. Один из вариантов — восстановить танец блокировки в unix_peek_fds(), но мы можем решить эту проблему более элегантно благодаря новому алгоритму.

Дело в том, что проблема не возникает без последующего close() и нам фактически не нужно синхронизировать MSG_PEEK с обнаружение мертвого SCC. Когда возникает проблема, close() и GC касаются одного и того же счетчика ссылок на файл. Если GC видит, что счетчик ссылок уменьшается с помощью close(), он может просто откажитесь от вывоза мусора в СЦК.

Поэтому нам нужно только сигнализировать о гонке во время MSG_PEEK с помощью правильный барьер памяти, чтобы сделать его видимым для GC. Давайте воспользуемся seqcount_t, чтобы уведомить GC о возникновении MSG_PEEK, и позволим это откладывает SCC до следующего запуска. Таким образом, на стороне MSG_PEEK не требуется никакой блокировки, и мы можем избегайте без необходимости налагать штрафы на каждый MSG_PEEK.

Обратите внимание, что мы можем повторить попытку в unix_scc_dead(), если MSG_PEEK обнаружено, но мы не делаем этого, чтобы избежать зависания задачи из-за оскорбительные вызовы MSG_PEEK.

Показать оригинальное описание (EN)

In the Linux kernel, the following vulnerability has been resolved: af_unix: Give up GC if MSG_PEEK intervened. Igor Ushakov reported that GC purged the receive queue of an alive socket due to a race with MSG_PEEK with a nice repro. This is the exact same issue previously fixed by commit cbcf01128d0a ("af_unix: fix garbage collect vs MSG_PEEK"). After GC was replaced with the current algorithm, the cited commit removed the locking dance in unix_peek_fds() and reintroduced the same issue. The problem is that MSG_PEEK bumps a file refcount without interacting with GC. Consider an SCC containing sk-A and sk-B, where sk-A is close()d but can be recv()ed via sk-B. The bad thing happens if sk-A is recv()ed with MSG_PEEK from sk-B and sk-B is close()d while GC is checking unix_vertex_dead() for sk-A and sk-B. GC thread User thread --------- ----------- unix_vertex_dead(sk-A) -> true <------. \ `------ recv(sk-B, MSG_PEEK) invalidate !! -> sk-A's file refcount : 1 -> 2 close(sk-B) -> sk-B's file refcount : 2 -> 1 unix_vertex_dead(sk-B) -> true Initially, sk-A's file refcount is 1 by the inflight fd in sk-B recvq. GC thinks sk-A is dead because the file refcount is the same as the number of its inflight fds. However, sk-A's file refcount is bumped silently by MSG_PEEK, which invalidates the previous evaluation. At this moment, sk-B's file refcount is 2; one by the open fd, and one by the inflight fd in sk-A. The subsequent close() releases one refcount by the former. Finally, GC incorrectly concludes that both sk-A and sk-B are dead. One option is to restore the locking dance in unix_peek_fds(), but we can resolve this more elegantly thanks to the new algorithm. The point is that the issue does not occur without the subsequent close() and we actually do not need to synchronise MSG_PEEK with the dead SCC detection. When the issue occurs, close() and GC touch the same file refcount. If GC sees the refcount being decremented by close(), it can just give up garbage-collecting the SCC. Therefore, we only need to signal the race during MSG_PEEK with a proper memory barrier to make it visible to the GC. Let's use seqcount_t to notify GC when MSG_PEEK occurs and let it defer the SCC to the next run. This way no locking is needed on the MSG_PEEK side, and we can avoid imposing a penalty on every MSG_PEEK unnecessarily. Note that we can retry within unix_scc_dead() if MSG_PEEK is detected, but we do not do so to avoid hung task splat from abusive MSG_PEEK calls.