
Proof-of-concept, демонстрирующий микроархитектурную утечку данных в процессорах Loongson LA464/LA664 через неопределённые старшие биты векторных регистров LASX, аналогично ZenBleed.
LoongBleed — это аппаратная уязвимость, концептуально схожая с ZenBleed (CVE-2023-20593) — затрагивающая процессоры Loongson LA464/LA664, реализующие одновременно LSX (128-битный SIMD) и LASX (256-битный SIMD).
В LoongArch регистры LSX $vr (128-битные) являются псевдонимами младшей половины регистров LASX
$xr (256-битных). Инструкции LSX и базовые операции с плавающей запятой определены только для работы
с младшими 128 битами (или их подмножеством); старшие биты соответствующего регистра $xr не определены.
Однако из-за микроархитектурного дефекта эти операции могут утекать данные через
старшие 128 бит $xr, раскрывая конфиденциальные данные через границы привилегий или
между SMT-соседями.
Примечание о независимом обнаружении — Тот же самый аппаратный дефект был независимо обнаружен и опубликован исследователями из CISPA Helmholtz Center for Information Security как LoongLeak (https://loongleakattack.com/), представленный на USENIX Security 2026 как "LoongLeak: Architectural Cross-Privilege-Boundary Data Leakage on LoongArch CPUs". Наша работа была выполнена независимо от команды LoongLeak; обе группы пришли к одному выводу, что процессоры Loongson LA464/LA664 утекают данные через неопределённые старшие биты регистров LASX
$xr. Отметим, что инструкции, которые мы используем для вызова утечки, не идентичны тем, что описаны в статье LoongLeak: наш PoC опирается на другой набор гаджетов (vor.v,vld,fld.d,fld.s) для воспроизведения того же самого аппаратного дефекта. Более того, поскольку утечка может быть вызвана чисто регистровыми инструкциями, такими какvor.v(без загрузки из памяти), наш анализ позволяет предположить, что первопричина, вероятно, заключается в повторном использовании физических регистров: старшие биты повторно используемого физического регистра не очищаются, что приводит к утечке устаревших данных от предыдущего владельца регистра. Это отличается от анализа в статье LoongLeak, которая приписывает утечку данных кэшу данных L1.
Доказательство концепции работает следующим образом:
$xrN с помощью xvld.xvst.PoC многократно выполняет этот гаджет для 16 архитектурных векторных регистров
($xr0–$xr15) в потоках, привязанных к физическим ядрам. Ненулевые значения, которые
появляются после инструкции, указывают на то, что микроархитектура перенесла
устаревшие или межконтекстные данные в архитектурное состояние регистров.
PoC поддерживает несколько тестовых инструкций, выбираемых с помощью --gadget:
На LA664 все четыре гаджета вызывают утечку, утекая не более 192 бит на
вектор. На LA464 утекают vld, fld.d и fld.s (не более 224 бит на
вектор); гаджет по умолчанию vor.v не вызывает утечку на LA464.
Usage: ./loongbleed_poc [OPTIONS]
Options:
-a, --all Launch one thread pinned to each physical core.
By default only thread on CPU 0 is launched.
-g, --gadget [vor|vld|fld.d|fld.s]
Use different instructions for testing.
-h, --help Show this help and exit.
# Single-thread mode on CPU 0
./run.sh
# Single-thread mode with vld gadget (required for LA464)
./run.sh --gadget vld
# All physical cores, default gadget
./run.sh -a
# All cores with fld.d gadget
./run.sh --all --gadget fld.d
Поток-жертва обрабатывает конфиденциальные данные на одном логическом CPU, в то время как PoC проверяет регистры на его SMT-соседе. Поток-шпион может наблюдать фрагменты данных жертвы в утёкших старших битах.
# Terminal 1 — start LoongBleed on CPU 0
./run.sh
# Terminal 2 — victim workload on the SMT sibling (CPU 1)
while true; do numactl -C 1 sort < /etc/shadow > /dev/null; done
Для автоматической настройки используйте предоставленный скрипт:
./poc_la664.sh
Он запускает нагрузку sort на CPU 1 (SMT-соседе CPU 0) и запускает
LoongBleed на CPU 0 с гаджетом по умолчанию.
Поток-жертва обрабатывает конфиденциальные данные на CPU, в то время как PoC проверяет
регистры на том же ядре. Требуется --gadget vld, поскольку гаджет по умолчанию vor.v
не вызывает утечку на LA464.
# Terminal 1 — start LoongBleed on CPU 0
./run.sh --gadget vld
# Terminal 2 — victim workload on the same core
while true; do numactl -C 0 sort < /etc/shadow > /dev/null; done
Для автоматической настройки:
./poc_la464.sh
Он запускает нагрузку sort на CPU 0 и запускает LoongBleed с --gadget vld.
PoC — это однофайловая программа на C++ без внешних зависимостей.
g++ -std=c++11 -O2 -march=native -pthread -o loongbleed_poc loongbleed_poc.cpp
Или используйте предоставленный скрипт:
./run.sh
# or, on LA464:
./run.sh --gadget vld
Когда обнаружена утечка и утёкшие байты содержат последовательность как минимум из 8 подряд идущих печатных ASCII-символов (0x20–0x7e), PoC выводит:
[cpu 0] LEAK chunk=14 data=0x7461646e756f4620_6572617774666f53_0000000000000000_0000000000000000 ascii=............Software Foundat
$xr0–$xr15) вызвал срабатывание$xrN после
инструкции, отображаемое как data3_data2_data1_data0, где:
data0 = биты [63:0] (младшие 64 бита результата)data1 = биты [127:64] (старшие 64 бита младшей 128-битной половины)data2 = биты [191:128] (младшие 64 бита старшей 128-битной половины)data3 = биты [255:192] (старшие 64 бита старшей 128-битной половины)..Любое ненулевое значение в старших 128 битах (data2 или data3) указывает на
микроархитектурную утечку данных.
Уязвимость была обнаружена при чтении статьи Chips and Cheese "Loongson's LSX and LASX Vector Extensions". В статье отмечалось, что векторные инструкции могут оставлять после себя остаточные случайные данные. Это привело нас к гипотезе, что переименование регистров может не очищать регистры — механизм, аналогичный ZenBleed — что теоретически позволило бы утекать данные из пространства ядра. Затем мы провели эксперименты для проверки этой гипотезы; утечка действительно происходит и воспроизводится как на Loongson 3A5000, так и на 3A6000.
Этот проект предоставлен исключительно в образовательных целях и для исследований в области безопасности.
| Гаджет | Инструкция | Описание | Утечка на LA664 | Утечка на LA464 |
|---|
vor | vor.v $vrN, $vrN, $vrN | Побитовое ИЛИ $vrN с самим собой | Да | Нет |
vld | vld $vrN, … | 128-битная загрузка из памяти в $vrN | Да | Да |
fld.d | fld.d $fN, … | 64-битная загрузка с плавающей запятой в $fN (псевдоним младших 64 бит $vrN) | Да | Да |
fld.s | fld.s $fN, … | 32-битная загрузка с плавающей запятой в $fN (псевдоним младших 32 бит $vrN) | Да | Да |