Насколько опасен доступ к массиву за его пределами (в C)? Иногда может случиться, что я читаю извне массива (теперь я понимаю, что затем я получаю доступ к памяти, используемой некоторыми другими частями моей программы или даже за ее пределами), или я пытаюсь установить значение для индекса вне массива. Программа иногда вылетает, но иногда просто запускается, только давая неожиданные результаты.
Теперь я хотел бы знать, насколько это опасно на самом деле? Если это повредит моей программе, это не так плохо. Если, с другой стороны, это что-то нарушает вне моей программы, потому что мне каким-то образом удалось получить доступ к какой-то совершенно не связанной памяти, то я думаю, что это очень плохо. Я читал много «что-нибудь может случиться», «сегментация может быть наименее плохой проблемой» , «ваш жесткий диск может стать розовым, а единороги могут петь под вашим окном», что все хорошо, но в чем на самом деле опасность?
Мои вопросы:
- Может ли чтение значений вне массива повредить что-нибудь помимо моей программы? Я мог бы представить, что просто глядя на вещи ничего не изменится, или это, например, изменит атрибут «последний раз открыт» файла, к которому я попал?
- Может ли установка значений вне массива повредить что-нибудь помимо моей программы? Из этого вопроса переполнения стека я понял, что есть возможность доступа к любой ячейке памяти, что нет гарантии безопасности.
- Теперь я запускаю свои маленькие программы из XCode. Обеспечивает ли это дополнительную защиту моей программы, когда она не может выйти за пределы собственной памяти? Может ли это повредить XCode?
- Любые рекомендации о том, как безопасно выполнить мой по своей сути глючный код?
Я использую OSX 10.7, Xcode 4.6.
Ответы:
Что касается стандарта ISO C (официальное определение языка), доступ к массиву за его пределами имеет « неопределенное поведение ». Прямое значение этого:
Ненормативная записка распространяется на это:
Такова теория. Какова реальность?
В «лучшем» случае вы получите доступ к некоторому фрагменту памяти, который принадлежит либо вашей текущей запущенной программе (что может привести к неправильной работе вашей программы), либо не принадлежит вашей текущей запущенной программе (что, вероятно, приведет к тому, что ваша программа сбой с чем-то вроде ошибки сегментации). Или вы можете попытаться записать в память, которой владеет ваша программа, но она помечена только для чтения; это, вероятно, также приведет к сбою вашей программы.
Это предполагает, что ваша программа работает в операционной системе, которая пытается защитить одновременно запущенные процессы друг от друга. Если ваш код работает на «голом железе», скажем, если он является частью ядра ОС или встроенной системы, то такой защиты нет; Ваш неправильный код - это то, что должно было обеспечить такую защиту. В этом случае возможности повреждения значительно выше, включая, в некоторых случаях, физический ущерб оборудованию (или вещам или людям поблизости).
Даже в защищенной среде ОС защита не всегда на 100%. Например, существуют ошибки операционной системы, которые позволяют непривилегированным программам получать root (административный) доступ. Даже с обычными пользовательскими привилегиями неисправная программа может потреблять чрезмерные ресурсы (ЦП, память, диск), что может привести к выходу из строя всей системы. Многие вредоносные программы (вирусы и т. Д.) Используют переполнения буфера для получения несанкционированного доступа к системе.
(Один исторический пример: я слышал, что на некоторых старых системах с памятью ядра , многократный доступ к одной области памяти в замкнутом цикле может буквально вызвать расплавление этого фрагмента памяти. Другие возможности включают в себя разрушение дисплея CRT и перемещение чтения / записать головку дисковода с частотой гармоник шкафа привода, заставляя его ходить по столу и падать на пол.)
И всегда есть о чем беспокоиться Скайнет .
Суть заключается в следующем: если вы могли бы написать программу, чтобы сделать что-то плохое намеренно , то, по крайней мере, теоретически возможно, что ошибочная программа могла бы сделать то же самое случайно .
На практике очень маловероятно, что ваша глючная программа, работающая в системе MacOS X, сделает что-то более серьезное, чем сбой. Но невозможно полностью предотвратить ошибочный код от действительно плохих действий.
источник
В целом, современные операционные системы (в любом случае, популярные) запускают все приложения в защищенных областях памяти с помощью диспетчера виртуальной памяти. Оказывается, это не очень ЛЕГКО (если можно так выразиться) просто читать или записывать в местоположение, которое существует в РЕАЛЬНОМ пространстве за пределами региона (-ов), которые были назначены / выделены вашему процессу.
Прямые ответы:
1) Чтение почти никогда не повредит непосредственно другому процессу, однако может косвенно повредить процесс, если вам случится прочитать значение KEY, используемое для шифрования, дешифрования или проверки программы / процесса. Чтение за пределами может иметь несколько неблагоприятных / неожиданных последствий для вашего кода, если вы принимаете решения на основе данных, которые вы читаете
2) Единственный способ, которым вы могли бы действительно повредить что-либо, записав в локатор, доступный по адресу памяти, - это если адрес памяти, в который вы пишете, на самом деле является аппаратным регистром (местоположение, которое на самом деле не для хранения данных, а для управления какой-то частью аппаратного обеспечения) не место в оперативной памяти. На самом деле, вы все равно обычно не повредите что-либо, если не пишете какое-то одноразовое программируемое место, которое нельзя перезаписать (или что-то в этом роде).
3) Как правило, запуск из отладчика запускает код в режиме отладки. Работа в режиме отладки позволяет TEND (но не всегда) останавливать ваш код быстрее, когда вы сделали что-то, что считается непрактичным или совершенно незаконным.
4) Никогда не используйте макросы, используйте структуры данных, в которые уже встроена проверка границ индекса массива и т. Д ....
ДОПОЛНИТЕЛЬНО Я должен добавить, что приведенная выше информация действительно только для систем, использующих операционную систему с окнами защиты памяти. Если вы пишете код для встроенной системы или даже системы, использующей операционную систему (в режиме реального времени или другую), у которой нет окон защиты памяти (или окон с виртуальной адресацией), следует соблюдать большую осторожность при чтении и записи в память. Также в этих случаях всегда следует применять методы кодирования SAFE и SECURE, чтобы избежать проблем с безопасностью.
источник
Не проверка границ может привести к неприятным побочным эффектам, включая дыры в безопасности. Одним из уродливых является выполнение произвольного кода . В классическом примере: если у вас есть массив фиксированного размера, и вы используете
strcpy()
для размещения предоставленной пользователем строки там, пользователь может дать вам строку, которая переполняет буфер и перезаписывает другие области памяти, включая кодовый адрес, куда ЦП должен возвращаться, когда ваша функция заканчивается.Это означает, что ваш пользователь может отправить вам строку, которая будет вызывать вашу программу по сути
exec("/bin/sh")
, которая превратит ее в оболочку, выполняя все, что он хочет в вашей системе, включая сбор всех ваших данных и превращение вашей машины в узел бот-сети.Смотрите Smashing The Stack для удовольствия и прибыли, чтобы узнать, как это можно сделать.
источник
foo[0]
черезfoo[len-1]
после того , как ранее использовал проверку вlen
отношении длины массива либо выполнить или пропустить фрагмент кода, компилятор должен чувствовать себя свободно работать , что другой код безоговорочно даже если бы приложение владело хранилищем после массива и эффектами чтения, он был бы доброкачественным, но эффект вызова другого кода не был бы.Ты пишешь:
Скажем так: зарядите пистолет. Направьте его за окно без особой цели и огня. В чем опасность?
Проблема в том, что вы не знаете. Если ваш код перезаписывает что-то, что приводит к сбою вашей программы, у вас все в порядке, потому что это остановит его в определенном состоянии. Однако, если это не терпит крах, тогда проблемы начинают возникать. Какие ресурсы находятся под контролем вашей программы и что она может с ними сделать? Какие ресурсы могут попасть под контроль вашей программы и что она может с ними сделать? Я знаю по крайней мере одну серьезную проблему, которая была вызвана таким переполнением. Проблема заключалась в, казалось бы, бессмысленной статистической функции, которая испортила некоторую несвязанную таблицу преобразования для производственной базы данных. Результатом была некоторая очень дорогая очистка впоследствии. На самом деле, было бы намного дешевле и проще справиться, если бы эта проблема отформатировала жесткие диски ... другими словами: розовые единороги могут быть вашей наименьшей проблемой.
Идея о том, что ваша операционная система защитит вас, оптимистична. Если возможно, постарайтесь не писать за пределами.
источник
Если вы не запустите вашу программу от имени пользователя root или любого другого привилегированного пользователя, это не повредит вашей системе, поэтому обычно это может быть хорошей идеей.
Записывая данные в какую-либо произвольную область памяти, вы не будете напрямую "портить" любую другую программу, запущенную на вашем компьютере, поскольку каждый процесс выполняется в своем собственном пространстве памяти.
Если вы попытаетесь получить доступ к любой памяти, не выделенной вашему процессу, операционная система остановит выполнение вашей программы с ошибкой сегментации.
Таким образом, напрямую (без запуска от имени root и прямого доступа к файлам, таким как / dev / mem) нет никакой опасности, что ваша программа будет мешать любой другой программе, работающей в вашей операционной системе.
Тем не менее - и, вероятно, это то, о чем вы слышали с точки зрения опасности, - слепая случайная запись случайных данных в случайные места памяти случайно приводит к тому, что вы можете повредить все, что сможете.
Например, ваша программа может захотеть удалить определенный файл, заданный именем файла, хранящимся где-то в вашей программе. Если случайно вы просто перезаписали место, где хранится имя файла, вы можете удалить совершенно другой файл.
источник
NSArray
S в Objective-C назначается определенный блок памяти. Превышение границ массива означает, что вы будете обращаться к памяти, которая не назначена массиву. Это означает:С точки зрения вашей программы вы всегда хотите знать, когда ваш код выходит за границы массива. Это может привести к возвращению неизвестных значений, что приведет к сбою приложения или предоставлению неверных данных.
источник
NSArrays
есть за пределами исключения. И этот вопрос, кажется, о массиве C.Возможно, вы захотите попробовать использовать
memcheck
инструмент в Valgrind при тестировании своего кода - он не будет обнаруживать нарушения отдельных границ массива в пределах стекового фрейма, но он должен улавливать многие другие виды проблем с памятью, в том числе те, которые могут привести к едва заметному, более широкому проблемы, выходящие за рамки одной функции.Из руководства:
ETA: Хотя, как говорится в ответе Kaz, это не панацея и не всегда дает наиболее полезные результаты, особенно когда вы используете захватывающие шаблоны доступа.
источник
Если вы когда-нибудь занимаетесь программированием на системном уровне или программированием встраиваемых систем, очень плохие вещи могут случиться, если вы пишете в произвольные области памяти. Старые системы и многие микроконтроллеры используют IO с отображением в памяти, поэтому запись в область памяти, которая сопоставляется с периферийным регистром, может привести к хаосу, особенно если это происходит асинхронно.
Примером является программирование флэш-памяти. Режим программирования на микросхемах памяти включается путем записи определенной последовательности значений в определенные места внутри диапазона адресов микросхемы. Если другой процесс будет записывать в любое другое место в чипе во время его работы, это приведет к сбою цикла программирования.
В некоторых случаях аппаратное обеспечение будет переносить адреса (наиболее значимые биты / байты адреса игнорируются), поэтому запись по адресу, выходящему за пределы физического адресного пространства, фактически приведет к записи данных прямо в середине.
И, наконец, старые процессоры, такие как MC68000, могут быть заблокированы до такой степени, что только аппаратный сброс может привести их в действие. Я не работал над ними пару десятилетий, но я полагаю, что когда он столкнулся с ошибкой шины (несуществующей памятью) при попытке обработать исключение, он просто остановился бы до тех пор, пока не был произведен аппаратный сброс.
Моя самая большая рекомендация - откровенный штекер для продукта, но я не заинтересован в нем, и я никоим образом не связан с ним - но основан на паре десятилетий программирования на C и встроенных системах, где надежность была критической, ПК Gimpel Lint не только обнаружит подобные ошибки, но и сделает из вас лучшего программиста на C / C ++, постоянно обращая внимание на вредные привычки.
Я также рекомендую прочитать стандарт кодирования MISRA C, если вы можете получить копию от кого-то. Я не видел ни одного недавнего из них, но в старые времена они давали хорошее объяснение того, почему вы не должны / не должны делать то, что они освещают.
Не знаю о вас, но примерно во 2-й или 3-й раз, когда я получаю coredump или зависание из любого приложения, мое мнение о том, какая компания его произвела, снижается вдвое. В 4-й или 5-й раз и независимо от того, какая упаковка находится, она становится полкой, и я вбиваю деревянную колу через центр упаковки / диска, в который она попала, просто чтобы убедиться, что она никогда не вернется, чтобы преследовать меня.
источник
Я работаю с компилятором для чипа DSP, который намеренно генерирует код, который обращается к концу массива из кода C, который не имеет!
Это потому, что циклы структурированы так, что конец итерации предварительно выбирает некоторые данные для следующей итерации. Таким образом, данные, предварительно выбранные в конце последней итерации, фактически никогда не используются.
Подобный код на C вызывает неопределенное поведение, но это лишь формальность из документа стандартов, который касается максимальной переносимости.
Чаще всего это не так, программа, которая выходит за границы, не умно оптимизирована. Это просто глючит. Код извлекает некоторое мусорное значение и, в отличие от оптимизированных циклов вышеупомянутого компилятора, код затем использует значение в последующих вычислениях, тем самым разрушая их.
Стоит обнаруживать подобные ошибки, и поэтому стоит сделать поведение неопределенным даже по одной этой причине: во время выполнения может появиться диагностическое сообщение типа «переполнение массива в строке 42 файла main.c».
В системах с виртуальной памятью может случиться так, что массив будет выделен так, что следующий адрес находится в не отображенной области виртуальной памяти. Доступ будет бомбить программу.
Тем не менее, доступ к неинициализированным или выходящим за пределы значениям иногда является допустимым методом оптимизации, даже если он не является максимально переносимым. Именно поэтому инструмент Valgrind не сообщает о доступе к неинициализированным данным, когда такой доступ происходит, а только тогда, когда значение впоследствии используется каким-либо образом, что может повлиять на результат программы. Вы получаете диагностику типа «условная ветвь в xxx: nnn зависит от неинициализированного значения», и иногда бывает трудно отследить, где оно происходит. Если бы все такие обращения были немедленно зафиксированы, было бы много ложных срабатываний, возникающих из оптимизированного компилятором кода, а также из правильно оптимизированного вручную кода.
Говоря об этом, я работал с некоторым кодеком от поставщика, который выдавал эти ошибки при портировании на Linux и запуске под Valgrind. Но продавец убедил меня, что только несколько битиспользуемого значения на самом деле происходило из неинициализированной памяти, и эти биты тщательно избегались логикой. Использовались только хорошие биты значения, и Valgrind не может отследить отдельный бит. Неинициализированный материал был получен при чтении слова после конца потока битов закодированных данных, но код знает, сколько битов находится в потоке, и не будет использовать больше битов, чем есть на самом деле. Поскольку доступ за концом массива битового потока не наносит вреда архитектуре DSP (после массива нет виртуальной памяти, нет портов с отображением в памяти и адрес не переносится), это допустимый метод оптимизации.
«Неопределенное поведение» на самом деле ничего не значит, потому что согласно ISO C простое включение заголовка, который не определен в стандарте C, или вызов функции, которая не определена в самой программе или стандарте C, являются примерами неопределенного поведение. Неопределенное поведение не означает «не определено никем на планете», просто «не определено стандартом ISO C». Но, конечно же , иногда неопределенное поведение действительно является абсолютно не определен никем.
источник
Кроме вашей собственной программы, я не думаю, что вы что-то сломаете, в худшем случае вы попытаетесь прочитать или записать адрес памяти, который соответствует странице, которую ядро не назначило вашим процессам, сгенерировав соответствующее исключение и быть убитым (я имею в виду, ваш процесс).
источник