В процессе анализа исходных текстов клиента для работы в анонимной сети Tor обнаружена (http://www.viva64.com/ru/b/0178/) необычная уязвимость, которая может привести к оседанию в системной памяти остаточных данных, которые могут содержать конфиденциальную информацию, например, введённые пароли. Интерес представляет то, что формально код Tor не содержит ошибок и уязвимость является следствием особенностей работы некоторых компиляторов.
Проблема связана с тем, что Tor использует для очистки кэша функцию memset(), которая игнорируется в результате работы оптимизаторов некоторых компиляторов, что может привести к появлению неочищенных областей памяти после закрытия приложения. Например, при выборе режима оптимизации на скорость (-O2) Microsoft Visual Studio 2010 просто удаляет вызов memset при обнулении данных, если буфер в дальнейшем не используется в коде.
В качестве примера корректного подхода к очистке буферов приводится OpenSSL, в котором для очистки создана специальная функция, затирающая содержимое буфера случайными данными. Но из-за ошибки в вычислении размера буфера при вызове данной функции, она затирает только первые 4 байта (вместо размера буфера передаётся размер указателя на буфер), оставляя содержимое неизменным.
URL: http://www.viva64.com/ru/b/0178/
Новость: http://www.opennet.me/opennews/art.shtml?num=35275
> ...вместо размера буфера передаётся размер указателя на буфер...Веселые кодеры, блин. Банальнейший эпик-фейл.
>> ...вместо размера буфера передаётся размер указателя на буфер...
> Веселые кодеры, блин. Банальнейший эпик-фейл.Да ладно, вопрос одного символа (*|&).
на то он и банальный
<facepalm> тесты писать не?
> <facepalm> тесты писать не?Для сабжевого случая это довольно сложно. Потому что тестировать всю операционку, все либы вообще и компилер - это немножечко так напряжно.
тест содержал буфер из 4 байт? )
чем сложнее системы, тем все более странные проблемы они вызывают и тем сложнее^3 их исследовать на предмет странностей
Не понял, о какой версии OpenSSL говорится ?
Мне вот только непонятна мысль автора статьи насчёт того, что "компилятор ВПРАВЕ удалить вызов memset(), если ... " -- это в каком стандарте на C написано, что компилятор чего-то там вправе УДАЛЯТЬ?Но всё равно и Tor, и OpenSSL поступают плохо. Надо, конечно, звать mlock(), потом затирать (плевать, чем -- нулями тоже сойдёт), а потом звать munlock().
> Мне вот только непонятна мысль автора статьи насчёт того, что "компилятор ВПРАВЕ удалить вызов memset(), если ... " -- это в каком стандарте на C написано, что компилятор чего-то там вправе УДАЛЯТЬ?А чего там непонятного? Компилятор вправе делать что ему вздумается до тех пор, пока это не влияет на конечный результат, что собственно и происходит с memset.
То есть, игнорирование функции memset — это нормальное действие компилятора?! На мыло такие компиляторы!!!
>Например, при выборе режима оптимизации на скорость (-O2) Microsoft Visual Studio 2010
>просто удаляет вызов memset при обнулении данных, если буфер в дальнейшем не используется
>в коде.Там же не про GCC написано =)
А так все логично, говно компилятор от Microsoft Visual Studio 2010, под говно платформой
дает говно результат.
Как дуршлаг не латай, все равно дырявый, так что или безопасность или винда.
+1Мелкософт мог бы предупредить об этой "оптимизации"...
Или вывести список таких "оптимизации".
А так остаётся ответить, что это не адекватно со стороны
"кого-то там" где-то там...
ну вообще то gcc на этой теме тоже когда-то светился
помнится кто-то даже выступал по этой теме и было предложено помечать такие функции как обязательные к исполнению и оптимизатор их не должен трогать, вот только сдвинулось ли с места - не знаю
При включенной оптимизации - да. Если ты не хочешь чтобы компилятор компилировал именно то, что ты написал, тебе необходимо отключить оптимизацию.Твой кэп.
наоборот, Кэп
>>При включенной оптимизации - да. Если ты не хочешь чтобы компилятор компилировал именно то, что ты написал, тебе необходимо отключить оптимизацию.
>>Твой кэп.
>>наоборот, КэпЕсли ты не хочешь чтобы компилятор компилировал именно то, что ты написал, тебе необходимо включать оптимизацию, так что-ли?
проверять надо возвращаемые значения. А прогах по безопасности еще и повторную проверку вставлять.
Всё правильно сказал, только как это связанно с предыдущими 4 комментариями?
> только как это связанно с предыдущими 4 комментариями?Мозг и софт надо оптимизировать. :)
И во-вторых, не все операционки, если не сказать большинство,
гарантируют немедленную очистку области памяти указанной в memset()
с нулями в аргументах. В старом виде эта область может прожить вплоть
до смерти процесса. Все зависит от общей загрузки системы.
> И во-вторых, не все операционки, если не сказать большинство,
> гарантируют немедленную очистку области памяти указанной в memset()
> с нулями в аргументах. В старом виде эта область может прожить вплоть
> до смерти процесса. Все зависит от общей загрузки системы.Хм. Общение между иерархией процессов "родитель-потомок" через anonymous shared memory -- научная фантастика?
Павлин, как насчёт поделиться адресом дилера?-)
> как насчёт поделиться адресом дилера?-)Google: R. Love, Linux Kernel Development
А-а-а-а-а, Линукс... Ну, всё понятно, да.
> А-а-а-а-а, Линукс... Ну, всё понятно, да.Извиняйте, VAX/VMS internals не проходили.
---
Кстати, с внедрением FSCACHE, CLEANCACHE, FSCACHE, SAMEPAGE, ...
вероятность утечек от подобных косяков увеличивается еще больше!
>> А-а-а-а-а, Линукс... Ну, всё понятно, да.
> Извиняйте, VAX/VMS internals не проходили.Мне кажется, что ты путаешь поведение ядра и userland'овских функций работы с ОЗУ. С точностью до rescheduling'а и memory barrier'ов два потока исполнения внутри одного процесса _обязаны_ видеть изменения одной и той же области ОЗУ _синхронно_.
>>> А-а-а-а-а, Линукс... Ну, всё понятно, да.
>> Извиняйте, VAX/VMS internals не проходили.
> Мне кажется, что ты путаешь поведение ядра и userland'овских функций работы с ОЗУ.юзерам низя работать с ОЗУ, им есть VM
Можно. :) mlock() гарантирует это! :)
> Можно. :) mlock() гарантирует это! :)Покаж как? Хачу за маалокать, 2 байта на первом модуле,
второго CPU (ну вот аппаратная NUMA у меня дома), во второй микрухе слева.
>> А-а-а-а-а, Линукс... Ну, всё понятно, да.
> Извиняйте, VAX/VMS internals не проходили.Впрочем, как я заметил, с точностью до рескедулинга. То есть, без mlock()'а таки есть вероятность попадания страницы в своп. Но я об этом написал сразу -- и тут и Tor, и OpenSSL одинаково плохо написаны.
>>> А-а-а-а-а, Линукс... Ну, всё понятно, да.
>> Извиняйте, VAX/VMS internals не проходили.
> То есть, без mlock()'а таки есть вероятность попадания страницы в своп.Просто с mlock()'ом тоже может попасть.
#include <sys/resource.h>
/* ... */
struct rlimit limit;
limit.rlim_cur = 0;
limit.rlim_max = 0;
if (setrlimit(RLIMIT_CORE, &limit) != 0) {
/* Handle error */
}
long pagesize = sysconf(_SC_PAGESIZE);
if (pagesize == -1) {
/* Handle error */
}
char *secret_buf;
char *secret;
secret_buf = (char *)malloc(size+1+pagesize);
if (!secret_buf) {
/* Handle error */
}
/* mlock() may require that the address is a multiple of PAGESIZE */
secret = (char *)((((intptr_t)secret_buf + pagesize - 1) / pagesize) * pagesize);
if (mlock(secret, size+1) != 0) {
/* Handle error */
}
/* Perform operations using secret... */
if (munlock(secret, size+1) != 0) {
/* Handle error */
}
secret = NULL;
free(secret_buf);
secret_buf = NULL;Но POSIX говорит, что "Memory residency of unlocked pages is unspecified."
http://pubs.opengroup.org/onlinepubs/009695399/functions/mlo...Вот такая ж...а. Короча храните все пароли в голове!
> /* Handle error */Хорошая обработка ошибок :). При том в 50% программ оно так и остается :)
>> /* Handle error */
> Хорошая обработка ошибок :). При том в 50% программ оно так и
> остается :)ну минимум туда можно всунуть return -1, для полного феншуя,
обработать errno и свалить всё в лог.Кстати, а чё будет с системой, если mlock прошел, а unlock не смог или его прибили киллом 9 ?
> Твой кэп.Хреновый кэп. Если оптимизация ломает поведение программы, это, пардон, буллшит.
Буллшит - это когда чудаки пальцем тыкают не подумав, а потом удивляются, чо это оно не работает.
> Буллшит - это когда чудаки пальцем тыкают не подумав, а потом удивляются,
> чо это оно не работает.Ну надо же какие наглецы, полагают что компилятор должен работать как просят и не подкладывать свинью.
Как ни странно, компилятор работал, как его просили.
Microsoft никогда не волновали такие "мелочи"
> до тех пор, пока это не влияет на конечный результатУдаление memset в данном случае, как показано, влияет на конечный результат, просто компилятор не в состоянии определить что эта память потом используется. Соответственно это ошибка в Visual Studio.
так память и не используется. данные просто остаются в памяти, в этом и потенциальная уязвимость. в целом к компилятору придраться нельзя, с его точки зрения он всё правильно сделал
Глядя на данную проблему, все таки можно придраться.
> Глядя на данную проблему, все таки можно придраться.где проблема-то? как ты получишь доступ к этой [не очищенной] памяти?
разработаешь rootkit и заразишь им компьютер? но с такимже успехом ты можешь разработать rootkit и ЗАРАНЕЕ заразить систему чтобы она делала ежесекундный разбор и сохранение нужных участков оперативной памяти... никакой super_forced_memset() не поможет
Удаляй memset() @ будь плохим парнем^W компилятором
Компилятор делает все правильно. Он видит, что эта память потом нигде не используется. Значит и очищать ее незачем. В этом и есть суть оптимизаций - выкидывается "лишний" код. Если вы хотите эту память непременно очистить (из соображений секьюрности) - пишите отдельную собственную функцию для очистки памяти, вместо дефолтной. Или отключайте режим оптимизации, тогда компилятор скомпилирует все именно так, как вы написали. Но уж будьте любезны, тогда, писать сразу быстрый код, так как оптимизация ложится на ваши плечи.
> Он видит, что эта память потом нигде не используется. Значит и очищать ее незачем.Ой. Лихо вы...
Смотрите. Единица трансляции -- это файлы .c, перечисленные в командной строке компилятора, плюс всё то, что в них поместит препроцессор оператором #include. Компилятор не вправе предполагать, что какие-то другие файлы в операционной системе вообще существуют.
А теперь, внимание, фокус: в первом исходнике я пишу код, в котором аллоцирую ОЗУ, и вызываю функцию, находящуюся во втором исходнике, которой передаю указатель на вышеупомянутую область ОЗУ. В этой функции я делаю pthread_create() -- система у меня с MMU, мне можно -- и из дочернего потока вызываю функцию из третьего исходника, в которой делаю memset() этой области данными вида 0xaa55 -- и всё, "закрывающая фигурная скобка" (tm).
Я что, не вправе ожидать, что поток-создатель там эти 0xaa55 увидит, если включу оптимизацию у компилятора?
Или C-компилятор у нас теперь стал stateful, и сохраняет информацию обо всех оттранслированных ими файлах за всё время, начиная с установки операционной системы?
Или он магически должен догадаться, что я в предыдущем исходнике поток создал, и слежу за этой же областью ОЗУ из, так сказать, "другого места"?
>> Или он магически должен догадаться, что я в предыдущем исходнике поток создал, и слежу за этой же областью ОЗУ из, так сказать, "другого места"?для этого и придумали volatile
иначе если ты в программе нигде ее больше не используешь то компилятор вправе выкинуть этот memset, дабы не делать лишнюю работу (результаты которой все-равно никому не нужны)
>>> Или он магически должен догадаться, что я в предыдущем исходнике поток создал,
>>> и слежу за этой же областью ОЗУ из, так сказать, "другого места"?
> для этого и придумали volatile'volatile' придумали НЕ для этого: volatile придумали для того, чтобы компилятор не пытался помещать в регистр переменную, которую изменяют из другого места.
Но вот про выкидывание кода ни в одном стандарте нигде ничего не написано.
> иначе если ты в программе нигде ее больше не используешь то компилятор
> вправе выкинуть этот memset, дабы не делать лишнюю работу (результаты которой
> все-равно никому не нужны)Ссылку на документ, согласно которому компилятор чего-то там "вправе выкинуть", в студию.
>>Но вот про выкидывание кода ни в одном стандарте нигде ничего не написано.тогда весь смысл оптимизации теряется если компилятор не вправе изменять код. С таким же успехом можно сказать что он не вправе разворачивать циклы, или заменять конструкцию "a > b? a : b" эквивалентным безусловным кодом, или вообще святотатствовать убирая процедуру которая вызывается одни раз в коде, заменяя вызов ее телом
> тогда весь смысл оптимизации теряется если компилятор не вправе изменять код.Он его в праве изменять только таким образом который не меняет результат выполнения программы. Разное содержимое памяти после работы программы как видим может являть собой проблему.
>> тогда весь смысл оптимизации теряется если компилятор не вправе изменять код.
> Он его в праве изменять только таким образом который не меняет результат
> выполнения программы. Разное содержимое памяти после работы программы как видим может
> являть собой проблему.=> Все программы сложнее хелловорлда проблемны.
Сейчас же вот всё надо бросить и ради разработчиков одной программы, не способных освоить опции компилятора, переписать весь компилятор. А на остальных пофиг, пускай у них код тормозит.
> 'volatile' придумали НЕ для этого: volatile придумали для того, чтобы компилятор не пытался помещать в регистр переменную, которую изменяют из другого места... и, кроме этого, количество записей в volatile-переменную после оптимизации строго совпадало с тем, что было до нее. иными словами, компилятор не имеет права выкинуть одну из повторяющихся строк:
(volatile int *)0xDEADBEAF = 0;
(volatile int *)0xDEADBEAF = 0;
ровно как и:
i = *(const volatile int *)0xDEADBEAF;
i = *(const volatile int *)0xDEADBEAF;
> *(volatile int *)0xDEADBEAF = 0;
> *(volatile int *)0xDEADBEAF = 0;звездочки забыл
> Или он магически должен догадаться, что я в предыдущем исходнике поток создал, и слежу за этой же областью ОЗУ из, так сказать, "другого места"?а думаешь компилятор не сможет понять -- используется ли некая переменная (указатель на блок памяти) -- ТОЛЬКО в одном исходном файле, или же существуют внешние (extern) функции которые способны этот указатель передать во вне... ыыыЫЫ?
помоему вполне логично предположить что если некий указатель замешан (напрямую или внутри структуры) на параметры extern-функции -- то значит НЕ НАДО делать связанные с ним высокоуровневые оптимизации.
...соответственно компилятор делать их и НЕ будет :-)
точнее говоря -- компилятор будет делать высокоуровневые оптимизации только если уверен что переменная (указатель на блок памяти) не имеет связи с "внешним миром".
# P.S.: ну и конешно же я надеюсь что когда ты пишешь программу на C/C++ -- то ты же НЕ используешь всякие "неопределённые поведения (undefined behaviour)"? а если используешь -- то значит ты сам себе злобный буратино, стреляющий себе в ногу :).
> вместо размера буфера передаётся размер указателя на буферстрогая типизайия -- оказалась не такая уж и строгая [для функции сёравно какой-именно int ей забирать :-D]
...а вот любители утиной типизации -- 20 раз проверят (глазами, и/или тэстами) свой код на то чтобы он делал то что надо.
Естественно, или существует два типа int?
Какая, на фиг, строгая типизация в сях? Ох уж эти фанатики, вечно болтают, не разбираясь в предмете...
вообще говоря -- что-то мне не нравится что программы пытаются брать на себя функции операционной системы.программа должна сделать ТОЛЬКО ``delete[] my_secret_array`` и дальше НЕ ОБЯЗАНА следить чтобы это утекло из оперативной памяти.
...за приватностью оперативной и виртуальной памяти -- должна следить операционная система.
не так уж и сложно сделать внутри /etc/crypttab -- строчку связанную со SWAP [этим должны заниматсья разработчики дистрибутивов, конешно же, а не простые пользователи].
малоли какие алгоритмы существуют в операционной системе для работы с памятью, и это ну точно не должно волновать прикладную программу!
Tor не совсем уж прикладная программа.
> Tor не совсем уж прикладная программа.Tor -- конешно же -- да -- необычная программа.
..но обсуждаемая проблема как я понимаю касается вообще любой программы которая работает с паролями, например.
сейчас наверно почти каждый-второй разработчик какого-нибудь своего демона -- читая эту тему и думает:
"""
ой! щаз надо обязательно вставить: mlock() и munlock() в исходный код демона (в функцию очищения паролей из памяти)!
приватность в опасности!!!
"""
Все верно, без явной поддержки со стороны ОС, я даже и не придумаю, как это можно надежно решить. Нужен отдельный сист. вызов.
> Tor не совсем уж прикладная программа.А какая, пардон? Прикладуха самая обычная. Ну, как прокси-сервак например.
> программа должна сделать ТОЛЬКО ``delete[] my_secret_array`` и дальше НЕ ОБЯЗАНА следить чтобы это утекло из оперативной памятиИ тормозить будет твоя операционка нещадно, затирая содержимое памяти после каждого delete.
> программа должна сделать ТОЛЬКО ``delete[] my_secret_array`` и дальше НЕ ОБЯЗАНА следить
> чтобы это утекло из оперативной памяти.Программы бывают разные. Вот в частности с криптографическими ключами намного лучше если программа явно рулит памятью и может явно изничтожить ключ в известный ей момент времени. Во избежание всяких идиотских сюрпризов типа: программа заверщилась а следующая получила память с ключом, ибо GC соптимихировал немного и забил на зануление памяти, т.к. та прога не заказывала инициализированную область. А то что она чужой ключ прочла - ой, фигня получилась!
> Во избежание всяких идиотских сюрпризов типа: программа заверщилась а следующая получила память с ключом<sarcasm>ды щаз! 25 раз получит новая программа необнулённую память!</sarcasm> ЛОЛ :-D ! это же просто смешно!
такое только в MS-DOS было
> такое только в MS-DOS былоНу да, конечно, операционка сидит и протирает сотни мегов нулями после завершения процесса. Заняться ей нечем, ага.
> Ну да, конечно, операционка сидит и протирает сотни мегов нулями после завершения
> процесса. Заняться ей нечем, ага.Учите матчасть.
> Microsoft Visual Studio 2010 просто удаляет вызов memsetБюро Медвежьих Услуг.
GCC -O3 делает то же самое, насколько мне известно. Думаю, что и другие компиляторы в процессе оптимизации тоже выкидывают какие-либо действия с областями памяти,которые в дальнейшем не используются. Освобождаемый стек - типичный пример. И для подавляющего большиснтва приложений это оптимлаьная стратегия. А если пишешь экзотику - принимай специальные меры, это вполне возможно. В общем, нормальная ситуация - дефолтная реализация удобная для большиснства, а меньшинство может с разумными затратами сделать так, как ему нужно (в данном случае - использовать вместо memset свою аналогичную функцию).
> GCC -O3 делает то же самое,-O3 это вообще небезопасная оптимизация в GCC на свой страх и риск. Стабильным и беспроблемным является -O2. Им все вменяемые люди и пользуются.
>> Microsoft Visual Studio 2010 просто удаляет вызов memset
> Бюро Медвежьих Услуг.в чём заключается медвежья услуга?
уязвимость -- сугубо теоретическая.
если не обнулился блок-памяти щаз, значит обнулится чуть по-пожже.
в этом и заключается оптимизация!
если бы блок памяти не обнулялся бы ВООБЩЕ (и доставался в таком виде другой программе) -- вот тут бы тогда можно было бы начать говорить про медведей :) .
но реальность такова что ново-запущенным программам -- достаются уже подчищенные кусочки памяти [а уж в какой конкретно момент они были подчищенны -- не всё ли равно?]
> в чём заключается медвежья услуга?В том что результат выполнения программы изменился.
> уязвимость -- сугубо теоретическая.
Не, извините, возможность посторонним хапнуть ключ/пароль/... откуда-то слева - это даже теоретически очень плохо.
> В процессе анализа исходных текстов клиента для работы в анонимной сети Tor
> обнаружена (http://www.viva64.com/ru/b/0178/) необычная уязвимость
> при выборе режима оптимизации на скорость (-O2) Microsoft Visual Studio 2010
> просто удаляет вызов memset при обнулении данных, если буфер в дальнейшемЗачем, достали, очередной зонд из ... пользователей offtop/
Интересно как подобная проблема безопасности решается в той же реализации ssl у самих М$
м... никак?
вообще, у них (внезапно!) может быть другая реализация, на которой их компилятор так себя не ведет. либо, они обложили этот кусок кода прагмами с отключением оптимизации