Насколько опасно доступ к массиву за пределами границ?
Насколько опасен доступ к массиву за пределами его границ (в C)? Иногда бывает, что я читаю из-за пределов массива (теперь я понимаю, что я потом обращаюсь к памяти, используемой некоторыми другими частями моей программы или даже за ее пределами), или я пытаюсь установить значение для индекса за пределами массива. Программа иногда вылетает, но иногда просто запускается, давая неожиданные результаты.
Теперь, что я хотел бы знать, насколько опасно это на самом деле? Если это наносит ущерб моей программе, это не так уж плохо. Если, с другой стороны, это нарушает что-то вне моей программы, потому что мне каким-то образом удалось получить доступ к какой-то совершенно несвязанной памяти, тогда я думаю, что это очень плохо. Я читал много "что-нибудь может случиться", "сегментация может быть наименее плохой проблемой", "ваш жесткий диск может стать розовым, а единороги могут петь под вашим окном", что все хорошо, но что на самом деле представляет опасность?
Мои вопросы:
- Может ли считывание значений из-за пределов массива повреждать что-либо отдельно от моей программы? Я бы предположил, что просто смотреть на вещи ничего не меняет, или, например, изменит атрибут "последний раз открыт" файла, к которому я попал?
- Можете ли выставлять значения вне вне массива, что-то кроме моей программы? Из этого вопроса о переполнении стека я понимаю, что можно получить доступ к любой ячейке памяти, что нет гарантии безопасности.
- Теперь я запускаю свои небольшие программы из XCode. Оказывает ли это некоторую дополнительную защиту вокруг моей программы, где она не может выйти за пределы собственной памяти? Может ли это нанести вред XCode?
- Любые рекомендации о том, как безопасно запускать свой код с ошибкой?
Я использую OSX 10.7, Xcode 4.6.
Ответы
Ответ 1
Что касается стандарта ISO C (официальное определение языка), доступ к массиву за пределами его границ имеет "поведение undefined". В буквальном смысле это:
при использовании непереносимой или ошибочной программы или ошибочных данных, для которых настоящий международный стандарт не налагает никаких требования
Ненормативная заметка расширяется по этому поводу:
Возможное поведение undefined варьируется от игнорирования ситуации полностью с непредсказуемыми результатами, вести себя во время перевода или выполнения программы документированным образом, характерным для (с выдачей диагностического сообщения или без него), чтобы прекращение перевода или исполнения (с выдачей диагностическое сообщение).
Итак, что теория. Какая реальность?
В "лучшем" случае вы получите доступ к некоторой части памяти, которая принадлежит вашей текущей программе (что может привести к неправильной работе вашей программы) или не принадлежащей вашей текущей программе (что, вероятно, вызовет ваша программа для сбоя с чем-то вроде сбоя сегментации). Или вы можете попытаться записать в память, что ваша программа владеет, но которая отмечена только для чтения; это, вероятно, также приведет к сбою вашей программы.
Это означает, что ваша программа работает под операционной системой, которая пытается защитить одновременно выполняющиеся процессы друг от друга. Если ваш код работает на "голом металле", скажем, если он является частью ядра ОС или встроенной системы, тогда такой защиты нет; ваш неправильный код - это то, что должно было обеспечить эту защиту. В этом случае возможности повреждения значительно больше, в том числе, в некоторых случаях, физический ущерб аппаратной части (или вещам или близким людям).
Даже в защищенной ОС, защита не всегда на 100%. Существуют ошибки операционной системы, которые позволяют непривилегированным программам получать root (административный) доступ, например. Даже с обычными привилегиями пользователя неисправная программа может потреблять чрезмерные ресурсы (процессор, память, диск), возможно, сбивая всю систему. Многие вредоносные программы (вирусы и т.д.) Используют переполнения буфера для получения несанкционированного доступа к системе.
(Один исторический пример: я слышал, что на некоторых старых системах с основной памятью, неоднократно обращался к одному месту памяти в узком цикле может в буквальном смысле привести к такому расходу памяти. Другие возможности включают в себя уничтожение экрана CRT и перемещение головки чтения/записи на диске с гармонической частотой шкафа привода, заставляя его ходить по столу и падать на пол.)
И всегда Skynet беспокоиться.
Суть в том, что если бы вы могли написать программу, чтобы сделать что-то плохое намеренно, это, по крайней мере, теоретически возможно, что багги-программа может сделать то же самое случайно.
На практике очень маловероятно, что ваша багги-программа, работающая в системе MacOS X, сделает что-нибудь более серьезное, чем авария. Но невозможно полностью предотвратить ошибочный код ошибки.
Ответ 2
В общем, операционные системы сегодня (в любом случае, популярные) запускают все приложения в областях защищенной памяти, используя диспетчер виртуальной памяти. Оказывается, что не просто ЛЕГКО (per say) просто читать или записывать в место, существующее в REAL, за пределами региона (областей), которые были назначены/выделены для вашего процесса.
Прямые ответы:
1) Чтение почти никогда не приведет к прямому повреждению другого процесса, но может косвенно привести к повреждению процесса, если вы прочитали значение KEY, используемое для шифрования, дешифрования или проверки программы/процесса. Чтение за пределами может иметь несколько неблагоприятные/неожиданные последствия для вашего кода, если вы принимаете решения на основе данных, которые вы читаете.
2) Единственный способ, которым вы действительно могли УБЫТЬ что-то, путем записи в доступный по адресу памяти документ, - это тот адрес, который вы пишете, на самом деле является аппаратным регистром (место, которое на самом деле не для хранения данных, а для контролируя некоторые аппаратные средства), а не место RAM. Фактически, вы все равно обычно не наносите ущерб чему-либо, если не пишете какое-то программируемое время, которое не перезаписывается (или что-то в этом роде).
3) Обычно запуск изнутри отладчика запускает код в режиме отладки. Работа в режиме отладки TEND (но не всегда) останавливает ваш код быстрее, когда вы делаете что-то, что считается непрактичным или совершенно незаконным.
4) Никогда не используйте макросы, используйте структуры данных, которые уже имеют встроенную проверку индексов массива и т.д.
ДОПОЛНИТЕЛЬНОЕ
Я должен добавить, что приведенная выше информация действительно предназначена только для систем, использующих операционную систему с окнами защиты памяти. Если вы пишете код для встроенной системы или даже системы, использующей операционную систему (в режиме реального времени или другого), которая не имеет окон защиты памяти (или виртуальных адресных окон), для которых нужно читать гораздо больше осторожности при чтении и записи в память. Также в этих случаях для предотвращения проблем с безопасностью следует всегда применять методы безопасности SAFE и SECURE.
Ответ 3
Не проверять границы может привести к уродливым побочным эффектам, включая дыры в безопасности. Одним из уродливых является произвольное выполнение кода. В классическом примере: если у вас есть массив фиксированного размера и используйте strcpy()
для ввода строки, предоставленной пользователем, пользователь может дать вам строку, которая переполняет буфер и перезаписывает другие ячейки памяти, включая адрес кода, где ЦП должен возвращаться когда ваша функция заканчивается.
Это означает, что ваш пользователь может отправить вам строку, которая заставит вашу программу по существу вызвать exec("/bin/sh")
, которая превратит ее в оболочку, выполняя все, что он хочет в вашей системе, включая сбор всех ваших данных и превращение вашего компьютера в бот-сеть node.
Подробнее о том, как это можно сделать, см. Smashing The Stack For Fun and Profit.
Ответ 4
Вы пишете:
Я читал много "что-нибудь может случиться", "сегментация может быть наименее плохая проблема", ваш жесткий диск может стать розовым, а единороги могут петь под окном ", что все хорошо, но что на самом деле опасность?
Давайте скажем так: заряжаем пистолет. Направьте его за окно без какой-либо конкретной цели и огня. В чем опасность?
Проблема в том, что вы не знаете. Если ваш код перезаписывает что-то, что приводит к сбою вашей программы, вы в порядке, потому что она остановит ее в определенном состоянии. Однако, если он не сбой, возникают проблемы. Какие ресурсы находятся под контролем вашей программы и что они могут сделать с ними? Какие ресурсы могут находиться под контролем вашей программы и что они могут сделать с ними? Я знаю, по крайней мере, одну серьезную проблему, вызванную таким переполнением. Проблема заключалась в кажущейся бессмысленной статистической функции, которая испортила некоторую несвязанную таблицу преобразования для производственной базы данных. Результатом стала некоторая очень дорогая очистка после этого. На самом деле это было бы намного дешевле и проще в обращении, если бы эта проблема отформатировала жесткие диски... другими словами: розовые единороги могут быть вашей наименее проблемой.
Идея, что ваша операционная система будет защищать вас, оптимистична. Если возможно, старайтесь избегать записи за пределы.
Ответ 5
Не запускать свою программу в качестве пользователя root или любого другого привилегированного пользователя, это не повредит какой-либо вашей системе, поэтому обычно это может быть хорошей идеей.
Записывая данные в какую-либо ячейку произвольной памяти, вы не будете непосредственно "повреждать" любую другую программу, запущенную на вашем компьютере, поскольку каждый процесс запускает в ней собственное пространство памяти.
Если вы попытаетесь получить доступ к любой памяти, не выделенной для вашего процесса, операционная система остановит вашу программу от выполнения с ошибкой сегментации.
Таким образом, прямо (без запуска от имени root и прямого доступа к файлам, таким как /dev/mem ) нет никакой опасности, что ваша программа будет мешать любой другой программе, запущенной в вашей операционной системе.
Тем не менее - и, возможно, это то, о чем вы слышали с точки зрения опасности, - слепо записывая случайные данные в случайные ячейки памяти случайно, вы можете повредить все, что можете нанести.
Например, ваша программа может захотеть удалить определенный файл, заданный именем файла, хранящимся где-то в вашей программе. Если случайно вы просто перезапишите место, где хранится имя файла, вы можете удалить совсем другой файл.
Ответ 6
Помимо вашей собственной программы, я не думаю, что вы что-то сломаете, в худшем случае вы попытаетесь прочитать или записать с адреса памяти, соответствующего странице, которую ядро не назначало вашим процессам, генерируя правильное исключение и убийство (я имею в виду, ваш процесс).
Ответ 7
NSArray
в Objective-C назначается определенный блок памяти. Превышение границ массива означает, что вы будете получать доступ к памяти, которая не назначена массиву. Это означает:
- Эта память может иметь любое значение. Нет никакого способа узнать, действительно ли данные действительны в зависимости от вашего типа данных.
- Эта память может содержать конфиденциальную информацию, такую как закрытые ключи или другие учетные данные пользователя.
- Адрес памяти может быть недействительным или защищенным.
- Память может иметь изменяющееся значение, поскольку к ней обращается другая программа или поток.
- В других вещах используется адресное пространство памяти, например порты с отображением памяти.
- Запись данных на неизвестный адрес памяти может привести к сбою вашей программы, перезаписать пространство памяти ОС и вообще привести к взрыву солнца.
С точки зрения вашей программы вы всегда хотите знать, когда ваш код превышает границы массива. Это может привести к возврату неизвестных значений, что приведет к сбою приложения или предоставлению недействительных данных.
Ответ 8
Вы можете попробовать использовать инструмент memcheck
в Valgrind, когда вы проверяете свой код - он не будет отслеживать нарушения границ массива в кадре стека, но он должен улавливать многие другие проблемы с памятью, в том числе те, которые могут вызывать тонкие и более широкие проблемы, выходящие за рамки одной функции.
Из руководства:
Memcheck - это детектор ошибок памяти. Он может обнаруживать следующие проблемы, общие для программ C и С++.
- Доступ к памяти не требуется, например. перекрытие и блокирование блоков кучи, превышение вершины стека и доступ к памяти после ее освобождения.
- Использование значений undefined, то есть значений, которые не были инициализированы или были получены из других значений undefined.
- Неправильное освобождение памяти кучи, например блокирование кучи двойной освобождения или несоответствие использования malloc/new/new [] по сравнению с бесплатным/удалением/удалением []
- Перекрытие указателей src и dst в memcpy и связанных с ними функциях.
- Утечки памяти.
ETA: Хотя, как говорит Каз, это не панацея и не всегда дает самый полезный результат, особенно когда вы используете интересные шаблоны доступа.
Ответ 9
Я работаю с компилятором для чипа DSP, который намеренно генерирует код, который обращается к одному из концов конца массива из кода C, который не работает!
Это связано с тем, что циклы структурированы так, что конец итерации предваряет некоторые данные для следующей итерации. Таким образом, базовая точка, запрограммированная в конце последней итерации, никогда не используется.
Написание кода C, подобного этому, вызывает поведение undefined, но это только формальность из документа стандартов, который относится к максимальной переносимости.
Чаще всего это не так, программа, которая выходит за пределы, не умно оптимистична. Это просто глючит. Код извлекает некоторое количество мусора и, в отличие от оптимизированных циклов вышеупомянутого компилятора, код затем использует значение в последующих вычислениях, тем самым искажая их.
Стоит поймать такие ошибки, и поэтому стоит повести поведение undefined даже для этой причины: так, чтобы время выполнения могло вызвать диагностическое сообщение типа "переполнение массива в строке 42 основного". с".
В системах с виртуальной памятью может быть выделен массив таким образом, чтобы следующий адрес находился в незанятой области виртуальной памяти. Затем доступ запустит программу.
Отметим, что в C нам разрешено создавать указатель, который находится за концом массива. И этот указатель должен сравнивать больше, чем любой указатель на внутренность массива. Это означает, что реализация C не может разместить массив в конце памяти, где один плюс адрес будет обертываться и выглядеть меньше, чем другие адреса в массиве.
Тем не менее, доступ к значениям неинициализированных или вне границ иногда является допустимым методом оптимизации, даже если он не является максимально переносимым. Это, например, почему инструмент Valgrind не сообщает о доступе к неинициализированным данным, когда эти обращения происходят, но только тогда, когда значение впоследствии используется каким-то образом, что может повлиять на результат программы. Вы получаете такую диагностику, как "условная ветвь в xxx: nnn зависит от неинициализированного значения", и иногда бывает трудно отследить, откуда она происходит. Если бы все такие обращения были немедленно захвачены, было бы много ложных срабатываний, возникающих из кода, оптимизированного компилятором, а также правильно оптимизированного вручную кода.
Говоря об этом, я работал с некоторым кодеком от поставщика, который выдавал эти ошибки при переносе в Linux и запускался под Valgrind. Но вендор убедил меня, что только несколько бит используемого значения действительно пришли из неинициализированной памяти, и эти биты были тщательно устранены логикой. Использовались только хорошие бит значения, и Valgrind не имеет возможности отслеживать отдельные бит. Неинициализированный материал приходил от чтения слова за конец бит потока закодированных данных, но код знает, сколько бит находится в потоке и не будет использовать больше бит, чем есть на самом деле. Поскольку доступ за пределами массива битового потока не наносит никакого ущерба архитектуре DSP (после массива нет виртуальной памяти, нет портов с отображением памяти и адрес не обертывается), это допустимая методика оптимизации.
"Undefined поведение" на самом деле мало значит, потому что согласно ISO C, просто включающий заголовок, который не определен в стандарте C, или вызов функции, которая не определена в самой программе или стандарте C, являются примерами поведения undefined. Поведение undefined не означает "не определено кем-либо на планете" просто "не определено стандартом ISO C". Но, конечно, иногда поведение undefined действительно абсолютно не определено кем-либо.
Ответ 10
Если вы когда-либо выполняете программирование на уровне системы или программируете встроенные системы, очень плохое может случиться, если вы пишете в случайные ячейки памяти. Старые системы и многие микроконтроллеры используют IO памяти с отображением памяти, поэтому запись в ячейку памяти, которая отображается в периферийном регистре, может привести к хаосу, особенно если она выполняется асинхронно.
Примером является программирование флэш-памяти. Режим программирования на микросхемах памяти активируется путем записи определенной последовательности значений в определенные местоположения внутри диапазона адресов чипа. Если другой процесс должен был записываться в любое другое место в чипе во время его продолжения, это приведет к сбою цикла программирования.
В некоторых случаях аппаратное обеспечение будет обертывать адреса (большинство значимых битов/байтов адреса игнорируются), поэтому запись на адрес за пределами физического адресного пространства фактически приведет к тому, что данные будут записываться прямо в середине вещей.
И, наконец, более старые процессоры, такие как MC68000, могут быть заблокированы до такой степени, что только аппаратное обеспечение reset может вернуть их снова. Не работали над ними в течение нескольких десятилетий, но я считаю, что когда он столкнулся с ошибкой шины (несуществующей памяти), пытаясь обработать исключение, он просто остановился, пока не будет заявлено аппаратное обеспечение reset.
Моя самая большая рекомендация - вопиющая пробная версия продукта, но у меня нет личного интереса к ней, и я никоим образом не связан с ними, но на основе нескольких десятилетий программирования C и встроенных систем, где надежность была критической, Gimpel PC Lint не только обнаружит подобные ошибки, но и сделает из вас лучшего программиста на C/С++, постоянно ухаживая за вами о вредных привычках.
Я бы также рекомендовал прочитать стандарт кодирования MISRA C, если вы можете уловить копию от кого-то. Я не видел последних, но в старые времена они дали хорошее объяснение, почему вы должны/не должны делать то, что они покрывают.
Не знаю о вас, но о 2-м или в 3-й раз я получаю coredump или зависание из любого приложения, мое мнение о том, какая компания произвела его, уменьшилась наполовину. 4-й или 5-й раз, и все, что пакет становится стеллажом, и я управляю деревянной ставкой через центр пакета/диска, в который он пришел, чтобы убедиться, что он никогда не возвращается, чтобы преследовать меня.
Ответ 11
Если это программа пользовательского пространства и работает на защищенной ОС, такой как linux, худшее, что вы увидите, это ошибка сегментации.