DoS уязвимость в Open vSwitch

в 8:04, , рубрики: Без рубрики

Спойлер: Open vSwitch версий меньше 1.11 уязвим перед атакой вида «flow flood», позволяющей злоумышленнику прервать работу сети отправкой относительно небольшого потока пакетов в адрес любой виртуальной машины. Версии 1.11 и старше проблеме не подвержены. Большинство серверов с OVS до сих пор используют OVS 1.4 или 1.9 (LTS-версии). Администраторам таких систем настоятельно рекомендуется обновить систему на более новую версию OVS.

Лирика: Прошло уже больше полутора лет с момента, когда я впервые сумел воспроизвести эту проблему. В рассылке OVS на жалобу сказали, что «в следующих версиях исправят» — и исправили, пол-года спустя. Однако, это исправление не коснулось LTS-версии, а значит, большинство систем, использующих OVS, всё так же уязвимо. Я пытался несколько раз связаться с Citrix'ом (т.к. он использует самую уязвимую версию OVS в составе Xen Server — в тот момент это был мой основной продукт для эксплуатации), но никакой внятной реакции не последовало. Сейчас у администраторов есть возможность устранить проблему малой кровью, так что я решил опубликовать описание очень простой в воспроизведении и крайне запутанной и в диагностике проблемы — проблеме «flow congestion», она же «flow flood attack», она же «странная неведомая фигня, из-за которой всё работает странно». Раньше в комментариях и в рассылках про эту проблему я уже несколько раз писал, но у меня ни разу не хватало пороху полностью описать проблему на русском языке так, чтобы суть проблемы была понятна обычному айтишнику. Исправляюсь.

Следующая строчка hping3 -i u10 virtual.machine.i.p нарушает работоспособность хоста виртуализации, где запущена виртуальная машина. И не только хоста виртуализации — любую систему, работающую на Open vSwitch версий меньше 1.11. Я делаю особый упор на версиях 1.4.3 и 1.9, потому что они являются LTS-версиями и используются чаще всего.

Более суровая версия того же вызова, на этот раз с нарушением правил пользования сетью: hping3 --flood --rand-source virtual.machine.i.p. Соотношение исходящего трафика (~10-60Мбит/с) и (потенциальной) пропускной способности интерфейса жертвы (2x10G, соотношение по доступной полосе атакующий/атакуемый порядка 1:300-1:1000) позволяет говорить именно про уязвимость, а не про традиционную DoS атаку флудом, забивающем каналы аплинков до нерабочего состояния.

Симптомы со стороны хоста виртуализации: неожиданные задержки при открытии соединений, рост потребления CPU процессом ovs-vswitchd до 100%, потеря пакетов для новых или малоактивных сессий. Если используется OVS 1.4, то процесс ovs-vswitchd не только съедает свои 100% CPU, но и начинает подъедать память и делает это со скоростью до 20 мегабайт в минуту, пока к нему не приходит добрый дедушка OOM и не проводит воспитательную беседу.

В отличие от обычных проблем с сетью, запущенные пинги с большой вероятностью «прорвутся», и как только flow с ними попадёт в ядро, ping работать будет без малейших нареканий, а вот новые соединения будет уже не установить. Это сильно затрудняет диагностику проблемы, потому что психологическое «пинги есть — сеть работает» изжить очень сложно. Если к этому добавить, что «прорвавшаяся» ssh-сессия на хост будет продолжать работать так же без особых затруднений, то убедить администратора в том, что проблема с сетью со стороны хоста будет крайне и крайне сложно. Если флуд на интерфейс превышает некоторый порог (~50-80Мб/с), то любая адекватная сетевая активность прекращается. Но коварство описанной проблемы состоит в том, что значительная деградация сервиса происходит при значительно меньших нагрузках, при которых канонические средства диагностики сети рапортуют «всё в порядке».

Пользуясь случаем хочу извиниться перед si14, который о странности с сетью у лабораторных виртуалок говорил, но не мог мне доказать их наличие, а я «видел», что у меня всё хорошо, и потребовалось больше года, пока я, наконец, не только сам заподозрил неладное, но и смог найти однозначный краш-тест.

В случае XenServer ситуация с OVS осложняется тем, что локальный management-трафик так же идёт через бридж Open vSwitch, а все бриджи обслуживаются одним процессом ovs-vswitchd, то есть счастливый администратор даже не сможет посмотреть на эти симптомы — его просто не пустят по ssh. А если пустят — то см выше, про «проблем нет». Поскольку трафик NAS/SAN так же идёт через бридж, то даже после исчезновения причин, виртуальные машины с большой вероятностью останутся в нерабочем состоянии. В этом случае симптомы проблемы — «неожиданное зависание, начавшееся с лагов и потери пакетов».

Описание уязвимости с точки зрения «production manager'а» — любой школьник с 10-50 мегабитным каналом на ADSL может положить всю ферму серверов виртуализации с десятигигабитными портами, если с них хотя бы одна виртуалка «смотрит» интерфейсом в интернет. И правила/ACL внутри виртуалки практически не помогут. Аналогичное может устроить любой пользователь любой виртуалки, даже лишённой доступа в интернет.

Техническое описание проблемы

DoS уязвимость в Open vSwitch
(иллюстрация из поста An overview of Openvswitch implementation)
Причина: Старые версии Open vSwitch не умеют объединять похожие flow и при обнаружении ethernet-кадра, похожего на новый flow всегда отправляют кадр на инспекцию через netlink-сокет процессу ovs-vswitchd, Так как процесс инспекции крайне медлительный (на фоне ошеломительной скорости хэш-таблиц в ядре), а сам ovs-vswitchd является однопоточным и единственным в системе, то производительность сети в условиях появления множества новых flow оказывается ограничена скоростью userspace-приложения (и его хождения в ядро/обратно через netlink). Через короткое время существующие kernel flow оказываются заменены новыми записями и вытеснят существующие (если только те не очень активны), а шансы восстановления новой flow обратно пропорциональны числу конкурентов — то есть флуду. Причём, количество пакетов в добросовестной сессии не играет никакой роли — вся сессия оказывается одной-единственной flow. Именно это служило долгое время «слепым пятном» — любой burst-тест с единичной TCP-сессией радостно сообщал о гигабитах трафика при почти нулевой утилизации процессора.

Kernel flow versus openflow flow

Ключевым является понимание разницы между kernel flow и openflow flow. Openflow flow подразумевает, что flow сопровождается битовой маской, точнее, формат openflow подразумевает указание на интересующие поля. При этом для остальных полей неявно подразумевается «не важно», то есть "*".

Модуль ядра в старых версиях Open vSwitch использует довольно изощрённый механизм работы с правилами, у которого есть только один недостаток: он не поддерживает маски и звёздочки.

Механизм там следующий: у входящего ethernet-кадра выбираются все его заголовки, начиная с L2 и заканчивая L4 (то есть, например, номер порта у TCP), за вычетом уж совсем бесполезных, наподобие «размер окна» у TCP, или «номер сегмента». Все они упаковываются в бинарную структуру, после чего от этой структуры считается хеш. Дальше этот хеш ищется в хеш-таблице правил, и если для правила находится совпадение, используется оно. Если ни одно не совпадает, пакет отправляется на инспекцию более интеллектуальной программе в userspace (ovs-vswitchd), которая присылает обратно новое правило, что делать с такими пакетами. Правила для openflow, а так же наложенные вручную правила через ovs-ofctl обрабатываются именно ovs-vswitchd, а ядерный модуль о них ничего не знает. Особо это касается правила «normal», которое означает «веди себя как коммутатор». Но даже обычный drop всё равно требует инспекции в user-space, потому что drop чаще всего не включает в себя все возможные комбинации входящего/исходящего порта, а, значит, с точки зрения OVS, содежит «звёздочки».

Хеш-таблица даёт фантастическую производительность, которая не зависит от числа правил (то есть 10000 правил будут обрабатываться примерно с такой же скоростью, как и одно).

К сожалению, если заголовки у каждого нового пакета разные, то каждый новый такой пакет уйдёт на инспекцию в userspace. Что медленно и печально.

Оригинальная задумка OVS была в том, что в рамках TCP-сессии все пакеты будут одинаковыми и на всю сессию будет одно правило. К сожалению, злой «хаккир вася», скопипастив строчку выше, сможет это ожидание нарушить.

В новых версиях эту проблему решили с помощью megaflow.

Megaflow

Они появились в Open vSwitch начиная с версии 1.11. Megaflow не относится к openflow, а касается взаимодействия ovs-vswitchd и ядерного модуля. К сожалению, цена за megaflow вполне ощутимая — производительность в ряде случаев падает процентов на 5-20%. К счастью, взамен ovs-vswitchd практически ни при каких условиях не будет отъедать непропорционально много ресурсов.

Что такое megaflow? Очень подробно на Си это излагается тут. Из того, что я понял — появляется понятие «маски», сами маски являются уникальными для всего kernel datapath, а при поиске приходится учитывать их все, то есть сложность поиска становится не o(1), а o(masks). Отсюда и получается некоторое падение производительности. Но на фоне невыразимых тормозов и отказа в обслуживании в случае flow flood'а — это радостная новость. И, пожалуй, единственный выход.

Кроме того, во многих инсталляциях масок будет очень мало, и падение производительности будет неощутимым. Например, неуправляемый режим «простого коммутатора», то есть normal, вероятнее всего, будет содержать одну-единственную маску.

Атака in the wild

При том, что в начале статьи речь шла про «атаку на отказ в обслуживании с hping/nping», реальная проблема куда шире. Если к виртуальной машине по какой-то причине идёт много обращений с множества разных адресов, или просто устанавливается много сессий, то с точки зрения поведения OVS разницы с «атакой» и «высокой нагрузкой» никакой. Я такое наблюдал в реальности, когда виртуальная машина использовалась для раздачи статики для какой-то жутко популярной браузерной игры, содержащей кучу мелких картинок. Игроков было много, картинок было много, они были маленькие. Итого — несколько тысяч новых TCP-сессий в секунду размером 300-500 байт. При этом получался весьма умеренный поток в 15-20 мегабит. И худший случай для старых версий OVS, так как каждая сессия — хождение в юзерспейс.

Дополнительную проблему создаёт то, что у netlink есть буфер, и у сетевых интерфейсов есть буфер, и у OVS есть буфер. Входящие пакеты не просто дропаются — они становятся в очередь, загружая процессор на 100% (да-да, на 100 из 800 доступных). Это приводит к росту latency в обработке новых flow. Причём эту latency крайне трудно диагностировать: лаг есть только на первом пакете, все последующие (в рамках созданной flow) обрабатываются быстро.

Рост latency приводит к второй части проблемы: если пакет из существующей TCP-сессии оказывается в очереди достаточно долго, то запись о такой сессии вытирают из kernel flow table. И пакет опять идёт на инспекцию, на создание новой kernel flow, и это ещё больше добивает OVS до состояния «не дышит и не шевелится».

Заметим, что проблема симметрична по отношению input/output. Это может быть не только «хаккир вася» снаружи, но и «хаккир вася» внутри. Виртуальная машина, рассылающая тысячи новых сессий наружу, вызывает такие же проблемы — и эти проблемы касаются всех, кто оказался рядом с «хаккиром». Если же рассылаются пакеты по соседям по сети, то одинокая виртуальная машина становится способна парализовать или сильно ухудшить качество сервиса для целого множества серверов виртуализации.

Понятно, что для публичного провайдера такое смерти подобно. На предыдущей работе мне пришлось написать довольно уродливый патч, который резал большую часть функционала OVS (т.к. megaflow к тому моменту ещё не было), оставляя в бинарных отпечатках кадра (из которого делают хеш) только минимальный возможный набор параметров. В апстрим такое не берут, но возникшую проблему я решил. А до выхода OVS 1.11 c megaflow оставалось ещё примерно пол-года…

Масштабы проблемы

дистрибутив версия OVS уязвимость
Debian Sqeeze x пакета нет
Debian Wheezy 1.4.2 уязвимая версия + memory leak
Debian Sid 1.9.3 уязвимая версия (вот вам и bleeding edge)
Ubuntu 12.04 1.4 уязвимая версия + memory leak
Ubuntu 14.04 2.0 ура, ура.
XenServer 5.5 1.4.2 уязвимая версия + memory leak
XenServer 6.2 1.4.3 уязвимая версия. До сих пор, даже не 1.9!
RHEL/CentOS 5 x Пакет не предоставляется
RedHat/CentOS 6.5 x Только модуль ядра, без userspace
Fedora 21 2.1 свежайшая!

(информация о версии OVS в вашем любимом дистрибутиве приветствуется и будет добавлена, указывать на версию желательно с ссылкой на сайт дистрибутива)
Поскольку OVS является рекомендованным и штатным бриджем для openstack, причём как opensource-версий, так и многих проприетарных (они всё равно используют OVS для трафика до виртуалки), то можно говорить, что большинство дефолтных установок OpenStack подвержено проблеме. Аналогичная проблема ожидает штатные установки libvirt на базе OVS — так как в дистрибутивах, в основном, проблемные версии.

Заключение

Обновляться, обновляться, обновляться. К величайшему счастью пользователей CentOS 5 и прочих ценителей достижений мамонтов, ядерный модуль OVS 1.11 поддерживает 2.6.18 (но не более 3.8), так что он будет работать на большинстве систем. Для более новых ядер стоит использовать более новые версии OVS — 2.0, или даже 2.1 (только-только вышел в начале мая 2014). Ключевая потенциальная проблема — если при обновлении OVS не обновить модуль ядра, то получившееся работать не будет (хотя утилиты командной строки будут пытаться изображать работоспособность).

Вторая проблема: все версии, в которых уязвимость исправлена, не являются LTS. Это означает, что их их следует обновлять практически сразу же, как выходит более новая, так как поддержка для не-LTS практически отсутствует (а багфиксы идут только в следующую версию).

Ссылки по теме:

P.S. Если кто-то решит проверить «как оно работает» из виртуалки — учтите, что после запуска есть вероятность, что Ctrl-C вы уже не нажмёте. Точнее, нажмёте, но сервер вас не услышит и продолжит слать флуд в интерфейс, и даже перезагрузить такую виртуалку будет сложно — если управление идёт через такой же бридж, то команда на выключение или перезагрузку просто «не дойдёт» до утилит на хосте.

Автор: amarao

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js