Пример кода с буферным потоком (метод get). Почему он не ведет себя так, как ожидалось? - PullRequest
2 голосов
/ 08 июня 2010

Это выдержка из программы c, которая должна демонстрировать переполнение буфера.

void foo()
{
  char arr[8];
  printf(" enter bla bla bla");
  gets(arr);
  printf(" you entered %s\n", arr);
}

Вопрос звучал так: «Сколько входных символов пользователь может ввести максимально, не создавая переполнение буфера»

Мой первоначальный ответ был 8, потому что массив char имеет длину 8 байт. Хотя я был почти уверен, что мой ответ был правильным, я попытался использовать большее количество символов и обнаружил, что предел количества символов, которые я могу ввести, прежде чем я получу ошибку сегментации, равен 11. (Я запускаю это в A VirtualBox Ubuntu)

Итак, мой вопрос: почему в этот 8-байтовый массив можно ввести 11 символов?

Ответы [ 5 ]

6 голосов
/ 08 июня 2010

Ваши символы фактически выходят за пределы определенного массива, что приводит к неопределенным результатам.Вы не увидите эффекта до тех пор, пока не перепишете часть памяти, которая используется для чего-то другого.

Язык и среда выполнения ничего не делают, чтобы предотвратить переполнение буфера, и именно поэтому эти ошибкитак плохо и иногда трудно отследить.

По этим причинам такие функции, как gets, становятся устаревшими для более безопасных функций (в данном случае getline), которые запрашивают длину массива, в котором они будут хранитьсяданные.

См .: http://crasseux.com/books/ctutorial/gets.html

Кроме того, вы можете надежно хранить только 7 символов, поскольку вам нужен восьмой для нулевого терминатора.

3 голосов
/ 09 июня 2010

(я запускаю это на VirtualBox Ubuntu) Так что мой вопрос: почему это можно ввести 11 символов в это 8 байтовый массив?

11 + 1 для нулевого завершения = 12 символов. Сбой IOW происходит, когда gets () записывает 13 символов в arr [8].

Вы не опубликовали точную трассировку стека, но по моему опыту она должна была вылететь после возврата foo ().

Кадр стека (с for void foo () + gets ()) будет выглядеть (*):

  • <нижний адрес памяти>
  • получает () локальные переменные
  • сохраненный указатель стека в момент вызова get () (так называемый «пролог»)
  • обратный адрес, указывает на foo ()
  • foo () локальные переменные (ваш символ arr [8])
  • сохраненный указатель стека в момент вызова get ()
  • обратный адрес, указывает на вызывающего foo ()
  • <старший адрес памяти>

Из всей информации наиболее важными битами являются адрес возврата и сохраненный указатель стека. И запись 13-го байта в вашем случае, вероятно, повредила сохраненный указатель стека функции foo (). Весьма вероятно, что вызов следующего printf () будет успешным, так как указатель стека все еще действителен (последний изменен при возврате из get ()). Но возврат из foo () приведет к восстановлению сохраненного указателя стека (теперь поврежденного) в foo (), и тогда любое действие, обращающееся к стеку из вызывающей функции, перейдет на неверный адрес.

Из моего опыта это наиболее вероятный сценарий. Когда стек поврежден, очень сложно точно сказать, что произойдет.

(*) Для получения более подробной информации о структуре фрейма стека посмотрите ABI - двоичный интерфейс приложения - для вашей архитектуры: например, IA-32 ABI для Intel i386 или AMD64 ABI для AMD64.

3 голосов
/ 08 июня 2010

Возможно из-за выравнивания и / или заполнения. Там может быть некоторая «запасная» память, которая на самом деле не используется, поэтому, когда вы перезаписываете ее, ничего не ломается. Это не значит, что это правильно или что оно работает, просто то, что оно сейчас не работает, для вас, на той машине, где используется этот компилятор, стул, цвет волос и т. Д.

1 голос
/ 08 июня 2010

В C ничто не мешает вам пройти конец массива.Когда вы помещаете строку в память, она заполняет ваш массив, а затем продолжает заполнять память, пока что-то на самом деле не остановит ее.В более крупных программах это может означать перезапись других переменных в памяти, что приводит к ошибкам, которые очень трудно отследить.В отдельном примечании вы можете подумать о том, как C находит конец строки, когда определяет, какой размер строки поместится в ячейку памяти.

1 голос
/ 08 июня 2010

Просто оказалось, что вам достаточно свободной памяти для хранения дополнительных данных.Вы никогда не должны полагаться на это и всегда держать данные в пределах массива.

Количество символов, фактически разрешенных в вашем примере, составляет 7 "стандартных" символов плюс символ NULL (всего 8).

...