Comments 93
Ждал этой такой статьи именно от вас.
+9
Вопрос немного не в тему — разве при эксплуатации этой уязвимости некоторые приложения иногда не будут падать с segfault?
0
UFO just landed and posted this here
Вообще то да. Атакующий не может знать как у меня распределение память, и не выйдем ли мы за пределы сегмента при попытке скопировать память. Да что там — мы и сами то не знам выйдем мы за сегмент или нет, зависит от того, как в данном конкретном случае.эта структура будет лежать в памяти, точнее — где будет лежать.
Собственно в си при случайном выходе за границу масссива (или просто обращении по не вал догму указателю) сегфолт бывает лишь иногда, а иногда просто чтение/запись не того что ожидалось. И это меняется от запуска к запуску приложения, и просто от раза к разу. Думаю каждый наблюдал подобное неоднократно в своих программах.
Собственно в си при случайном выходе за границу масссива (или просто обращении по не вал догму указателю) сегфолт бывает лишь иногда, а иногда просто чтение/запись не того что ожидалось. И это меняется от запуска к запуску приложения, и просто от раза к разу. Думаю каждый наблюдал подобное неоднократно в своих программах.
+3
UFO just landed and posted this here
Поясни мысль плиз.
0
UFO just landed and posted this here
SIGSEGV (если мы говорим о юниксах и им подобных типа линукса) возникает следующим образом — все зарождается в MMU, при попытке обратиться к адресу не отображенному в память, это сигнал (прервывание? никогда непосредственно с MMU не работал, надо будет попробовать) дергает за хвост ядро, которое уже обрабатывает эту ситуацию, если эта страничка в свопе, то она из свопа подгружается в ОЗУ, и прикладному процессу возвращается управление как будто ничего и не было, если же приложение не имеет прав на чтение/записть этой странички, или она вообще не была для него валидна (скажем через mmap не отображена), то приложению посылается сигнал SIGSEGV.
Соответственно SIGSEGV в случае heartbleed может возникнуть ровно по той же причине, по которой он может возникнуть и в случае обычного выхода за границу массива — наступили на страничку которая не отображена в память, либо к которой у нас нет прав на read.
Поэтому, иногда, в некоторых случаях, сегфолт тут таки возможен. Вероятность возникновения сегфолта при эксплуатации дыры сильно зависит от менеджера памяти данной конкретной софтины и того как активно им пользуются.
Соответственно SIGSEGV в случае heartbleed может возникнуть ровно по той же причине, по которой он может возникнуть и в случае обычного выхода за границу массива — наступили на страничку которая не отображена в память, либо к которой у нас нет прав на read.
Поэтому, иногда, в некоторых случаях, сегфолт тут таки возможен. Вероятность возникновения сегфолта при эксплуатации дыры сильно зависит от менеджера памяти данной конкретной софтины и того как активно им пользуются.
+2
UFO just landed and posted this here
Процесса? Сколько угодно. Ибо в этом же процессе может быть еще и вся бизнес-логика, работа с графикой, звуком и управлением 3Д печатью в одном флаконе.
А может занимать совсем мало — если это мой личный hello world работы с сокетами.
А может занимать совсем мало — если это мой личный hello world работы с сокетами.
0
UFO just landed and posted this here
OpenSSL это либа, а не отдельное приложение. Даже если она создает отдельный поток для какой-то там своей активности (как скажем делает x264), то все равно это всё будет находиться в одном адресном пространстве с моей программой, которая печатает на 3Д принтере и запускает пони в космос. Более того — у них даже менеджер памяти скорее всего будет один и тот же.
Если бы весь OpenSSL был в отдельном процессе, то эта уязвимость стала бы сразу на порядок менее критической — ведь вся бизнеслогика, все логины, пароли, номера кредиток и так далее, были бы от нее изолированы, лежали бы в другом, отдельном адресном пространстве, до которого из OpenSSL нельзя было бы добраться даже теоретически, даже если поменять его код ради этого.
Уязвимы бы были только те данные которые прошли через OpenSSL в данную сессию в явном (не шифрованном) виде. (тут сессия — сессия работы приложения, то есть от старта процесса с OpenSSL)
Если бы весь OpenSSL был в отдельном процессе, то эта уязвимость стала бы сразу на порядок менее критической — ведь вся бизнеслогика, все логины, пароли, номера кредиток и так далее, были бы от нее изолированы, лежали бы в другом, отдельном адресном пространстве, до которого из OpenSSL нельзя было бы добраться даже теоретически, даже если поменять его код ради этого.
Уязвимы бы были только те данные которые прошли через OpenSSL в данную сессию в явном (не шифрованном) виде. (тут сессия — сессия работы приложения, то есть от старта процесса с OpenSSL)
+5
OpenSSL может использоваться с неблокирующими сокетами в асинхронном сервере, где лишние форки вообще запрещены, ибо число воркеров должно быть постоянным.
+3
Я уже ответил ниже.
SEGFAULT может возникнуть, когда буфер, из которого подпрограмма обработки TLS Heartbeat читает данные, волею звезд на небе расположился в конце динамической кучи, слишком близко к границе сегмента этой самой кучи.
Вероятность такого события зависит от используемых алгоритмов распределения памяти, однако ни в одном из случаев не становится нулевой.
Кстати, если натравить на сервер одновременно пару десятков тысяч атакующих, комбинируя при этом heartbleed с долгим чтением, то SEGFAULT становится неизбежным.
SEGFAULT может возникнуть, когда буфер, из которого подпрограмма обработки TLS Heartbeat читает данные, волею звезд на небе расположился в конце динамической кучи, слишком близко к границе сегмента этой самой кучи.
Вероятность такого события зависит от используемых алгоритмов распределения памяти, однако ни в одном из случаев не становится нулевой.
Кстати, если натравить на сервер одновременно пару десятков тысяч атакующих, комбинируя при этом heartbleed с долгим чтением, то SEGFAULT становится неизбежным.
+6
UFO just landed and posted this here
читаемый буфер НЕ вызодит за границы выделнной маллоком памяти, откуда сегфаул?
-2
Не будет крэша. Потому что в один запрос — одна операция. Это возможно только для каких то редких приложений или самописных древностей, где бы СРАЗУ десятки тысячи запросов обрабатывались в пределах одного процесса (мультитред). А Apache будет форкать до предела, а потом ставить в очередь. При этом каждый форк будет обрабатывать один запрос.
-1
Как раз наоборот, сейчас популярен асинхронный подход, как в nginx, где один и тот же процесс (и один и тот же поток) одновременно обрабатывает множество запросов.
0
Ну, я рассматривал случай с общим адресным пространством. Специфика ОС, в которой я работаю…
Да, если каждый процесс выполняет не более 1 запроса — его так не положить.
Да, если каждый процесс выполняет не более 1 запроса — его так не положить.
0
Но, как правило, память под кучу выделяется довольно крупными блоками, и очень редко отдается обратно системе. Чем дольше сервер работает — тем больше у него куча, и тем ниже вероятность случайного выхода за ее пределы.
Для того, чтобы сервер упал с сегфолтом, очередной буфер должен быть выделен близко к концу динамической кучи — но его малый размер приводит к тому, что он чаще оказывается в начале.
Ну, и наконец, далеко не каждый segfault приводит к смерти приложения. Приложение может быть достаточно кривым, чтобы игнорировать сигнал — или же мастер-процесс может просто перезапустить упавший воркер.
Для того, чтобы сервер упал с сегфолтом, очередной буфер должен быть выделен близко к концу динамической кучи — но его малый размер приводит к тому, что он чаще оказывается в начале.
Ну, и наконец, далеко не каждый segfault приводит к смерти приложения. Приложение может быть достаточно кривым, чтобы игнорировать сигнал — или же мастер-процесс может просто перезапустить упавший воркер.
+1
Либо куча должна выделяться не непрерывным куском адресного пространства. То есть некоторые диапазоны адресов «из середины» должны быть не отображены в память.
Грубо говоря, если мои секретные данные лежат только в диапазоне адресов за страницей не отображенной в память, то через эту уязвимость до них не добраться в принципе.
Плюс, насколько я понимаю, если не увлекаться malloc/free, то есть если расположение данных довольно статично и предсказуемов (в пулах), то сделать дамп всей памяти процесса через эту уязвимость не получится, просто потому, что скорее всего через уязвимость будут утекать одни и те же 64Кб данных.
Грубо говоря, если мои секретные данные лежат только в диапазоне адресов за страницей не отображенной в память, то через эту уязвимость до них не добраться в принципе.
Плюс, насколько я понимаю, если не увлекаться malloc/free, то есть если расположение данных довольно статично и предсказуемов (в пулах), то сделать дамп всей памяти процесса через эту уязвимость не получится, просто потому, что скорее всего через уязвимость будут утекать одни и те же 64Кб данных.
0
Совершенно верно. Кстати, хорошая идея для хранения приватных ключей в памяти — хранить их в отдельных блоках, окруженных блоками зарезервированными.
Кстати, работает и в обратную сторону. Я так делал с буферами, отдаваемыми сторонним библиотекам — была одна глючная либа, которая так и норовила повредить управляемую кучу и уронить сервер. Но отдельная область памяти плюс перехват AccessViolationException сотворили чудо…
Однако, такую сущность, как расшифрованный http-запрос, прятать заранее бесполезно — а значит, все на свете защитить особой работой с памятью не получится.
Кстати, работает и в обратную сторону. Я так делал с буферами, отдаваемыми сторонним библиотекам — была одна глючная либа, которая так и норовила повредить управляемую кучу и уронить сервер. Но отдельная область памяти плюс перехват AccessViolationException сотворили чудо…
Однако, такую сущность, как расшифрованный http-запрос, прятать заранее бесполезно — а значит, все на свете защитить особой работой с памятью не получится.
+1
Если вы про heartbleed, то segfault не возможен при атаках
-1
>Код слишком запутанный.
>Да потому, что OpenSSL качественный проект.
Может быть, сам факт того, что код запутанный говорит что-то о качестве продукта?
>Да потому, что OpenSSL качественный проект.
Может быть, сам факт того, что код запутанный говорит что-то о качестве продукта?
0
Был бы некачественным, не использовался бы во всём мире. А недостатки у всех есть.
+9
Качественный проект с точки зрения статического анализатора?
Потому что с точки зрения использования — это ужасный продукт. Да, я его использовал достаточно много, но лишь потому что это единственный в свое время инструмент который мог подписывать документы по ГОСТ Р 34.10-2012 в среде linux. Других плюсов у данного инструмента — нет.
Потому что с точки зрения использования — это ужасный продукт. Да, я его использовал достаточно много, но лишь потому что это единственный в свое время инструмент который мог подписывать документы по ГОСТ Р 34.10-2012 в среде linux. Других плюсов у данного инструмента — нет.
0
А в чем конкретно неудобство использования?
И речь идет о консольной тулзе или сишной либе?
И речь идет о консольной тулзе или сишной либе?
0
Не знаю, но в тему, единственный минус, который я нашёл в OpenSSL это то, что его под Windows самостоятельно приходится собирать с обязательными правками, это претензия именно к качеству кода. Для любого опытного программиста, хоть раз встречавшегося с необходимостью интеграции сторонней библиотеки это всё привычно, но факт остаётся фактом.
0
Наверно не существует проекта где нету ляпов с проверкой указателя после его разыменования…
0
UFO just landed and posted this here
то есть они не используют популярные структуры данных, типо бинарного дерева, или связного списка?
0
UFO just landed and posted this here
Я знаю о c++. Но я всегда верил в то, что рекурсивные структуры данных не могут использовать не указатели, потому что рекурсия сразу развернется и займет всю доступную память. Я не прав?
0
template <typename T>
class bin_tree
{
private:
struct node {
node left;
node right;
T val;
} root;
};
Вот такой код у меня даже не компилируется, если расставить указатели — все ок
-2
UFO just landed and posted this here
Вы мне пример уже покажите, не терпится увидеть.
В 1960 конечно не было, но на сколько я понимаю тогда можно было оперировать сырой памятью и самому написать указатели, не называя их таковыми.
В 1960 конечно не было, но на сколько я понимаю тогда можно было оперировать сырой памятью и самому написать указатели, не называя их таковыми.
0
UFO just landed and posted this here
не знаю как вы, я всегда считал идиотизмом так извращаться и делать структуру, у которой главное приемущество — бесконечность в пределах памяти и динамическое добавление/удаление. В таком виде прийдется постоянно копировать элементы
0
Кольцевой буфер без указателей в точности также подвержен тем же ошибкам что и с указателями — промахнуться мимо конца массива как нефиг делать :-)
Какая разница, пишешь ты foo[n] или же *(foo+n)?
Какая разница, пишешь ты foo[n] или же *(foo+n)?
+1
Но уж выход-то за границы массива отловить можно. Если вспомнить про такой язык, как Паскаль, то там проверки времени выполнения на выход за пределы диапазона включены по умолчанию…
+1
UFO just landed and posted this here
То есть Паскаль за хранение длины строки в самой строке — уважения не достоин, в отличие от Фортрана?..
0
Ок. Отловите на этапе компиляции будет ли вот тут выход за границу массива:
int a = new int[rand()];
a[rand()] = 42;
0
Без проблем. Ошибка компиляции: невозможно привести тип int* к типу int.
PS мы же вроде договаривались без указателей?
PS мы же вроде договаривались без указателей?
0
Да какая разница?
Вместо rand может быть что угодно — например значение которое приехало по сети (собственно так heartbleed и работает), или вычисляемое по хитрому алгоритму значение. Или что-то там еще.
Единственное что тут можно делать — заставить явным образом перед использованием этого значения проверять его на вхождение в диапазон индексов массива. Но это, мягко говоря, не всегда эффективно будет, и в ряде случаев будет замусоривать код, делая его менее читабельным. Соответственно эту проверку будут отключать.
int a[1234];
a[rand()] = 42;
Вместо rand может быть что угодно — например значение которое приехало по сети (собственно так heartbleed и работает), или вычисляемое по хитрому алгоритму значение. Или что-то там еще.
Единственное что тут можно делать — заставить явным образом перед использованием этого значения проверять его на вхождение в диапазон индексов массива. Но это, мягко говоря, не всегда эффективно будет, и в ряде случаев будет замусоривать код, делая его менее читабельным. Соответственно эту проверку будут отключать.
0
UFO just landed and posted this here
В паскале это отлавливается только в рантайме. В compile time, в общем случае, это не ловится даже теоретически.
Да, и отличий тут от указателей опять таки никаких — отловить обращение указателя не туда тоже можно в runtime. При желании.
Да, и отличий тут от указателей опять таки никаких — отловить обращение указателя не туда тоже можно в runtime. При желании.
-1
И что? Чем вас не устраивает отлов таких ситуаций в рантайме?
0
Обычно устраивает (кроме тех случаев когда это начинает существенно тормозить работу алгоритма, бывают такие ситуации).
Но речь шла не об этом — речь шла о том, что если выпилить указатели из того же кольцевого буфера, то все сразу станет безопасней. Не станет. Валидность указателей в рантайме тоже можно проверять. Или точно также не проверять.
Опять таки, если вернуться в Си, то там убрав указатели и заменив на индексы массива мы не добьемся вообще ничего — они там просто эквивалентны.
Но речь шла не об этом — речь шла о том, что если выпилить указатели из того же кольцевого буфера, то все сразу станет безопасней. Не станет. Валидность указателей в рантайме тоже можно проверять. Или точно также не проверять.
Опять таки, если вернуться в Си, то там убрав указатели и заменив на индексы массива мы не добьемся вообще ничего — они там просто эквивалентны.
+1
Да, сразу — не станет. Но если действовать последовательно, то вполне реально получить проект, который в принципе не может испытывать проблем с указателями (если вы не забыли, началось все это обсуждение с вопроса — существуют ли такие проекты).
0
Ну да, проблем с указателями не будет. Будут другие проблемы :-) Например проблемы с индексами ;-)
Понятно что Си — штука не безопасная в том смысле, что там вероятность того, что там самодиагностика приложения находится в жутко рудиментарном состоянии. Там где в java будет exception, там в Си часто будет просто порча памяти. Молча. (причем оно потом может быть даже и крашнется, но совершенно внезапно и в совершенно другом месте)
Вопрос лишь в цене которую мы готовы заплатить за улучшенную самодиагностику приложения в рантайме, и большую предсказуемость поведения в случае ошибок :-)
Понятно что Си — штука не безопасная в том смысле, что там вероятность того, что там самодиагностика приложения находится в жутко рудиментарном состоянии. Там где в java будет exception, там в Си часто будет просто порча памяти. Молча. (причем оно потом может быть даже и крашнется, но совершенно внезапно и в совершенно другом месте)
Вопрос лишь в цене которую мы готовы заплатить за улучшенную самодиагностику приложения в рантайме, и большую предсказуемость поведения в случае ошибок :-)
-1
Не «сразу — не станет», а никогда не станет, по крайней мере, если не переписать OpenSSL на другом языке, на котором ссылки и указатели — не одно и то же, как в С.
-2
Ну внутри-то STD все равно используются указатели. Без них трудно.
0
UFO just landed and posted this here
Как насчёт Common Lisp или Scheme? Всё там есть — и деревья, и собаки. Однако указателей нет.
0
Я к сожалению мало знаком с функциональными языками. И я говорил на счет указателей относительно C/C++. В моем понимании указатель — это переменная, которая хранит адрес значения, а не само значение. Без такой возможности языка я не понимаю как можно создать рекурсивные структуры данных, которые хранят самих себя, с бесконечной (ограниченно памятью) вложенностью.
Ну и мы обсуждаем написание с нуля структуры данных, а не встроенное в язык. В C++ тоже есть std::list, который, кстати, использует указатели, но саму структуру можно использовать без указателей.
Ну и мы обсуждаем написание с нуля структуры данных, а не встроенное в язык. В C++ тоже есть std::list, который, кстати, использует указатели, но саму структуру можно использовать без указателей.
0
Расскажите, пожалуйста, а как анализатор узнал о BIO_write? Он сам увидел, что вместе со строкой функции всегда передается ее длина, или это увидели Вы и пометили ее для правила?
+2
В статье написано:
Отвлекусь немного. У читателя мог возникнуть вопрос, как PVS-Studio находит такие ошибки. Поясню. Он собирает информацию о вызовах функций, в данном случае о strncmp(), и строит матрицу данных:
vstart, «ASCII», 5
vstart, «UTF8», 4
vstart, «HEX», 3
vstart, «BITLIST», 3
У функции есть строковый аргумент и числовой. В основном длина строки совпадает с числом. Значит, этот аргумент задаёт длину строки. В одном месте это не так и значит надо выдать предупреждение V666.
+3
Я в конце рассказал, как работает диагностика V666. Ещё раз кратко. Анализатор PVS-Studio не требует никаких «пометок». Он собирает однотипные вызовы функций и строит матрицу данных. Затем, делает на её основе некоторые предположения. Если в функцию передаются строки и какие-то числовые аргументы, значения которых совпадает с длиной строки, то, наверное, это и есть длина строки. Если скажем, 20 раз совпало, а два раза нет, то видимо есть 2 ошибки.
+4
это, на самом деле, круто!
+2
О! Каждое правило это целая история и философские поиски. Я как ни будь начну рассказывать эти истории о разработке наиболее интересных диагностик. Просто всё никак. Вот Qt проверили, уже статья есть, но тут OpenSSL вклинился. Не публиковать же всё сразу. Статья про WinSCP лежит протухает… Прям очередь… А ведь ещё обязательно нужно статью про сравнение другими словами написать. А то прошлую заминусовали за то, что непочтительно отозвался о священной корове (Cppcheck). Никто так и не понял, какой объём работы был проделан и насколько адекватно мы старались сделать сравнение. Один только автор Cppcheck оценил проделанную работу :).
Но ничего, расскажу, расскажу про разработку привил.
Но ничего, расскажу, расскажу про разработку привил.
+7
Читаю все ваши статьи что попадаются. Но только после этого вашего комментария понял на сколько неординарный продукт вы делаете: «Он собирает однотипные вызовы функций и строит матрицу данных. Затем, делает на её основе некоторые предположения. Если в функцию передаются строки и какие-то числовые аргументы, значения которых совпадает с длиной строки, то, наверное, это и есть длина строки. Если скажем, 20 раз совпало, а два раза нет, то видимо есть 2 ошибки.»
+3
Рад что попал на вашу статью. Теперь читаю все с интересом. У вас шикарный инструмент, надеюсь когда нибудь я смогу его себе приобрести!
Отдельно очень интересно было бы почитать рассказ о формировании правил проверки.
Отдельно очень интересно было бы почитать рассказ о формировании правил проверки.
+1
Можно начать с CppCat. Это намного более доступный инструмент.
+2
Кстати, Каверти исправился и теперь находит heartbleed
0
А толку то. Это ведь для галочки сделано. Будет другая такая сложная ситуация и опять мимо. Я не планирую делать такую диагностику в PVS-Studio. Маркетологи конечно расскажут, как Coverity теперь вообще «все уязвимости ищет» и в том числе heartbleed. Толку-то. Parasoft вот тоже говорят «нашёл» эту ошибку. Где они раньше то были.
+17
А чем так уникален этот баг что нельзя вывести из него обобщенный случай? Поиск выхода за границы и чтения «чужой» памяти будет полезна.
+1
Потому, что статический анализ не всесилен. Я как раз недавно писал заметку на эту тему: Статический и динамический анализ кода. Плюс предлагаю почитать объяснения Coverity, почему всё не так просто: blog.regehr.org/archives/1125, ericlippert.com/2014/04/15/heartbleed-and-static-analysis/, blog.regehr.org/archives/1128. Их видимо тоже достали, раз уже несколько заметок на эту тему написали. :)
+5
Да, согласен. Маркетинг ж)
0
Маркетологи заставили написать детектор именно для него. Будут теперь трубить на каждом шагу, что мол наш анализатор детектит heartbleed (неявно подразумевая, что и другие подобные баги тоже)! А на деле нет, но зато продается.
+4
> Код корректен, но не аккуратен. Лучше вначале проверить переменную 'o', а уже потом использовать 'i':
На самом деле, возможно, что и нет. Т.к. тогда время выполнения кода при инициализированной o и при неинициализированной будет сильно различаться, что может как-то использовать атакующий. en.wikipedia.org/wiki/Timing_attack
На самом деле, возможно, что и нет. Т.к. тогда время выполнения кода при инициализированной o и при неинициализированной будет сильно различаться, что может как-то использовать атакующий. en.wikipedia.org/wiki/Timing_attack
-2
Мне кажется, Вы что-то перемудрили. По вашим словам получается, что сложить два числа (j+=i;) это медленно и можно заметить потраченное время. Не верю.
0
Каюсь, невнимательно посмотрел. Мне показалось, что вы рекомендовали проверять o в САМОМ начале, до DecryptUpdate и т.п. Вот это действительно может быть проблемой, а перестановка двух соседних команд, скорее всего, нет. Хотя разницу в десяток сложений уже легко поймать статистически и использовать.
0
Чтобы делать такие заявления, надо иметь перед глазами листинг ассмеблера после всех оптимизаций. Компилятор не дурак и вполне может реорганизовать дерево условий так, чтобы вынести одинаковые подвыражения «за скобки». CSE называется. Как и в примере с ASN1_PRINTABLE_type.
+2
Там парой строчек ниже и так стоит досрочный выход из функции! Время выполнения будет отличаться, независимо от местоположения
j+=i
0
А разве качество кода как-то связано с его malware-назначением?
Ясно, что в openssl это был банальный недосмотр — но по-моему требовать, чтобы подобное было обнаружено статическим анализатором — это уж слишком.
Если так рассуждать — то при анализе исходников явных вирусов он тоже должен сразу распознавать явно злобные намерения кода и ругаться, что есть мочи?
Ясно, что в openssl это был банальный недосмотр — но по-моему требовать, чтобы подобное было обнаружено статическим анализатором — это уж слишком.
Если так рассуждать — то при анализе исходников явных вирусов он тоже должен сразу распознавать явно злобные намерения кода и ругаться, что есть мочи?
+1
Переменная 'i' может оказаться неинициализированной, если (o == false). В результате, к 'j' будет прибавлено непонятно что. Это не страшно, так как, если (o == false), то срабатывает обработчик ошибки, и функция прекращает свою работу.
IMO, это всё-таки достаточно серьёзно, поскольку использование неинициализированной переменной вызывает неопределённое поведение, и в результате может произойти что угодно.
Например, компилятор может думать так:
- В строчке j += i; неопределённое поведение, если o == false перед вызовом EVP_DecryptUpdate.
- Правильная программа никогда не вызовет неопределённое поведение в этой строчке.
- То есть, o никогда не будет false перед вызовом EVP_DecryptUpdate.
- Поэтому выбрасываем из кода первый if (o).
0
Компиляторы так не думают. И не будут так думать.
Просто потому, что это превратит огромное количество кода в русскую рулетку.
Так что не думаю что ваши опасения оправданны.
Просто потому, что это превратит огромное количество кода в русскую рулетку.
Так что не думаю что ваши опасения оправданны.
0
Sign up to leave a comment.
Скучная статья про проверку OpenSSL