Большинство уязвимостей живут в коде. Их находят, патчат, закрывают обновлением, и жизнь продолжается. Rowhammer устроена принципиально иначе, и именно это делает её настолько неудобной темой. Это физическое явление в кремниевых ячейках DRAM: при достаточно частом обращении к одной строке памяти электрический заряд перетекает в соседние ряды и переворачивает биты, к которым программа формально не имеет никакого доступа. Память нарушает собственные гарантии изоляции, и происходит это по законам физики, а не по ошибке программиста. Патч прошивки это не закроет. Обновление ядра тоже. Именно поэтому разговор о rowhammer и Linux так неудобен: у этой истории нет чёткого финала, и те, кто утверждают обратное, либо не разобрались в теме, либо сознательно упрощают.

Когда команда Google Project Zero в 2015 году опубликовала рабочий эксплойт, дающий полный контроль над ядром через переворот одного бита в page table entry, без каких-либо привилегий и без единой строчки в логах, стало ясно: речь не об академическом курьёзе. С тех пор ядро Linux обрастало патчами, каждый из которых закрывал одну брешь и немедленно порождал вопрос о следующей. Разбирать эту историю полезно не только потому, что rowhammer актуальна прямо сейчас, но и потому, что она честно показывает, где заканчиваются возможности программной защиты.

Как один перевёрнутый бит открывает всю физическую память

Механику классического эксплойта стоит понять детально, потому что именно она объясняет логику всех последующих патчей. Атакующий начинает с подготовки физической памяти: заполняет её своими записями PTE и методично добивается того, чтобы одна из них оказалась физически соседней с агрессорной строкой DRAM. Управлять физическим расположением страниц из пользовательского пространства можно через массовое выделение и освобождение памяти, вынуждая аллокатор ядра размещать новые страницы в нужных физических позициях. Это называется heap spraying на уровне физической памяти.

Затем запускается сам молоток. Цикл обращений к паре агрессорных строк, окружающих целевую, выглядит примерно так:

/* Упрощённая иллюстрация двойного rowhammer */
for (int i = 0; i < 1000000; i++) {
    *(volatile uint64_t *)aggressor1;  /* обращение к строке выше цели */
    *(volatile uint64_t *)aggressor2;  /* обращение к строке ниже цели  */
    asm volatile("clflush (%0)" :: "r"(aggressor1)); /* вытеснение из кэша */
    asm volatile("clflush (%0)" :: "r"(aggressor2));
    asm volatile("mfence");            /* барьер памяти                  */
}

Инструкция CLFLUSH здесь критична: без вытеснения строки из кэша процессор будет обслуживать повторные обращения из L1/L2, не нагружая DRAM вообще. Rowhammer требует именно обращений к основной памяти, где накапливается заряд. Порядка 150 000 обращений в секунду к одной строке достаточно, чтобы вызвать bit flip на уязвимых модулях DDR3 и ранних DDR4.

Если нужный бит в PTE переворачивается, физический адрес, на который указывает запись, меняется. При удачном раскладе атакующий получает указатель на одну из собственных страничных таблиц, что даёт прямой доступ на запись ко всей физической памяти системы. Chmod и capabilities здесь бессильны: атака обходит всю абстракцию управления доступом на уровне ниже операционной системы. Весь процесс занимает секунды, не требует привилегий и не оставляет следов в логах, потому что ничего незаконного с точки зрения ядра не происходит: просто пользовательский процесс активно читает свою собственную память.

Попытка поймать атаку через счётчики производительности

Первой серьёзной попыткой защиты на уровне ядра стал патч nohammer, пытавшийся использовать аппаратные счётчики производительности (PMU) как детектор аномалий. Логика выглядела убедительно: если атакующий вынужден делать сотни тысяч cache miss-ов подряд к одним и тем же адресам, это статистически должно быть видно. Модуль вводил параметр dram_max_utilization_factor, задающий максимально допустимую частоту промахов LLC (Last Level Cache), при превышении которой поток должен был замедляться или уведомлять систему.

Проблема обнаружилась быстро. PMU считает промахи глобально по всем адресам, а не по конкретным строкам. Любая легитимная нагрузка с последовательным обходом большого массива данных создаёт точно такой же профиль cache miss-ов, что и rowhammer-атака. Потоковая обработка видео, работа с большими матрицами, массовый ввод-вывод буферов, всё это порождало ложные срабатывания, и система начинала тормозить там, где никакой атаки не было.

Кроме того, PMU работает на уровне отдельного логического ядра, а атака, распределённая по восьми потокам одновременно, создаёт шум, который ни одно ядро не воспринимает как угрозу по отдельности. Порог обнаружения приходилось поднимать настолько высоко, что реальная атака проходила незамеченной. Патч не попал в mainline и был отклонён именно по этим причинам. Его история стала хрестоматийным примером того, почему детектировать rowhammer программными средствами принципиально сложно: атака мимикрирует под нормальную работу с памятью, и отличить одно от другого без знания физических адресов обращений невозможно.

KPTI лишает атаку карты местности

Ключевым патчем, реально попавшим в mainline в Linux 4.15, стало Kernel Page Table Isolation. Официальный повод для его создания, уязвимость Meltdown, но для rowhammer KPTI имеет самостоятельное и принципиальное значение. Чтобы понять почему, надо вспомнить, что rowhammer требует точного наведения. Атакующий должен знать физический адрес цели, будь то PTE ядра, страница page table или иная критическая структура. Путь к физическому адресу обычно начинается с утечки виртуального адреса ядерной структуры через side-channel или информационную утечку, а виртуальный адрес затем конвертируется в физический через /proc/self/pagemap (до его закрытия для непривилегированных пользователей) или аналогичные механизмы.

KPTI разрывает эту цепочку в самом начале. До патча таблицы страниц были общими для пользовательского и ядерного пространства: процесс в user mode видел виртуальные адреса ядра, просто без права на доступ. После KPTI каждый процесс получил две независимые PGD:

До KPTI:
  Единая PGD
  ├── User space mappings  (r/w)
  └── Kernel space mappings (r/x, недоступны из user mode)

После KPTI:
  PGD #1 (активна в kernel mode)
  ├── User space mappings
  └── Kernel space mappings (полные)

  PGD #2 (активна в user mode)
  ├── User space mappings
  └── Kernel space mappings (только минимум для входа в ядро)

Переключение между PGD выполняется при каждом переходе между режимами через запись в регистр CR3. Процесс в пользовательском пространстве теперь физически не видит ядерные адреса и не может получить из них ориентир для rowhammer-атаки.

Цена этого решения реальна. Без поддержки PCID каждая запись в CR3 полностью инвалидирует TLB. При высокой частоте системных вызовов, характерной для серверных нагрузок, накладные расходы становятся критическими. На некоторых рабочих нагрузках просадка производительности от KPTI без PCID составляла 30% и выше. Проверить, активны ли KPTI и PCID на конкретной системе, можно так:

# Статус защиты от Meltdown — PTI означает что KPTI активен
cat /sys/devices/system/cpu/vulnerabilities/meltdown

# Полный список активных митигаций ядра
grep . /sys/devices/system/cpu/vulnerabilities/*

# Проверка поддержки PCID процессором (бит 17 в CPUID ECX)
grep -m1 pcid /proc/cpuinfo

# Флаги митигаций в параметрах ядра
cat /proc/cmdline | grep -oE 'pti=[^ ]*|nopti'

Процессоры с поддержкой PCID (большинство Intel начиная с Westmere, AMD начиная с определённых Zen) сохраняют записи TLB при смене адресных пространств, помечая их тегом контекста. Это снижает потери до 2-5% на типичных серверных нагрузках, что уже терпимо. Но на системах без PCID администраторам приходилось выбирать между безопасностью и производительностью, и часть из них выбирала второе, добавляя pti=off в параметры ядра. Это решение, от которого стоит воздерживаться, если только профиль нагрузки не требует абсолютного приоритета производительности на изолированной машине.

G-CATT меняет физическую топологию памяти

Параллельно с KPTI исследователи предложили подход с другой стороны задачи. G-CATT (Guardion-CATT) модифицирует физический аллокатор памяти ядра так, чтобы страницы ядра и пользовательского пространства никогда не оказывались физически соседними в DRAM. Идея строгая: если страницы разных доменов безопасности не являются физическими соседями, bit flip в одном домене не достигает другого вне зависимости от паттерна атаки. G-CATT не пытается предотвратить сам переворот бита, а делает его последствия локальными.

Реализация требует отслеживания физической топологии при аллокации: между последней страницей ядерного домена и первой страницей пользовательского домена должна находиться защитная полоса, Guard Row, достаточной ширины, чтобы rowhammer-эффект не перешагнул границу. Ширина полосы подбирается исходя из характеристик конкретных DRAM-чипов.

Идея элегантная, но у неё есть принципиальная слабость, которая и объяснила, почему G-CATT не попал в mainline. Атака через "memory ambush" позволяет разместить специальные буферы, находящиеся одновременно в ядерном и пользовательском владении, физически рядом с целевыми структурами, не истощая системную память. G-CATT строился на допущении, что атакующий обязан занять почти всю доступную память для управления физическим расположением страниц. Memory ambush это допущение опровергла, показав, что при использовании разделяемой памяти (shared memory, mmap файлов) можно управлять физическим соседством без истощения ресурсов. Тем не менее принципы изоляции доменов памяти на уровне аллокатора активно обсуждаются в lkml и влияют на текущие решения по управлению физической памятью в ядре.

Blacksmith и RowPress показывают, почему аппаратные заслоны тоже не успевают

Производители DRAM ответили на rowhammer механизмом Target Row Refresh. При обнаружении частых обращений к агрессорной строке контроллер памяти превентивно обновляет соседние ряды, не давая зарядам дойти до порога переворота. Подход работал против известных атак и давал индустрии право утверждать, что DDR4 надёжно защищена. Это утверждение оказалось преждевременным.

В 2022 году исследователи ETH Zurich представили Blacksmith. Вместо равномерного молотка по двум агрессорным строкам, на который был настроен TRR, Blacksmith использует асимметричные, нерегулярные паттерны, варьируя частоту, фазу и амплитуду обращений к разным строкам. TRR отслеживает количество активаций строк и принимает решение о превентивном обновлении на основе счётчиков. Нерегулярные паттерны не достигают пороговых значений счётчиков ни по одной из отслеживаемых строк, хотя суммарный эффект утечки заряда остаётся. Blacksmith обошёл TRR на всех 40 протестированных DDR4-модулях разных производителей и вызвал в среднем в 87 раз больше bit flip, чем предыдущие методы. Это не улучшение старой атаки, это доказательство того, что TRR решает задачу защиты от конкретных паттернов, а не от физического явления как такового.

В конце 2023 года появился RowPress, и здесь атакующие воспользовались самой природой современных контроллеров памяти. Вместо быстрого переключения между строками RowPress удерживает строку открытой на продолжительное время, что усиливает утечку заряда при значительно меньшем числе активаций. Ирония в том, что контроллеры памяти, оптимизированные под производительность, намеренно держат строки открытыми как можно дольше, избегая лишних циклов закрытия и повторного открытия. RowPress эксплуатирует именно эту оптимизацию. Атака требует на порядок меньше обращений, чем классический rowhammer, и опускается ниже любых порогов обнаружения TRR, которые настроены на высокую частоту активаций.

Что реально работает и где заканчиваются возможности программной защиты

Честный ответ звучит неудобно: полностью закрыть rowhammer программными средствами невозможно. Ключевые разработчики ядра этого не отрицают. Аппаратная природа уязвимости означает, что каждый новый программный барьер рано или поздно обходится через изменение паттерна или вектора атаки. Тем не менее совокупность мер существенно поднимает практическую планку, и для production-систем проверить текущее состояние несложно:

# Полный статус всех аппаратных митигаций ядра
grep . /sys/devices/system/cpu/vulnerabilities/*

# Тип коррекции ошибок памяти (ECC или нет)
dmidecode -t memory | grep -A3 "Memory Device" | grep "Error Correction"

# Наличие и состояние EDAC (Error Detection And Correction) подсистемы
edac-util -s 4
ls /sys/bus/platform/drivers/i7core_edac/ 2>/dev/null || echo "EDAC не загружен"

# Статистика исправленных ошибок памяти (если ECC активно)
edac-util -r mc

ECC-память остаётся наиболее надёжным барьером на уровне железа. Она не устраняет bit flip, но обнаруживает и исправляет однобитовые ошибки до того, как они повлекут последствия. Успешный эксплойт в среде с ECC требует вызвать переворот одного и того же бита несколько раз подряд быстрее, чем ECC успеет его исправить. Это на порядки усложняет задачу, и на серверных платформах с ECC практических эксплойтов пока продемонстрировано не было. Развернуть rowhammer-атаку на сервере с ECC теоретически возможно, но это уже другая категория сложности.

DDR5 меняет уравнение на аппаратном уровне. TRR в DDR5 заменён системой Per-Bank Refresh Management (PBRM), которая отслеживает активации на уровне всего банка памяти и выдаёт превентивные обновления при достижении настраиваемого порога. Пространство для манёвра с нестандартными паттернами здесь значительно уже. Масштабируемый фаззинг DDR5 затруднён, хотя исследователи уже демонстрировали единичные bit flip и на DDR5. Это не победа над rowhammer, но это другое уравнение сложности для атакующего.

Для ядра Linux работа продолжается. KPTI убирает карту местности у атакующего, не давая ему узнать физические адреса ядерных структур. Изоляция аллокатора ограничивает физическое соседство критических страниц. Обсуждения в lkml вокруг pKVM и гипервизорной модели Heki намечают следующий рубеж: если ядро само по себе не может полностью доверять памяти, способен ли гипервизор дать ему надёжный фундамент, изолировав физическую память гостевых доменов на уровне ниже ядра? Ответ ещё формируется. Именно поэтому rowhammer остаётся одной из самых честных нерешённых задач в системной безопасности: она не притворяется закрытой, и это вызывает уважение.