В ядре Linux устранена следующая уязвимость:
tcp: отложить md5sig_info kfree после льготного периода RCU в tcp_connect
Согласование md5+ao в tcp_connect() (net/ipv4/tcp_output.c)
имеет две симметричные ветви:
если (needs_md5) {
tcp_ao_destroy_sock (ск, ложь);
} еще если (needs_ao) {
tcp_clear_md5_list (ск);
kfree(rcu_replace_pointer(tp->md5sig_info, NULL, ...));
}
Обе ветви освобождают объект аутентификации для каждого сокета, пока сокет открыт.
в TCP_SYN_SENT и уже находится в хэше сети (вставлено
inet_hash_connect() в tcp_v4_connect()). Обе ветки доступны
считывателями пути RX softirq, которые загружают соответствующий информационный указатель
через неявный RCU до того, как будет выполнен bh_lock_sock_nested(). Ветка потребности_md5 исправлена в предыдущем патче путем повторного введения
call_rcu() бесплатно в tcp_ao_destroy_sock(): эквивалент для каждого ключа
цикл выполняется внутри tcp_ao_info_free_rcu(), обратного вызова RCU, поэтому
время, когда он освобождает каждый tcp_ao_key, все считыватели программных прерываний, захватившие
контейнер уже завершил rcu_read_unlock().
Ветка потребности_ао не является симметричной. Контейнер
бесплатную можно отложить через kfree_rcu(md5sig, rcu) -- struct
tcp_md5sig_info уже имеет необходимый член rcu
(include/net/tcp.h:1999-2002), а остальная часть дерева уже делает это.
это в путях отката tcp_md5sig_info_add()
(net/ipv4/tcp_ipv4.c:1410, 1436). Но демонтаж каждого ключа выполнен.
с помощью tcp_clear_md5_list() в контексте процесса ДО того, как контейнер
Льготный период RCU: он проходит &md5sig->head и освобождает каждый
tcp_md5sig_key с голым hlist_del + kfree.
Параллельный программный прерывание
читатель в __tcp_md5_do_lookup() / __tcp_md5_do_lookup_exact()
(tcp_ipv4.c:1253, 1298) проходит по тому же списку через
hlist_for_each_entry_rcu() и мчится с этим голым kfree на
Сами ключи — блок использования после освобождения для каждого ключа того же класса.
как ошибка TCP-AO, в том же окне гонки. Исправьте это в двух половинах:
1. Преобразуйте пустую функцию kfree() в tcp_connect() в kfree_rcu(), чтобы
Контейнер md5sig_info присоединяется к остальной части жизненного цикла md5sig.
Подъемник с локальной регулировкой является механическим и необходим, потому что
kfree_rcu() — это макрос, который ожидает lvalue.
2. Сделайте tcp_clear_md5_list() безопасным для RCU, заменив hlist_del +
kfree(ключ) с помощью hlist_del_rcu + kfree_rcu(ключ, rcu). структура
tcp_md5sig_key уже содержит член rcu
(include/net/tcp.h:1995) и tcp_md5_do_del()
(net/ipv4/tcp_ipv4.c:1456) уже использует kfree_rcu, так что это
восстанавливает инвариант жизненного цикла, за ним следует остальная часть файла
вместо того, чтобы вводить одноразовый продукт. Другой вызывающий метод tcp_clear_md5_list() — tcp_md5_destruct_sock().
(net/ipv4/tcp.c:412), который запускается из деструктора sock, когда
сокет уже нехеширован и недоступен; дополнительный льготный период
есть ненужное, но безобидное.
Делаем помощника безоговорочно RCU-safe — это более чистый контракт. Ветка потребности_ао недоступна для используемого репродуктора пользователей. чтобы продемонстрировать знак на стороне АО (репродукция устанавливает оба ключа, но попадает в ветку потребности_md5, поскольку узел подключения соответствует клавиша MD5, а не клавиша AO); однако симметричная раса существует и сопровождающий, касающийся этого кода, не должен думать о какая ветка выходит за рамки RCU, а какая нет. [также спасибо Цихану, который обнаружил, что это происходит с TCP-diag]
Показать оригинальное описание (EN)
In the Linux kernel, the following vulnerability has been resolved: tcp: defer md5sig_info kfree past RCU grace period in tcp_connect The md5+ao reconciliation in tcp_connect() (net/ipv4/tcp_output.c) has two symmetric branches: if (needs_md5) { tcp_ao_destroy_sock(sk, false); } else if (needs_ao) { tcp_clear_md5_list(sk); kfree(rcu_replace_pointer(tp->md5sig_info, NULL, ...)); } Both branches free a per-socket auth-info object while the socket is in TCP_SYN_SENT and is already on the inet ehash (inserted by inet_hash_connect() in tcp_v4_connect()). Both branches are reachable by softirq RX-path readers that load the corresponding info pointer via implicit RCU before bh_lock_sock_nested() is taken. The needs_md5 branch is fixed in the prior patch by re-introducing the call_rcu() free in tcp_ao_destroy_sock(): the equivalent per-key loop runs inside tcp_ao_info_free_rcu(), the RCU callback, so by the time it frees each tcp_ao_key all softirq readers that captured the container have already completed rcu_read_unlock(). The needs_ao branch is not symmetric in the same way. The container free can be deferred via kfree_rcu(md5sig, rcu) -- struct tcp_md5sig_info already has the required rcu member (include/net/tcp.h:1999-2002), and the rest of the tree already does this in the tcp_md5sig_info_add() rollback paths (net/ipv4/tcp_ipv4.c:1410, 1436). But the per-key teardown is done by tcp_clear_md5_list() in process context BEFORE the container's RCU grace period: it walks &md5sig->head and frees each tcp_md5sig_key with bare hlist_del + kfree. A concurrent softirq reader in __tcp_md5_do_lookup() / __tcp_md5_do_lookup_exact() (tcp_ipv4.c:1253, 1298) walks the same list via hlist_for_each_entry_rcu() and races with that bare kfree on the keys themselves -- a per-key slab use-after-free of the same class as the TCP-AO bug, on the same race window. Fix this in two halves: 1. Convert the bare kfree() in tcp_connect() to kfree_rcu() so the md5sig_info container joins the rest of the md5sig lifecycle. The local-variable lift is mechanical and required because kfree_rcu() is a macro that expects an lvalue. 2. Make tcp_clear_md5_list() RCU-safe by replacing hlist_del + kfree(key) with hlist_del_rcu + kfree_rcu(key, rcu). struct tcp_md5sig_key already carries the rcu member (include/net/tcp.h:1995) and tcp_md5_do_del() (net/ipv4/tcp_ipv4.c:1456) already uses kfree_rcu, so this restores the lifecycle invariant the rest of the file follows rather than introducing a one-off. The other caller of tcp_clear_md5_list() is tcp_md5_destruct_sock() (net/ipv4/tcp.c:412), which runs from the sock destructor when the socket is already unhashed and unreachable; the extra grace period there is unnecessary but harmless. Making the helper unconditionally RCU-safe is the cleaner contract. The needs_ao branch is not reachable by the userns reproducer used to demonstrate the AO-side splat (the repro installs both keys but ends up in the needs_md5 branch because the connect peer matches the MD5 key, not the AO key); however the symmetric race exists and a maintainer touching this code should not have to think about which branch escapes RCU and which one does not. [also credits to Qihang, who found that this races with tcp-diag]
Характеристики атаки
Последствия
Строка CVSS v3.1