Стаття, згадана sgbj в коментарях, написаних Павлом Тернером від Google, пояснює наступне значно детальніше, але я спробую це зробити:
Наскільки я можу скласти це разом з обмеженою інформацією на даний момент, ретрансляція - це зворотний батут, який використовує нескінченний цикл, який ніколи не виконується для запобігання процесору міркувати про ціль непрямого стрибка.
Основний підхід можна побачити у відділенні ядра Andi Kleen, що стосується цього питання:
Він вводить новий __x86.indirect_thunk
виклик, який завантажує ціль виклику, адреса пам'яті (яку я зателефоную ADDR
) зберігається вгорі стека і виконує стрибок, використовуючи RET
інструкцію. Потім сам виклик викликається за допомогою макросу NOSPEC_JMP / CALL , який використовувався для заміни багатьох (якщо не всіх) непрямих викликів та стрибків. Макрос просто розміщує ціль виклику на стеці і при необхідності правильно встановлює зворотну адресу (зверніть увагу на нелінійний потік управління):
.macro NOSPEC_CALL target
jmp 1221f /* jumps to the end of the macro */
1222:
push \target /* pushes ADDR to the stack */
jmp __x86.indirect_thunk /* executes the indirect jump */
1221:
call 1222b /* pushes the return address to the stack */
.endm
Розміщення call
в кінці є необхідним, щоб коли непрямий виклик закінчувався, потік управління продовжується позаду використання NOSPEC_CALL
макросу, щоб його можна було використовувати замість звичайногоcall
Сам грона виглядає так:
call retpoline_call_target
2:
lfence /* stop speculation */
jmp 2b
retpoline_call_target:
lea 8(%rsp), %rsp
ret
Потік управління тут може трохи заплутатися, тому дозвольте мені уточнити:
call
натискає поточний покажчик інструкцій (мітка 2) до стеку.
lea
додає 8 до покажчика стека , ефективно відкидаючи останнє натиснуте чотирислівне, що є останньою зворотною адресою (до мітки 2). Після цього вершина стека знову вказує на реальну адресу повернення ADDR.
ret
переходить до *ADDR
та скидає вказівник стека на початок стеку викликів.
Зрештою, вся ця поведінка практично еквівалентна стрибкам безпосередньо *ADDR
. Одна з переваг, яку ми отримуємо, полягає в тому, що передбачувач гілки, який використовується для операторів повернення (Buffer Return Stack, RSB), виконуючи call
інструкцію, передбачає, що відповідне ret
твердження перейде до мітки 2.
Частина після мітки 2 насправді ніколи не виконується, це просто нескінченний цикл, який теоретично заповнив би конвеєр JMP
інструкцій інструкціями. При використанні LFENCE
, PAUSE
або в більш загальному випадку інструкції в результаті чого конвеєра команд буде зрив зупиняє процесор від витрачаючи сил і часу на цьому спекулятивному виконанні. Це тому, що у випадку, якщо виклик retpoline_call_target повернеться нормально, LFENCE
наступна інструкція повинна бути виконана. Це також те, що прогнозує галузевий предиктор на основі вихідної адреси повернення (мітка 2)
Цитувати з посібника з архітектури Intel:
Інструкції, що слідують за LFENCE, можуть бути вилучені з пам'яті перед LFENCE, але вони не виконуватимуться, поки LFENCE не завершиться.
Однак зауважте, що в специфікації ніколи не згадується, що LFENCE і PAUSE викликають зупинку трубопроводу, тому я читаю тут трохи між рядками.
Повернімось до вашого початкового запитання: Розкриття інформації пам'яті ядра можливо завдяки поєднанню двох ідей:
Незважаючи на те, що спекулятивне виконання повинно бути вільним від побічних ефектів, коли спекуляція була неправильною, спекулятивне виконання все ще впливає на ієрархію кешу . Це означає, що коли навантаження на пам'ять виконується спекулятивно, це все ще може призвести до вилучення кеш-лінії. Цю зміну в ієрархії кешу можна визначити, ретельно вимірявши час доступу до пам'яті, яка відображається на той самий набір кешу.
Можна навіть просочити деякі біти довільної пам'яті, коли адреса джерела зчитуваної пам'яті сама зчитується з пам'яті ядра.
Прогноз непрямої гілки процесорів Intel використовує лише найнижчі 12 біт інструкції щодо джерела, тому легко отруїти всі 2 ^ 12 можливих історій прогнозування з керованими користувачами адресами пам'яті. Вони можуть тоді, коли прогнозується непрямий стрибок у межах ядра, спекулятивно виконуватись з привілеями ядра. Використовуючи бічний канал кешування часу, ви можете просочити довільну пам'ять ядра.
ОНОВЛЕННЯ: У списку розсилки ядра триває дискусія, яка змушує мене вважати, що ретполіни не повністю пом'якшують проблеми передбачення гілки, як коли буфер повернення стека (RSB) працює порожнім, новіші архітектури Intel (Skylake +) відпадають для вразливого цільового буфера галузі (BTB):
Retpoline як стратегія пом'якшення міняє непрямі гілки для повернення, щоб уникнути використання прогнозів, що надходять від BTB, оскільки вони можуть отруїтися зловмисником. Проблема з Skylake + полягає в тому, що підтік RSB переходить до використання прогнозування BTB, що дозволяє зловмиснику взяти під контроль спекуляції.