Я провожу курс в колледже, где одна из лабораторий заключается в выполнении эксплойтов по переполнению буфера для кода, который они нам дают. Это варьируется от простых эксплойтов, таких как изменение адреса возврата для функции в стеке для возврата к другой функции, вплоть до кода, который изменяет состояние регистра / памяти программы, но затем возвращает к функции, которую вы вызвали, что означает, что вызванная вами функция совершенно не обращает внимания на эксплойт.
Я провел некоторое исследование в этой области, и подобные виды эксплойтов даже сейчас используются повсеместно, например, для запуска homebrew на Wii и отвязанного джейлбрейка для iOS 4.3.1.
Мой вопрос: почему эту проблему так трудно решить? Очевидно, что это один из основных эксплойтов, используемых для взлома сотен вещей, но кажется, что это будет довольно легко исправить, просто обрезав любой ввод за допустимую длину и просто очистив весь ввод, который вы берете.
РЕДАКТИРОВАТЬ: Еще одна точка зрения, которую я хотел бы рассмотреть ответы - почему создатели C не исправить эти проблемы путем переопределения библиотек?
источник
Это не совсем неверно говорить , что C на самом деле «подверженные ошибкам» по дизайну . Помимо некоторых серьезных ошибок, таких как
gets
, язык C не может быть по-другому, не теряя при этом основной функции, которая в первую очередь привлекает людей к C.C был разработан как системный язык, чтобы действовать как своего рода «переносимая сборка». Главная особенность языка C состоит в том, что в отличие от языков более высокого уровня, код C часто очень близко соответствует реальному машинному коду. Другими словами,
++i
обычно это простоinc
инструкция, и вы часто можете получить общее представление о том, что процессор будет делать во время выполнения, взглянув на код Си.Но добавление неявной проверки границ добавляет много дополнительных накладных расходов, которые программист не просил и мог не захотеть. Эти издержки выходят далеко за рамки дополнительного хранилища, необходимого для хранения длины каждого массива, или дополнительных инструкций для проверки границ массива при каждом доступе к массиву. А как насчет арифметики указателей? Или что, если у вас есть функция, которая принимает указатель? Среда выполнения не может знать, попадает ли этот указатель в пределы законно выделенного блока памяти. Чтобы отслеживать это, вам понадобится серьезная архитектура времени выполнения, которая может проверять каждый указатель на таблицу выделенных в данный момент блоков памяти, и в этот момент мы уже попадаем на территорию управляемого времени выполнения в стиле Java / C #.
источник
Я думаю , что реальная проблема заключается не в том , что эти виды ошибок , которые трудно исправить, но то , что они так легко сделать: Если вы используете
strcpy
,sprintf
и друзья в (казалось бы) простой способ , который может работать, то вы , вероятно , открыл дверь для переполнения буфера. И никто не заметит, пока кто-нибудь не воспользуется им (если у вас нет очень хороших обзоров кода). Теперь добавьте тот факт , что есть много посредственных программистов , и что они находятся под давлением времени большую часть времени - и у вас есть рецепт для кода , который настолько пронизана с переполнением буфера , что это будет трудно исправить их все просто потому , что есть их так много, и они так хорошо прячутся.источник
sizeof(ptr)
4 или 8. Это еще одно ограничение C: нет способа определить длину массива, учитывая только указатель на него.#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]) / (sizeof(a) != sizeof(void *))
запуск деления на ноль во время компиляции. Еще один умный пример, который я впервые увидел в Chromium, -#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]) / !(sizeof(a) % sizeof((a)[0]))
это обмен горстки ложных срабатываний на некоторые ложные отрицания - к сожалению, это бесполезно для символа []. Вы можете использовать различные расширения компилятора, чтобы сделать его еще более надежным, например blogs.msdn.com/b/ce_base/archive/2007/05/08/… .Трудно устранить переполнение буфера, потому что C практически не предоставляет полезных инструментов для решения проблемы. Это фундаментальный изъян языка, родные буфера не обеспечивают никакой защиты , и это практически, если не полностью, невозможно заменить их продукт высочайшего класса, как C ++ сделал с
std::vector
иstd::array
, и это трудно даже в режиме отладки , чтобы найти переполнение буфера.источник
std::vector
эффективно реализовывать конструкции более высокого уровня . Иvector::operator[]
делает тот же выбор для скорости над безопасностью. Безопасностьvector
достигается за счет упрощения перемещения по размеру, что является тем же подходом, который используют современные библиотеки Си.realloc
(C99 также позволяет определять размеры массивов стека, используя определенный во время выполнения, но постоянный размер через любую автоматическую переменную, почти всегда предпочтительнееchar buf[1024]
). Во-вторых, проблема не имеет ничего общего с расширением буферов, она связана с тем, несут ли буферы размер с ними и проверяют этот размер при доступе к ним.vector::operator[]
выполняет проверку границ в режиме отладки - чего не могут сделать нативные массивы - и, во-вторых, в C нет способа поменять местный тип массива на тот, который может выполнять проверку границ, потому что нет шаблонов и операторов перегрузки. В C ++, если вы хотите перейти отT[]
кstd::array
, вы можете просто поменять typedef. В C нет способа достичь этого, и нет способа написать класс с эквивалентной функциональностью, не говоря уже об интерфейсе.std::vector<T>
иstd::array<T, N>
в C ++. Не было бы никакого способа спроектировать и указать какую-либо библиотеку, даже стандартную, которая могла бы сделать это.std::vector
также никогда не может быть статического размера. Что касается универсального, вы можете сделать его настолько универсальным, насколько это требуется хорошему C - небольшое количество фундаментальных операций над void * (добавление, удаление, изменение размера) и все остальное написано специально. Если вы собираетесь жаловаться, что C не имеет обобщений в стиле C ++, это выходит за рамки безопасной обработки буфера.Проблема не в языке Си .
ИМО, единственное серьезное препятствие, которое нужно преодолеть, заключается в том, что Си просто плохо учат . Десятилетия плохой практики и неверной информации были закреплены в справочных руководствах и примечаниях к лекциям, отравляя умы каждого нового поколения программистов с самого начала. Студентам дается краткое описание из «простых» функций ввода / вывода , как
gets
1 или ,scanf
а затем оставил их собственные устройства. Им не сказано, где и как эти инструменты могут выйти из строя, или как предотвратить эти ошибки. Им не говорят об использованииfgets
иstrtol/strtod
потому что они считаются «продвинутыми» инструментами. Затем они обрушиваются на профессиональный мир, чтобы нанести ущерб. Не так уж много из более опытных программистов знают лучше, потому что они получили такое же образование с повреждением мозга. Это сводит с ума. Я вижу так много вопросов здесь, в Stack Overflow и на других сайтах, где ясно, что человека, задающего вопрос, обучает тот, кто просто не знает, о чем говорит , и, конечно, вы не можете просто сказать «Ваш профессор не прав», потому что он профессор, а вы просто какой-то парень в Интернете.И затем у вас есть толпа, которая презирает любой ответ, начинающийся с «ну, в соответствии с языковым стандартом ...», потому что они работают в реальном мире, и в соответствии с ними стандарт не применим к реальному миру . Я могу иметь дело с кем-то, у кого просто плохое образование, но любой, кто настаивает на том , чтобы быть невежественным, - это просто упадок в отрасли.
Не было бы проблем переполнения буфера, если бы язык преподавался правильно с акцентом на написание безопасного кода. Это не "сложно", это не "продвинутый", это просто быть осторожным.
Да, это была напыщенная речь.
1 Который, к счастью, был окончательно извлечен из языковой спецификации, хотя он навсегда просуществует через 40 лет унаследованного кода.
источник
sprintf
, но это не значит, что язык не был ошибочным. C был ошибочным и имеет недостатки - как и любой язык - и важно, чтобы мы признали эти недостатки, чтобы мы могли продолжать их исправлять.Проблема не столько в некомпетентности программистов, сколько в управленческой близорукости. Помните, что приложению на 90 000 строк требуется только одна небезопасная операция, чтобы быть полностью незащищенной. Практически за пределами вероятности, что любое приложение, написанное поверх принципиально небезопасной обработки строк, будет на 100% совершенным, что означает, что оно будет небезопасным.
Проблема заключается в том, что расходы, связанные с отсутствием безопасности, либо не взимаются с правильного адресата (компании, продающей приложение, почти никогда не придется возвращать покупную цену), либо неясно видны в момент принятия решения («Мы должны отправить в марте не смотря ни на что! "). Я вполне уверен, что если бы вы учитывали долгосрочные затраты и затраты для своих пользователей, а не для прибыли вашей компании, то писать на C или родственных языках было бы намного дороже, возможно, настолько дорого, что во многих случаях это был бы неправильный выбор. области, где в наше время общепринятая мудрость говорит, что это необходимость. Но это не изменится, если не будет введена более строгая ответственность за программное обеспечение, чего никто в отрасли не хочет.
источник
Одна из великих возможностей использования C заключается в том, что он позволяет вам манипулировать памятью любым удобным для вас способом.
Одним из больших недостатков использования C является то, что он позволяет вам манипулировать памятью любым удобным для вас способом.
Есть безопасные версии любых небезопасных функций. Тем не менее, программисты и компилятор не строго предписывают их использование.
источник
Возможно, потому что C ++ уже сделал это и обратно совместим с кодом C. Поэтому, если вам нужен безопасный строковый тип в вашем C-коде, вы просто используете std :: string и пишете C-код, используя компилятор C ++.
Базовая подсистема памяти может помочь предотвратить переполнение буфера путем введения защитных блоков и проверки их корректности - поэтому во все выделения добавляется 4 байта «fefefefe», когда при записи в эти блоки система может генерировать воблер. Не гарантируется предотвращение записи в память, но это покажет, что что-то пошло не так и нуждается в исправлении.
Я думаю, что проблема в том, что старые процедуры strcpy и т. Д. Все еще присутствуют. Если бы они были удалены в пользу strncpy и т. Д., Это помогло бы.
источник
Просто понять, почему проблема переполнения не устранена. С был несовершенен в нескольких областях. В то время эти недостатки считались терпимыми или даже как особенность. Теперь, десятилетия спустя, эти недостатки не устраняются.
Некоторые части сообщества программистов не хотят, чтобы эти дыры были закрыты. Просто посмотрите на все пламенные войны, которые начинаются с строк, массивов, указателей, сборки мусора ...
источник
memcpy()
доступностью и наличием единственного стандартного средства эффективного копирования сегмента массива.