Поток 1: Что-то положили в YMM (из памяти регистров выделилось два блока для низа и верха)
Поток 1: Что-то положили в XMM (верхняя часть как бы свободна, но мы её не трогали)
Поток 1: Дёргаем спекуляцию на очистку верхней части -- проц спекулятивно ставит z-бит и освобождает блок для верха
Поток 2: Что-то делает с регистрами, получая блок под работу, потом освобождает его (данные в блоке остаются, так как регистр был освобождён, не переписан)
Поток 1: Упс, это, спекуляция не проканала, отменяем (убирает z-бит и возвращает блок для верха обратно). => хоба! мы получачем доступ к данным от прошлого потока которые там лежали
Хотя эксплоит может обеспечить утечку данных со скоростью только около 30 КБ/с
Поправка. Не "только" а целых 30 килобайт в секунду на ядро. Это очень очень очень очень очень жырно. RAMbleed (развитие rowhammer) обеспечивало утечки несколько бит в секунду.
Хм. Хоть и удаётся залезть куда вроде бы не стоит (например, `sin.__self__`), но сделать с этим знанием ничего нельзя благодаря не самым плохим ограничениям.
Худше что можно получить -- MemoryError на запрос типа "asdf"*10000000000
Выравнивание требуется по разным причинам. x86 в целом довольно вольно к выравниванию относится -- ну упадёт производительность "немного" если читать не выровненное, но и только. Но прямо сейчас в миру тонны процессоров которые невыровненные читать откажутся с SIGBUS'ом даже внутри страницы.
В данном же случае они полагаются на всё сразу -- выравнивание и для инструкции (_mm_load_si128 грузит только выровненные адреса) и для производительности. (кстати, совсем не факт что интрисинк сгенерит обязательно выровненную инструкцию -- см например https://stackoverflow.com/questions/73912363/mm-load-si128-is-not-throwing-on-unaligned-access) и дополнительно приведен комментарий объясняющий (как тот математик) безопасность этого действия.
Не "просто подавлено". Эта оптимизация включена (и подавлена) только для __SSE2__ и используют гарантированно пакетные выровненные загрузки через _mm_load_si128 и добавлены комментарии почему это безопасно :)
А я посмотрел. Однако же просьба была вызвать падение в коде, вызванное UB от обращения позади нуля. Я вызвал? Вызвал. Оно упало из-за обращения к следующему после выравнивания. Да, на современных архитектурах, вызвать падение "на пальцах" можно только в этом месте.
Остальной код лезет в лишние места, но только в пределах выровненных 128 байт. То есть вызвать проблему всё еще можно -- напрмер, в случае обращения к какому-либо устройству через MMIO если дёргать не поддержанные адреса.
Еще можно получить шар с волосами на архитектурах где размер страницы меньше 128 байт.
Еще можно напороться на параноидальный гипервизор, контролирующий доступ в пределах субстраниц.
Исправленный код допустим даже в продакшен (и даже я бы принял его с подавлением санитайзера в этом случае) но только при условии комментария и инит-теста что размер страницы больше 128.
Program received signal SIGSEGV, Segmentation fault.
0x0000555555555097 in _run_switches (i=0x555555559ff8 <c+8120> "") at a.c:24
24 rs[n] = i[n] ? 0 : ~0;
Могу и корку прислать если интересно, но вы легко можете и повторить падение.
Если вы любите прыгать в стог сена с вилами в нём и ни разу не напоролись на них -- не повод рассказывать что опасность вил забытых в сене преувеличена.
Компилятор имеет право на оптимизации в рамках, заданными стандартом -- где находятся чекпоинты, какие побочные эффекты прописаны и так далее.
Форма (if / switch / и так далее) не является четким определением того, как будет выглядеть итоговый машинный код до тех пор, пока все эффекты эквивалентны исходному коду.
Вы привели пример когда два НЕ эквивалентных исходных кода генерируют различный машинный код. Спасибо, кэп?
Мой аргумент был о том, что два различных исходных кода, использующих разную запись исходника (с if и switch) но выполняющих одну и ту же работу с одними и теми же данными -- являющиеся эквивалентами друг друга -- приводят к эквивалентному машинному коду, что еще раз доказывает, что if и switch эквивалентны (до тех пор, пока они использованы эквивалентно! это не означает, что они эквивалентны всегда).
Исключительно синтаксическое изменение исходного кода не должно приводить к изменениям производительности -- оптимизирующие компиляторы для того и существуют, чтобы это делать.
Если я правильно понял, получается следующее:
Поток 1: Что-то положили в YMM (из памяти регистров выделилось два блока для низа и верха)
Поток 1: Что-то положили в XMM (верхняя часть как бы свободна, но мы её не трогали)
Поток 1: Дёргаем спекуляцию на очистку верхней части -- проц спекулятивно ставит z-бит и освобождает блок для верха
Поток 2: Что-то делает с регистрами, получая блок под работу, потом освобождает его (данные в блоке остаются, так как регистр был освобождён, не переписан)
Поток 1: Упс, это, спекуляция не проканала, отменяем (убирает z-бит и возвращает блок для верха обратно). => хоба! мы получачем доступ к данным от прошлого потока которые там лежали
Поправка. Не "только" а целых 30 килобайт в секунду на ядро. Это очень очень очень очень очень жырно. RAMbleed (развитие rowhammer) обеспечивало утечки несколько бит в секунду.
Это прямо EPIC.
Совсем-совсем вручную, или используя ply ? ply вкусный, лайк-шер-рекоменд
Ну вот арифметика в статье неплохо ограничена, вроде ничего не торчит....
А что понимается под "взломать"?
Комп не под рукой... Что на тему (sin.__name__*10000000)?
Хм. Хоть и удаётся залезть куда вроде бы не стоит (например, `sin.__self__`), но сделать с этим знанием ничего нельзя благодаря не самым плохим ограничениям.
Худше что можно получить -- MemoryError на запрос типа "asdf"*10000000000
срочно наесть или сбросить 5-6 кило чтобы сменить размер футболки.
Хорошая копипаста не только не требует, так ещё и приносит много денег!
Пробовал портал с рейтрейсом и не понял в чем фишка. Разница настолько незначительная, что вау эффекта нет...
Выравнивание требуется по разным причинам. x86 в целом довольно вольно к выравниванию относится -- ну упадёт производительность "немного" если читать не выровненное, но и только. Но прямо сейчас в миру тонны процессоров которые невыровненные читать откажутся с SIGBUS'ом даже внутри страницы.
В данном же случае они полагаются на всё сразу -- выравнивание и для инструкции (_mm_load_si128 грузит только выровненные адреса) и для производительности. (кстати, совсем не факт что интрисинк сгенерит обязательно выровненную инструкцию -- см например https://stackoverflow.com/questions/73912363/mm-load-si128-is-not-throwing-on-unaligned-access) и дополнительно приведен комментарий объясняющий (как тот математик) безопасность этого действия.
Не "просто подавлено". Эта оптимизация включена (и подавлена) только для
__SSE2__и используют гарантированно пакетные выровненные загрузки через _mm_load_si128 и добавлены комментарии почему это безопасно :)Благодаря найденным исходникам http://ascii.textfiles.com/archives/5039 -- можно поиграть в оригинал адаптированный к телефонам :)
"Please be patient. The applet is 46K long". Господи, на WAP это стоило бы две квартиры и дачу впридачу!
Кто разрешил пятничный пост в понедельник публиковать? :)
А я посмотрел. Однако же просьба была вызвать падение в коде, вызванное UB от обращения позади нуля. Я вызвал? Вызвал. Оно упало из-за обращения к следующему после выравнивания. Да, на современных архитектурах, вызвать падение "на пальцах" можно только в этом месте.
Остальной код лезет в лишние места, но только в пределах выровненных 128 байт. То есть вызвать проблему всё еще можно -- напрмер, в случае обращения к какому-либо устройству через MMIO если дёргать не поддержанные адреса.
Еще можно получить шар с волосами на архитектурах где размер страницы меньше 128 байт.
Еще можно напороться на параноидальный гипервизор, контролирующий доступ в пределах субстраниц.
Исправленный код допустим даже в продакшен (и даже я бы принял его с подавлением санитайзера в этом случае) но только при условии комментария и инит-теста что размер страницы больше 128.
Могу и корку прислать если интересно, но вы легко можете и повторить падение.
Если вы любите прыгать в стог сена с вилами в нём и ни разу не напоролись на них -- не повод рассказывать что опасность вил забытых в сене преувеличена.
Да, код падает.
Выдаёт segmentation fault на вашей реализации и не выдаёт на простых без UB.
gcc version 11.3.0 (Ubuntu 11.3.0-1ubuntu1~22.04.1)
Собирал вот так:
Я не знаю о каком заговоре идёт речь.
Компилятор имеет право на оптимизации в рамках, заданными стандартом -- где находятся чекпоинты, какие побочные эффекты прописаны и так далее.
Форма (if / switch / и так далее) не является четким определением того, как будет выглядеть итоговый машинный код до тех пор, пока все эффекты эквивалентны исходному коду.
Вы привели пример когда два НЕ эквивалентных исходных кода генерируют различный машинный код. Спасибо, кэп?
Мой аргумент был о том, что два различных исходных кода, использующих разную запись исходника (с if и switch) но выполняющих одну и ту же работу с одними и теми же данными -- являющиеся эквивалентами друг друга -- приводят к эквивалентному машинному коду, что еще раз доказывает, что if и switch эквивалентны (до тех пор, пока они использованы эквивалентно! это не означает, что они эквивалентны всегда).
Исключительно синтаксическое изменение исходного кода не должно приводить к изменениям производительности -- оптимизирующие компиляторы для того и существуют, чтобы это делать.