Retpoline в ядре Linux против Spectre v2: замеряем деградацию на гипервизорах
Retpoline-патчи ядра Linux вошли в дистрибутивы в 2018 году. Обновили гипервизоры и замерили: 3-5% деградации на вычислительных задачах - приемлемая цена за закрытие Spectre v2.
Retpoline-патчи ядра Linux против Spectre v2 вошли в основные дистрибутивы в 2018 году и стали базовым способом программного митигейшна CVE-2017-5715
В январе мы патчили ядра против Meltdown и первый раз смотрели на Spectre v2. Тогда история закончилась на полуслове: микрокод Intel для Broadwell/Haswell оказался нестабильным, катить его было нельзя, а retpoline упоминался как частичная мера - «помогает, но не закрывает всё». Прошло полгода. За это время retpoline осел в апстриме ядра Linux (с версии 4.15), прошёл в стабильные ветки и добрался до дистрибутивных ядер - RHEL 7, Ubuntu 16.04 LTS, Debian 9. Настало время обновить гипервизоры и посмотреть что получилось.
Что такое retpoline и почему это не просто патч компилятора
Spectre v2 (CVE-2017-5715) атакует branch predictor - блок предсказания переходов в процессоре. Классическое решение через IBRS (Indirect Branch Restricted Speculation) требует микрокода и имеет ощутимый overhead из-за того, что при каждом входе в ядро нужно запрещать непрямые переходы. На Broadwell/Haswell с тем самым нестабильным микрокодом эта история зависла.
Retpoline - компиляторный трюк, предложенный Google. Суть: каждый непрямой переход (indirect call, indirect jmp) заменяется конструкцией, которая заставляет предиктор всегда «думать» что переход пойдёт в бесконечный цикл - и никогда не делает спекулятивный переход туда, куда хочет атакующий. Работает без изменения микрокода. Ядро Linux перекомпилируется с retpoline-поддержкой, и гостевые VM получают защиту через гипервизор.
Проверить что retpoline активен на Linux:
cat /sys/devices/system/cpu/vulnerabilities/spectre_v2
Ожидаемый ответ на свежем ядре с retpoline выглядит примерно так:
Mitigation: Full generic retpoline
Если видите Vulnerable - ядро устаревшее или retpoline не собран.
Как обновляли гипервизоры
У нас несколько KVM-кластеров под разные задачи клиентов. Гипервизоры на Ubuntu 16.04 HWE и RHEL 7. На Ubuntu 16.04 retpoline-ядро пришло с HWE-стеком версии 4.15 (пакет linux-generic-hwe-16.04). На RHEL 7 - backport в 3.10 с соответствующими CVE-патчами.
Порядок обновления стандартный для KVM-инфраструктуры:
- Сначала один хост в каждом кластере. Мигрируем VM на соседние ноды, обновляем ядро, перезагружаем, проверяем что VM поднялись, смотрим на
dmesg- нет ли проблем с IOMMU или сетевыми драйверами. - Затем остальные хосты по одному. Без спешки, с окном между перезагрузками.
- Проверка через Ansible. Инвентаризируем версии ядра по всем хостам после обновления, убеждаемся что нигде не осталось старых версий.
Неожиданных проблем не было. На паре хостов с нестандартными сетевыми картами пришлось поднять версию драйвера - не связано с retpoline, просто HWE-ядро новее.
Деградация производительности: мерим на гостевых VM
Это был главный вопрос - и он предметный. Retpoline защищает гипервизор и гостевые VM, но не бесплатно. Мы прогнали несколько синтетических тестов на гостевых VM до и после обновления хоста.
Что использовали:
- sysbench CPU - нагрузка чистыми вычислениями, минимум syscall'ов
- sysbench fileio - дисковая нагрузка с большим количеством системных вызовов
- pgbench на PostgreSQL 10 - OLTP-нагрузка, умеренное количество syscall'ов
Наблюдения:
sysbench CPU - деградация минимальная, укладывается в погрешность измерения. Retpoline здесь почти не виден: вычислительные задачи без активного ввода-вывода не делают много непрямых переходов через границу ядра.
sysbench fileio - вот тут разница заметна. На тесте с random reads/writes просадка была в диапазоне 3-5% по throughput. Дисковые операции - это системные вызовы, а системные вызовы - это та самая граница, которую retpoline патчирует.
pgbench - похоже на fileio, 3-5% на транзакционной нагрузке с короткими транзакциями. Тяжёлые аналитические запросы просаживаются меньше - те же 1-2% в районе погрешности.
Итого: на вычислительных задачах - практически нет потерь, на IO-интенсивных - 3-5%. Это сопоставимо с тем, что публиковали другие команды в сети. Цифры не сенсационные и не катастрофические.
Что это значит для гостевых VM на KVM
Важный момент: retpoline на гипервизоре - это защита самого гипервизора и изоляция между VM. Гостевая VM с устаревшим ядром внутри себя остаётся уязвимой - retpoline хоста не просачивается внутрь гостя автоматически. Обновлять ядра нужно на всех уровнях: гипервизор и каждая гостевая VM отдельно.
Для клиентских VM под нашим управлением - обновляем через тот же Ansible-пайплайн что и хосты, только по другому инвентарю. Для VM, где у клиента свой доступ и своя ответственность за ОС внутри - информируем и даём инструкцию.
Это та же логика что и при Meltdown: патч ядра закрывает один уровень, но полная защита - это стек изменений на нескольких уровнях одновременно.
Где мы сейчас
Все гипервизоры под нашим управлением обновлены, retpoline активен. По Spectre v2 картина такая: programmatic-митигейшн через retpoline работает, микрокодный IBRS для Broadwell/Haswell Intel в итоге выпустил revised patch, но мы к нему отнеслись осторожно - катим только на платформах, где updated microcode прошёл через вендора или дистрибутив с нормальным QA.
3-5% деградации на IO - приемлемо. Это не повод откладывать обновление и жить с открытой CVE-2017-5715. Организации, у которых гипервизоры до сих пор не обновлены по соображениям «а вдруг производительность упадёт» - рискуют больше, чем теряют.
Если хотите понять реальную картину уязвимостей своей инфраструктуры - аудит как раз про это: инвентаризация, проверка статуса митигейшнов, оценка рисков по слоям.