Оптимізація повторного передавання пакетів у глобальних мережах (WAN) тривалий час залишалася серйозним викликом для архітекторів мережевих стеків. Протягом десятиліть протокол передавання даних Transmission Control Protocol покладався виключно на кумулятивні підтвердження (Cumulative ACKs) згідно зі стандартом RFC 793. Якщо хоча б один пакет у вікні передавання губився в дорозі, відправник був змушений повторно надсилати не лише втрачений фрагмент, а й усі наступні за ним сегменти, що призводило до суттєвого марнування смуги пропускання.
Щоб подолати цю нераціональність, інженерна група IETF стандартизувала механізм TCP Selective Acknowledgment (SACK) у RFC 2018, дозволивши одержувачу чітко вказувати межі окремих розрізнених блоків даних, успішно доставлених до буфера. Проте у 2019 році фахівці з безпеки виявили критичну вразливість у мережевому стеку ядра Linux: CVE-2019-11477 (SACK Panic). Шляхом маніпуляцій з максимальним розміром сегмента (MSS) та надсилання спеціально сформованих блоків SACK зловмисники могли викликати 16-бітне цілочисельне переповнення в чергах сокетних буферів, провокуючи миттєвий крах операційної системи (Kernel Panic).
+-------------------------------------------------------------------------+
| КУМУЛЯТИВНИЙ ACK ПРОТИ СЕЛЕКТИВНОГО ACK (RFC 2018) |
+-------------------------------------------------------------------------+
| СЦЕНАРІЙ: Пакети 1, 2, 4, 5 надійшли; Пакет 3 втрачено в мережі. |
| 1. Кумулятивний ACK (RFC 793): Одержувач шле ACK=3; Відправник повторює |
| передавання пакетів 3, 4, 5 (зайвий трафік). |
| 2. Селективний SACK (RFC 2018): Одержувач шле ACK=3 + SACK [4-6]; |
| Відправник повторює ТІЛЬКИ Пакет 3. Нульові втрати пропускної здатності|
+-------------------------------------------------------------------------+
Еволюція підтверджень TCP: Кумулятивні ACK проти SACK (RFC 2018)
У традиційній схемі кумулятивних підтверджень 32-бітне поле номера підтвердження в заголовку TCP вказує наступний очікуваний порядковий номер байта, засвідчуючи успішне отримання всіх попередніх даних. Коли втрачаються кілька сегментів у різних ділянках потоку, відправник опиняється перед вибором: або повторити передавання першого загубленого сегмента та чекати цілий круговий час (RTT) для оцінки ситуації, або сліпо передати все вікно заново.
RFC 2018 розв'язав цю проблему завдяки розширенню стандартного набору параметрів заголовка TCP (TCP Options). Це дозволило стороні, яка приймає трафік, інформувати відправника про наявність розрізнених острівців даних у своєму вхідному буфері.
+-------------------------------------------------------------+
| СТРУКТУРА ОПЦІЇ TCP SACK (RFC 2018) |
+-------------------------------------------------------------+
| Тип = 5 (1 байт) | Довжина = 8*N + 2 (1 байт) |
+------------------------+------------------------------------+
| Ліва межа 1-го блоку (Left Edge) - 4 байти |
| Права межа 1-го блоку (Right Edge) - 4 байти |
+-------------------------------------------------------------+
| Ліва межа N-го блоку (Left Edge) - 4 байти |
| Права межа N-го блоку (Right Edge) - 4 байти |
+-------------------------------------------------------------+
Анатомія опції SACK: Формати блоків та дескриптори меж
Стандартний заголовок TCP містить максимум 40 байтів для розміщення додаткових опцій (загальна довжина заголовка становить 60 байтів мінус 20 байтів базової структури). Початкове погодження функції відбувається під час тристороннього рукостискання (3-way handshake) за допомогою двобайтової опції SACK-Permitted (тип 4).
Коли стається втрата даних, одержувач формує опцію SACK (тип 5). Кожен блок описується 8 байтами (двома 32-бітними межами):
- Ліва межа (Left Edge): Початковий порядковий номер безперервного отриманого діапазону.
- Права межа (Right Edge): Порядковий номер, наступний за останнім отриманим байтом цього блоку.
Зважаючи на ліміт у 40 байтів, а також використання опції часових міток TCP Timestamps (10 байтів), пакет TCP може містити щонайбільше 3 або 4 блоки SACK одночасно.
Керування невпорядкованими сегментами в ядрі Linux: Черга sk_buff
У підсистемі ядра Linux ключовою одиницею керування мережевими пакетами виступає структура struct sk_buff (зазвичай скорочується як skb). Коли програма надсилає потік даних через TCP-сокет, ядро зберігає вихідні пакети в черзі повторного передавання (write_queue) аж до моменту надходження підтвердження.
+-------------------------------------------------------------------------+
| СТРУКТУРА SK_BUFF ТА ФРАГМЕНТАЦІЯ В ЯДРІ LINUX |
+-------------------------------------------------------------------------+
| struct sk_buff |
| ├── next / prev (вказівники двозв'язного списку черги) |
| ├── len / data_len (загальна та фрагментована довжина корисних даних) |
| └── skb_shared_info |
| ├── gso_segs (лічильник сегментів: 16-бітне ціле без знака, u16) |
| ├── gso_size (розмір максимального фрагмента MSS) |
| └── frags[] (масив сторінок фізичної пам'яті skb_frag_t) |
+-------------------------------------------------------------------------+
Механіка фрагментації сокетного буфера (skb) та використання оперативної пам'яті
Для зниження навантаження на центральний процесор сучасні дистрибутиви Linux застосовують механізми апаратного розвантаження, такі як Generic Segmentation Offload (GSO) і TCP Segmentation Offload (TSO). Замість виділення окремих дескрипторів sk_buff під кожен пакет розміром 1500 байтів, мережевий стек агрегує до 64 КБ даних в одну велику структуру sk_buff, що посилається на масив сторінок пам'яті.
Коли від клієнта надходять блоки SACK, які підтверджують отримання крихітних діапазонів усередині такого агрегованого буфера, ядро змушене викликати підпрограму tcp_fragment(), щоб розщепити великий sk_buff на дрібніші структури, межі яких суворо збігаються зі звітами SACK.
Механізм вразливості SACK Panic (CVE-2019-11477): Переповнення 16-бітного цілого числа
Корінь проблеми CVE-2019-11477 містився у функції злиття фрагментів tcp_shifted_skb() у вихідному коді net/ipv4/tcp_input.c. Метою цієї функції була мінімізація фрагментації пам'яті шляхом об'єднання суміжних блоків SACK у попередні структури sk_buff.
/* Спрощена логіка оновлення лічильника сегментів у ядрі Linux */
static bool tcp_shifted_skb(struct sock *sk, ..., unsigned int pcount, ...)
{
struct sk_buff *prev = tcp_write_queue_prev(sk, skb);
...
/* Додавання кількості зміщених сегментів до попереднього буфера */
skb_shinfo(prev)->gso_segs += pcount;
...
}
У допоміжній структурі skb_shared_info лічильник кількості фрагментів gso_segs було оголошено як 16-бітне беззнакове ціле число типу u16. Граничне значення, яке може вмістити такий тип, дорівнює:
$$2^{16} - 1 = 65,535$$
Якщо віддалений клієнт змушував ядро розбивати та зливати сегменти так, що загальна кількість gso_segs перевищувала 65535, відбувалося арифметичне переповнення з переходом через нуль (wraparound). Лічильник раптово перетворювався на 0 або надзвичайно мале число. При наступних спробах обробки черги функція tcp_fragment() стикалася з невідповідністю довжини буфера та викликала аварійне завершення BUG_ON(), що призводило до миттєвого зависання всієї системи (Kernel Panic).
Роль мікрозначень MSS у масштабуванні виснаження буферів ядра
У звичайних мережах Ethernet максимальний розмір сегмента MSS зазвичай дорівнює 1460 байтам. За такого розміру навіть максимальне вікно TCP не дозволяє досягти 65535 сегментів для провокування переповнення лічильника.
Атака стала реальною через механізм Micro-MSS:
- Зловмисник ініціює TCP-з'єднання з цільовим сервером і під час рукостискання оголошує навмисно занижене значення MSS, наприклад, 48 байтів (мінімально допустимий розмір для IPv4 з опціями TCP).
- Далі клієнт надсилає запит на завантаження великого файлу чи ресурсу з сервера.
- Серверний стек ділить вихідний потік даних на десятки тисяч мікрофрагментів по 48 байтів у своїй черзі
write_queue. - Зловмисник генерує спеціальну серію пакетів SACK, які вибірково підтверджують парні сегменти та ігнорують непарні. Така шахматна структура змушує функцію
tcp_shifted_skb()багаторазово переносити сегменти й накопичувати лічильникgso_segsв одному буфері, долаючи позначку 65535 і викликаючи миттєвий крах ядра.
+-------------------------------------------------------------------------+
| СХЕМА АТАКИ SACK PANIC ЧЕРЕЗ МАНІПУЛЯЦІЮ MSS |
+-------------------------------------------------------------------------+
| [Зловмисник] [Цільовий сервер] |
| | | |
| |---- SYN (MSS=48, SACK-Permitted) -------------------->| |
| |<--- SYN-ACK (MSS=1460, SACK-Permitted) ---------------| |
| |---- ACK --------------------------------------------->| |
| |---- Запит великого обсягу даних --------------------->| |
| |<--- Потік вихідних мікропакетів (по 48 байтів) -------| |
| | | |
| |---- Спеціальні пакети SACK із пропусками ------------>| |
| | (Ядро дробить sk_buff та зливає їх у gso_segs) | |
| | [Переповнення]
| | [Kernel Panic]
+-------------------------------------------------------------------------+
Деградація швидкодії SACK та вичерпання обчислювальних ресурсів (CVE-2019-11478 і CVE-2019-11479)
Одночасно з виявленням SACK Panic дослідники опублікували інформацію про ще дві споріднені вразливості:
- CVE-2019-11478 (SACK Sluggishness): У застарілих версіях ядра сокетна черга повторного передавання базувалася на лінійному двозв'язному списку. Отримання складних і роздроблених блоків SACK змушувало процесор повторно проходити весь список елементів. Загальна складність алгоритму зростала до $O(N^2)$. Генеруючи невеликий потік спеціальних підтверджень, клієнт міг завантажити процесор ядра на 100% у процесах обробки програмних переривань (
ksoftirqd). - CVE-2019-11479: Надмірне навантаження на систему через надсилання фрагментованих пакетів безпосередньо призводило до дефіциту сокетної пам'яті та відмови в обслуговуванні легітимних користувачів.
Асиметричні навантаження на процесор під час сканування непатчених зв'язних списків
Головна небезпека подібних алгоритмічних атак криється в їхній різкій асиметрії. Зловмиснику не потрібно орендувати потужні ботнети: кількох сотень кілобітів трафіку на секунду достатньо, щоб змусити сервер витрачати мільярди тактів процесора на перебирання вказівників пам'яті, блокуючи виконання продуктивних завдань.
Як тунелювання третього рівня та RiceGuard нівелюють вразливості SACK
Використання криптографічних тунелів третього рівня (Layer 3 Tunneling) формує надійний захисний бар'єр, який ізолює мережевий стек ядра від ворожих маніпуляцій зовнішнього Інтернету.
Під час розгортання захищеної мережі VPNRice із протоколом RiceGuard:
- Ізоляція транспорту через інкапсуляцію в UDP: Тунелі RiceGuard інкапсулюють увесь користувацький IP-трафік усередині стандартизованих пакетів UDP, повністю усуваючи взаємодію вразливих TCP-обробників ядра з публічною мережею.
- Термінація TCP на захищених шлюзах: Зовнішні сесії зупиняються на захищених граничних серверах, обладнаних сучасними ядрами із виправленими лічильниками сегментів.
- Примусова нормалізація MSS: Віртуальні адаптери шлюзів блокують будь-які спроби узгодження аномальних мікрозначень MSS, унеможливлюючи створення умов для атаки.
Отримати актуальні клієнтські застосунки для захисту робочих станцій і серверів можна на сторінці завантаження VPN.
+-------------------------------------------------------------------------+
| ЗАХИСТ ВІД АТАК SACK ЧЕРЕЗ ТУНЕЛЬ RICEGUARD |
+-------------------------------------------------------------------------+
| [Публічний Інтернет] |
| │ |
| Шкідливі пакети TCP (Micro-MSS / роздроблені блоки SACK) |
| ▼ |
| [Шлюз безпеки та міжмережевий екран RICEGUARD] |
| │ |
| 1. Блокування пакетів із розміром MSS < 536 байтів. |
| 2. Інкапсуляція очищеного потоку в шифрований тунель UDP. |
| ▼ |
| [Цільовий внутрішній сервер] |
| Буфери sk_buff працюють у штатному режимі без ризику паніки. |
+-------------------------------------------------------------------------+
Зміцнення ядра та інспекція пакетів: Налаштування Sysctl і фільтрація MSS
Для забезпечення безпеки серверів Linux під час роботи у відкритих мережах інженерам рекомендується впровадити такі заходи зміцнення системи:
1. Тимчасове вимкнення SACK у разі неможливості оновлення ядра: Якщо оперативне встановлення виправлень неможливе, обробку SACK можна вимкнути через підсистему sysctl:
sudo sysctl -w net.ipv4.tcp_sack=0
Зверніть увагу: вимкнення SACK повертає систему до класичних кумулятивних підтверджень, що може призвести до зниження швидкості на каналах із помітними втратами пакетів.
2. Фільтрація мікрозначень MSS через міжмережевий екран:
За допомогою iptables або nftables можна блокувати спроби встановлення з'єднань з підозріло малим розміром сегмента:
# Блокування пакетів SYN із параметром MSS менше 536 байтів
sudo iptables -A INPUT -p tcp --tcp-flags SYN,RST SYN -m tcpmss --mss 1:535 -j DROP
3. Встановлення нижньої межі MSS у конфігурації ядра: Сучасні патчі ядра дозволяють встановити системне обмеження мінімального розміру сегмента:
sudo sysctl -w net.ipv4.tcp_min_snd_mss=536
Архітектурне порівняння: Звичайний TCP проти вразливого SACK та захищених тунелів
Порівняльний аналіз демонструє суттєві експлуатаційні відмінності між різними підходами до транспортування даних:
| Експлуатаційний параметр | Звичайний TCP (RFC 793) | Вразливий стек SACK | Захищений тунель RiceGuard |
|---|---|---|---|
| Швидкість відновлення втрат | Низька (повтор усього вікна) | Висока (селективний повтор) | Оптимальна (шар UDP з інкапсуляцією) |
| Ризик аварійного краху (Panic) | Відсутній (немає логіки SACK) | Критичний (CVE-2019-11477) | Нульовий (фільтрація MSS та ізоляція) |
| Навантаження на процесор під атакою | Низьке | Критичне (фрагментація черг) | Мінімальне (фіксовані кільцеві буфери) |
| Використання оперативної пам'яті | Стабільне й прогнозоване | Лавиноподібне (тисячі мікро-skb) | Фіксоване й детерміноване |
| Стійкість до маніпуляцій посередників | Низька (відкриті заголовки) | Середня (можливість скидання опцій) | Повна (криптографічний захист) |
| Продуктивність на лініях із втратами | Падіння до 60% швидкості | Висока ефективність | Максимальна стабільна швидкість |
Сучасне виправлення в ядрі Linux: Черги на червоно-чорних деревах (RB-Tree)
Оприлюднення інформації про SACK Panic стимулювало масштабну перебудову мережевої підсистеми ядра Linux:
- Захист лічильників від переповнення: У функцію
tcp_shifted_skb()додано строгі перевірки, які запобігають обертанню значенняgso_segsчерез максимальну межу. - Перехід на червоно-чорні дерева (Red-Black Trees): Черги невпорядкованих пакетів переведено з лінійних зв'язних списків на деревоподібні структури
struct rb_node. Це знизило обчислювальну складність пошуку й оновлення записів з $O(N)$ до $O(\log N)$, повністю ліквідувавши проблему виснаження процесора (CVE-2019-11478). - Жорстке обмеження мінімального MSS у коді: У функції
tcp_fragment()закріплено програмну заборону на дроблення буферів до розмірів, менших за 48 байтів.
Контрольний список мережевого інженера для захисту інфраструктури TCP SACK
Для гарантування надійної роботи серверних комплексів та хмарних шлюзів фахівцям рекомендується виконувати такий регламент:
- Контроль версій ядра: Переконайтеся, що сервери працюють на захищених гілках ядра (Linux 4.19.52+, 5.1.11+ або дистрибутивах LTS із застосованими патчами безпеки).
- Фільтрація аномальних пакетів на периметрі: Налаштуйте маршрутизатори та балансувальники на відсікання вхідних TCP-з'єднань із параметром MSS нижче 536 байтів.
- Перевірка системних параметрів sysctl: Перевірте наявність та значення змінної
net.ipv4.tcp_min_snd_mss = 536. - Ізоляція віддалених підключень: Маршрутизуйте сесії співробітників через зашифровані тунелі RiceGuard, щоб запобігти безпосередній обробці сирого TCP на внутрішніх хостах.
- Моніторинг програмних переривань: Налаштуйте збір метрик завантаження процесора демонами
ksoftirqd, щоб вчасно виявляти аномалії в обробці черг. - Використання SYN-Cookies: Активуйте параметр
net.ipv4.tcp_syncookies = 1для додаткового захисту від переповнення таблиць сесій.
Поєднання своєчасного оновлення ядра з криптографічною інкапсуляцією трафіку на третьому рівні забезпечує найвищу надійність роботи корпоративної інфраструктури без компромісів у швидкості передавання даних.



