Блог / Разборы CVE

CVE-2026-98432: одна функция заменена другой дважды, и почему отключение MPTCP не исправляет уязвимость

Собственное исправление ядра для CVE-2026-98432 заменило kfree() на kfree_rcu() в двух деструкторах. Вот почему эта замена одного слова важнее, чем MPTCP.

Аспирантура по информационной безопасности завершенаВнутривузовская защита пройдена · Итоговая защита диссертации впереди

Одна функция заменена другой дважды — и оценка CVSS 9.8

До: kfree(). После: kfree_rcu(). Это всё исправление для CVE-2026-98432, повторённое в двух разных функциях в двух разных файлах. Если сначала прочитать оценку CVSS, уязвимость звучит абстрактно: 9.8, критическая, без привилегий, без взаимодействия с пользователем. Если сначала прочитать сам дифф, абстракция исчезает. Уязвимость — это одно имя функции, заменённое на неверное, ровно в двух местах.

CVE-2026-98432 — это use-after-free в том, как стек IPv4 TCP ядра Linux обрабатывает IP-опции соединения; уязвимость присутствует с коммита 2015 года и теперь исправлена во всех поддерживаемых стабильных ветках.[1] Собственный CNA NVD оценил её в 9.8, AV:N/AC:L/PR:N/UI:N: удалённая эксплуатация, низкая сложность, без привилегий, без взаимодействия с пользователем. Спустя три недели после публикации собственный бюллетень Red Hat до сих пор не называет исправленную сборку, а собственный трекер Ubuntu до сих пор помечает каждый поддерживаемый релиз как уязвимый или находящийся в работе.[2][3] По сообщениям, неопубликованная исследовательская модель Anthropic превратила эту уязвимость в рабочий zero-click путь к root менее чем за час.[4] Подробнее об этом утверждении — ниже, и аккуратно, потому что одна деталь, которая ходит вместе с ним в сети, на самом деле не то, что сказала сама Anthropic.

Я сам прочитал исправляющий коммит, а не его пересказ. git.kernel.org вернул HTTP 403 на прямой запрос, поэтому я прочитал тот же самый коммит, по хешу, из публичного зеркала стабильного дерева, которое Грег Кроа-Хартман ведёт на GitHub.[5] Я не воспроизводил это в лаборатории. Этот материал построен на чтении первичных источников от начала до конца, а не на запуске эксплойта, и это различие важно для всего, что следует далее.

// The actual kfree -> kfree_rcu swap, per the fixing commit (dossier
// S2). Surrounding code is an illustrative reconstruction of shape, not
// a literal excerpt.

net/ipv4/af_inet.c, inet_sock_destruct():
-   kfree(opt)
+   kfree_rcu(opt, rcu)

net/ipv4/tcp_ipv4.c, tcp_v4_reqsk_destructor():
-   kfree(opt)
+   kfree_rcu(opt, rcu)

Что должно давать «освободить позже»

Вот инвариант, на который сетевой код ядра опирается постоянно, не особо задумываясь: читатель внутри rcu_read_lock() имеет право полагать, что память, на которую он смотрит, всё ещё существует, потому что соглашение таково — писатели откладывают реальное освобождение до тех пор, пока каждый читатель заведомо не закончит. Это RCU, read-copy-update, и это ответ ядра на сложную задачу: как дать читателям работать быстро и без блокировок, при этом позволяя писателям удалять данные, без того чтобы читатель когда-либо коснулся уже освобождённой памяти?

TCP request socket — объект ядра для полуоткрытого соединения во время установления связи — может нести IP-опции: данные, заданные атакующим или сетью, прикреплённые к IP-заголовку пакета SYN. Ядро хранит эти опции за указателем (ireq_opt у request socket, inet_opt после повышения до полноценного socket) и читает их позже, при построении SYN-ACK или ACK, внутри ip_options_build(). Это чтение происходит под RCU. Так и должно быть: второй CPU, обрабатывающий повторно переданный SYN того же соединения, может на законных основаниях находиться внутри этой функции в тот самый момент, когда другой CPU разрушает соединение.[1][5]

Два деструктора, один короткий путь, ноль периода отсрочки

Два места в коде нарушили контракт, на котором держится RCU: inet_sock_destruct() в net/ipv4/af_inet.c и tcp_v4_reqsk_destructor() в net/ipv4/tcp_ipv4.c — оба освобождали один и тот же указатель на IP-опции обычным kfree(), немедленно, вместо отложенного через RCU kfree_rcu().[5] Обычный kfree() делает память доступной для повторного использования в тот же момент, когда вызывается: никакого периода отсрочки, никакого ожидания, пока читатели закончат. Любой CPU, всё ещё находящийся внутри ip_options_build() и читающий указатель, который деструктор только что освободил, теперь читает память, которую можно переиспользовать и перезаписать прямо из-под него.

Читающая сторона использовала RCU: дёшево, без блокировок, устойчиво к параллельным освобождениям — при условии, что эти освобождения отложены. Два места на стороне записи не выполнили свою часть этой договорённости. Это несоответствие — ровно тот тип уязвимости, который никогда не увидят WAF, проверка аутентификации или любой контроль на уровне приложения, потому что она целиком живёт внутри управления памятью самого ядра, на глубине двух однострочных вызовов функций в пути деструктора.

// Illustrative timeline of the SYN-retransmit race (one of two trigger
// paths), reconstructed from the fixing commit's own description -- not
// a captured trace, and not exploit code.

CPU A (processing the connection)        CPU B (retransmitted SYN)
----------------------------------      ----------------------------------
child socket created, inherits the
ireq_opt pointer -- not copied
tcp_v4_reqsk_destructor() runs:
  kfree(ireq_opt)   <-- freed now,
  no grace period
                                          still inside rcu_read_lock(),
                                          ip_options_build() dereferences
                                          the same pointer -- already
                                          freed, possibly reused

SYN, который вы повторяете, или request socket, который вы мигрируете

Исправляющий коммит описывает ровно два способа попасть в это окно, и ни один не требует ничего необычного. Первый: tcp_v4_syn_recv_sock() передаёт IP-опции request socket новому дочернему socket без их копирования, поэтому второй CPU, всё ещё обрабатывающий повторно переданный SYN — обычное поведение TCP, — может прочитать эти опции уже после их освобождения.[1][5] Повторные передачи SYN постоянно происходят в реальных сетях, поэтому атакующему не нужно изобретать это условие. Ему нужно только точно подгадать момент.

Второй путь требует ещё одного условия: явно включённой миграции TCP request socket — через net.ipv4.tcp_migrate_req=1 либо через подключённую BPF-программу с SK_REUSEPORT_SELECT_OR_MIGRATE.[5] Это не включено по умолчанию ни в одной конфигурации. Когда это включено, клонированный request socket делит тот же указатель на опции с оригиналом, и тот же деструктор освобождает его прямо из-под того, который его всё ещё использует. Если у вас смешанный стек IPv4/IPv6, здесь важна одна деталь: исправляющий коммит утверждает, что IPv6 не подвержен гонке при повторной передаче, потому что IPv6 дублирует опции request socket, а не делит их. Связанная проблема миграции для IPv6 отложена на отдельный патч.[1][5] Это исправление касается только IPv4.

Сорок восемь минут до указателя, ещё пять — до root

Вот та часть этой истории, которая реально разошлась по сети, и её стоит изложить точно. Собственный исследовательский пост Anthropic, подписанный командой Frontier Red Team, утверждает, что неопубликованная внутренняя исследовательская модель нашла и объединила уязвимости в удалённый zero-click эксплойт против Linux-серверов с запущенным слушателем multipath TCP: без учётных данных, без взаимодействия с пользователем, root по сети.[4][6][7] Пост называет CVE-2026-98432 по номеру. Собственными словами Anthropic: на утечку указателя ядра ушло 48 минут, и ещё пять минут после этого — на получение выполнения команд в шелле.[4]

Одна деталь, которая ходит вместе с этим утверждением по сети, на самом деле в нём не содержится. Кодовое имя «Project Glasswing» не встречается в посте Anthropic нигде. Оно восходит к непроверенному второстепенному источнику, а не к словам самой Anthropic, и я не буду повторять его здесь так, будто Anthropic подтвердила название, которое она никогда не использовала. Что подтверждено независимо: два второстепенных издания сообщают тот же номер CVE, тот же балл CVSS и то же время — 48 минут и 5 минут, — и оба ссылаются на собственный пост Anthropic как на источник.[6][7] Одно из них добавляет фразу, над которой стоит задержаться перед следующим разделом: «публичная запись об этой CVE описывает исправление для IPv4 TCP, а не для multipath TCP, и эти два связаны только постом Anthropic».[7]

Бюллетень Red Hat ни разу не называет MPTCP. Как и сам патч.

Эта фраза — вся суть истории, и именно здесь большинство пересказов ошибается. Прочитайте собственный исправляющий коммит ядра — он описывает два пути срабатывания в обычных терминах IPv4 TCP: повторная передача SYN и миграция request socket, без единого упоминания Multipath TCP.[1][5] Прочитайте собственный бюллетень Red Hat, RHSB-2026-016, — то же самое: «системы, принимающие входящие TCP-соединения» как подверженная популяция, общее описание состояния гонки, никакого MPTCP ни в предпосылках, ни в векторе атаки, ни в рекомендациях по снижению риска.[2] Два первичных источника. Ни один из них не упоминает то, что отключить советует половина интернета.

Это моё собственное сопоставление этих двух источников, а не прямая фраза из какого-либо из них, поэтому скажу прямо: слушатель MPTCP — это один из способов надёжно добраться до этой уязвимости. Это не определяющая предпосылка самой уязвимости. Если конкретный продемонстрированный эксплойт использовал MPTCP как выбранную точку входа — а, судя по предыдущему разделу, это правдоподобная, но всё ещё непроверенная деталь одного заявленного эксплойта, — это говорит об одном практическом способе выиграть гонку, а не о том, что на самом деле требуется самой уязвимости.[4][7] Отключение MPTCP (net.mptcp.enabled=0) убирает этот конкретный тип слушателя как поверхность для срабатывания. Оно не трогает сам use-after-free, который остаётся полностью достижимым через обычную, не связанную с MPTCP, обработку TCP-соединений.

// What the vulnerability actually requires (b), versus what one
// reported demonstration used (c). Don't let (c) stand in for (b).

(b) Real preconditions, per the kernel's own commit and Red Hat's
    advisory -- neither names MPTCP:
      - A listener processing IPv4 TCP SYNs and retransmitted SYNs
        (ordinary behavior, any TCP listener), OR
      - Request-socket migration explicitly enabled
        (net.ipv4.tcp_migrate_req=1, or a BPF
        SK_REUSEPORT_SELECT_OR_MIGRATE program) -- not the default.
      - Precise timing to win the race window.
      - No credentials, no user interaction (PR:N/UI:N).

(c) What one reported demonstration reportedly used to trigger it
    reliably:
      - A reachable multipath TCP (MPTCP) listener.

Disabling MPTCP removes (c). It leaves (b) exactly where it was.

Исправлено в ядре. Но пока не в том ядре, которое запущено у большинства

Исправление уже в основной ветке, и датировано точно: стабильные ветки чисты начиная с 5.10.272, 5.15.223, 6.1.190, 6.6.159, 6.12.113 и 6.18.56, с 7.2.10 и исходным исправлением, помеченным для mainline-ветки 7.3 — всё в точности совпадает с собственными данными поставщика в NVD.[1] Это лёгкая часть этой истории. Совпадает ли ядро в вашей реальной нагрузке с одним из этих номеров — это та часть, которую нужно проверить, а не предположить.

Собственный бюллетень Red Hat, обновлённый последний раз 2026-10-30, оценивает это как «критическое воздействие», но не называет ни исправленной сборки, ни бюллетеня исправлений, ни какого-либо балла CVSS вообще. В нём сказано лишь, что Red Hat «ускоряет выпуск исправлений».[2] Собственный трекер Ubuntu, на момент написания этого материала, помечает каждый поддерживаемый релиз с 16.04 по 26.10 как Vulnerable или Vulnerable, work in progress. Только 14.04, уже вышедший из стандартной поддержки, помечен как чистый.[3] Временная мера от Red Hat, если вы ждёте сборку: правило nftables, отбрасывающее входящие пакеты IPv4 TCP, чьи IP-заголовки несут опции, которое не трогает обычные опции на уровне TCP и прямо названо не заменой патчу.[2]

Ещё одна цифра важна здесь, потому что заголовки сильно напирают на срочность: на момент написания этого материала CVE-2026-98432 отсутствует в каталоге известных эксплуатируемых уязвимостей CISA — проверено двумя независимыми способами, — и ни один из рассмотренных здесь источников не фиксирует подтверждённую эксплуатацию в реальных условиях.[8][9] Это не то же самое, что «можно не торопиться». Балл 9.8 без аутентификации и без взаимодействия с пользователем сам по себе создаёт реальную срочность. Это честная оценка масштаба: критично и не исправлено в большинстве дистрибутивов, но не подтверждено как активно атакуемое.

Unaffected starting at (NVD vendor data, matches the kernel's own
per-branch fixes exactly):
  5.10.272   5.15.223   6.1.190   6.6.159
  6.12.113   6.18.56    7.2.10    (original fix tagged for mainline 7.3)

Distro status, read from each vendor's own tracker as of this writing:
  Red Hat (RHSB-2026-016):  Critical impact. No fixed version or
                             errata named. "Expediting the release
                             of fixes." Last updated 2026-10-30.
  Ubuntu (security tracker): every supported release, 16.04-26.10,
                             Vulnerable or Vulnerable, work in
                             progress. Only 14.04 (out of support)
                             is clear.

Что подтверждено, а что пока лишь утверждение в сети

Вокруг этой CVE ходят ещё две вещи, и я не буду разрешать ни одну из них, потому что запись источников этого не позволяет. По одной версии событий, Red Hat сначала оценила это в 5.9, затем в 8.1, затем в 9.8, и эскалация частично связывается с раскрытием времени эксплойта от Anthropic. Текущий бюллетень Red Hat просто не показывает ни балла, ни вектора, ни какой-либо истории оценок вообще, так что эта конкретная история 5.9–8.1–9.8 ни подтверждается, ни опровергается единственным доступным для проверки первичным источником.[2]

Второй вопрос — действительно ли опубликован рабочий эксплойт-PoC. Одна версия утверждает, что PoC были на GitHub с 2026-10-29; другие говорят, что эксплойт никогда не публиковался. Я не нашёл и не проверил ни один рабочий репозиторий с PoC в ту или другую сторону. Если кто-то уверенно говорит вам, что из этого правда, попросите у него репозиторий, а не утверждение.

Что реально проверить на этой неделе

В том порядке, в котором это действительно важно, а не в том, который лучше смотрится в заголовке.

Patch and verify, in order:
  [ ] Confirm the kernel actually RUNNING, not the changelog: 5.10.272 /
      5.15.223 / 6.1.190 / 6.6.159 / 6.12.113 / 6.18.56 / 7.2.10 or later.
  [ ] If your distro hasn't shipped a fixed build yet, check its own
      tracker directly -- don't trust a blog post's snapshot of it,
      this one included. Apply Red Hat's interim nftables rule in the
      meantime: drop inbound IPv4 TCP with IP header length > 5.
  [ ] Check net.ipv4.tcp_migrate_req. If it's 1, or a BPF
      SK_REUSEPORT_SELECT_OR_MIGRATE program is attached, the second
      trigger path is open regardless of MPTCP.
  [ ] Disable MPTCP if you want to (net.mptcp.enabled=0) -- a
      reasonable attack-surface reduction, but record it as exactly
      that, not as the fix for this CVE.
  [ ] Don't use CISA's KEV list as a signal of how exposed you are
      here. As of this writing this CVE isn't on it, and that's a
      statement about confirmed in-the-wild use, not about your risk.

В бюллетене сказано «use-after-free». В диффе сказано, где именно

Каждый бюллетень про эту CVE повторяет одни и те же три слова: use-after-free, ядро, IPv4. Ни один из них сам по себе не говорит вам, что настоящая ошибка — это выбор kfree() вместо kfree_rcu() в двух деструкторах, или что советы про MPTCP, которые сейчас ходят по сети, выдают выбранную эксплойтом дверь за единственную дверь в здании. Мне об этом рассказал дифф. Бюллетень — нет, и не пытался: это не его задача.

Это именно тот разрыв, который я ищу при обзоре безопасного кода: не говорит ли changelog «исправлено», а действительно ли сам дифф устраняет корневую причину, а не только тот единственный путь, который кто-то случайно нашёл первым. Если вы поддерживаете код на C или C++ с похожим паттерном разделяемого владения и отложенного жизненного цикла — через RCU или иначе, — и хотите, чтобы кто-то прочитал пути деструкторов именно с этим вопросом в голове, это ровно тот вид обзора, который я делаю. Расскажите, что у вас запущено, и мы определим объём работ.

Источники

Нужна такая проверка для вашего продукта?

Аудит безопасности кода

Хотите сами научиться находить такие уязвимости?

Курс аудита безопасности кода, индивидуально
Все статьи

08Контакт

Выпускаете продукт с данными пользователей?

Пришлите короткое описание. Я отвечу в течение трёх дней вопросами или наброском предложения, а стоимость согласуем письменно до начала работ.