Переполнение буфера вызывает segfault только тогда, когда важный указатель перезаписан?
Предполагая, что у меня есть программа, которая объявляет char buffer[size]
и другая переменная и использует gets(buffer);
записать данные в buffer
, Если gets
обеспечивается слишком длинный ввод, затем он будет выливаться из буфера в следующую переменную (при условии, что переменная находится в следующем адресе после buffer
):
void f(){
char str[12] = "hello_world";
char buffer[1];
gets(buffer); // provided with a random char and then "hello_kitty"
printf("str = %s\n", str); // no crash. Just prints "hello_kitty" as expected
}
При запуске с "легальным вводом" (т.е. не переполнением 2-го буфера) все было в порядке. Это было нормально, даже когда я немного переполнил буфер, но после слишком большого ввода программа вылетела.
Как я понимаю, это (имеется в виду - не переполнение 2-го буфера) не должно вызывать сбоев. Причиной сбоя может быть повреждение памяти, в которой хранится указатель инструкции, поэтому теперь он будет указывать на недопустимый адрес (это ошибка страницы?).
Это правильно? Может ли плохая запись, которая не затрагивает ни один из указателей стека / кадра / инструкции, вызвать сбой?
3 ответа
Если бы вы читали, как разбить стек для удовольствия и выгоды от Aleph One, вы бы знали, что компьютеры взламываются из- за переполнения буферов (и перезаписи важных указателей)...
... и, как говорится в статье, segfault не существует, потому что часть кода эксплойта часто exit(0);
если злоумышленник не сможет восстановить программу лучше.
Итак, чтобы ответить на ваши вопросы:
это ошибка страницы?
Не обязательно. Такая концепция не должна существовать в мире C.
Это правильно? Может ли плохая запись, которая не затрагивает ни один из указателей стека / кадра / инструкции, вызвать сбой?
Конечно, но на это нельзя полагаться. Вот почему люди предполагают, что неопределенное поведение, такое как переполнение буфера, может вызвать действительно радикальные вещи (такие как драконы и ядерные холокосты).
Из других комментариев я вижу намерение, стоящее за этим вопросом:
В этом случае вы на правильном пути... Единственное, что вам не хватает, это то, что упомянутые вами случайные символы являются машинным кодом, и указатель инструкции необходимо перезаписать, чтобы он указывал на этот машинный код. Aleph One рассказывает об этом более подробно, хотя ваш пробег, безусловно, будет отличаться, поскольку это древний документ.
Если бы не эта проблема со многими неопределенными поведениями, все эксперты C, вероятно, перехватывали бы сигналы для восстановления своего программного обеспечения без проблем, но, увы, C не Java, лучше всего следовать примеру экспертов и избегать неопределенного поведения...
Предположим, что достойный гид постарается помочь вам избежать неопределенного поведения, где это возможно. Есть по крайней мере одно приличное руководство в форме книги, которую мы называем "библией" форм, частично потому, что она была написана некоторыми авторитетными людьми. На нем большая синяя буква "С", а сегодня "ВТОРОЕ ИЗДАНИЕ" написано красным...
Будь это упражнение, чтобы найти эту книгу. У меня есть уверенность.
Чтобы ответить на ваш вопрос кратко, нет. Продолжайте читать для более длинного ответа.
Наиболее типичным способом, которым переполнение буфера приводит к ошибке сегментации, является то, что переполненный буфер находится в стеке, и переполнение перезаписывает указатель возврата. Когда указатель возврата возвращается в указатель инструкции после возврата функции, ошибка сегментации обычно возникает, потому что процессор пытается прочитать память, к которой у вас нет доступа.
Это последнее предложение очень важно. Перезапись указателя возврата является лишь одним из способов возникновения ошибки сегментации. Фактически, любое переполнение буфера, которое перезаписывает адрес памяти, который впоследствии используется для доступа к памяти, может привести к ошибке сегментации или, если это попытка записи в память, к нарушению доступа.
Например, представьте, что у вас есть структура, выделенная в куче. Эта структура принимает эту форму:
struct sample_struct {
char bytes[20];
struct sample_struct *next;
};
Если данные копируются в bytes
член без правильной проверки границ, следующий элемент в памяти будет next
указатель. Если этот указатель будет перезаписан, а затем будет предпринята попытка чтения из него, очень вероятно, что произойдет сбой сегментации, если предположить, что значение, представленное здесь, представляет адрес памяти вне вашего контроля. Если вам случится получить адрес внутри вашего пространства памяти, результатом будет попытка интерпретировать расположенные там байты как struct sample_struct
, вероятно, приводит к другим проблемам.
Как примечание, не думайте, что для перезаписи указателя в приведенной выше структуре примера требуется всего 21–24 байта; возможно, что выделение памяти для этой структуры будет включать дополнительные байты для выравнивания, если вы не дадите указанию компилятору упаковать структуру.
Переполнение буфера вызывает segfault только тогда, когда важный указатель перезаписан?
Нет необходимости, давайте начнем с начала. В большинстве процессоров память может быть сегментирована на большие куски или маленькие куски. Большой кусок обычно называется сегментом. Небольшой кусок обычно называется страницей.
Если вы выполняете запись за буфером (переполнение буфера) и "вещи", которые перезаписываются за этим буфером, принадлежат одному и тому же процессу, немедленная ошибка не возникает.
SEGMENT n | SEGMENT n
buffer1 | buffer2
+-----+-----+-----+-----+-----+-----+-----+-----+
| 'a' | 'a' | 'a' | 'a' | 'a' | 'a' | 'a' | 'a' |
+-----+-----+-----+-----+-----+-----+-----+-----+
^-----^-----^-----^-----^-----^-----^-----^-------- Writing to variables of same process (UB)
SEGFAULT происходит, когда переполненный буфер заканчивается в конце сегмента памяти. Что находится за сегментом, так или иначе, в текущем состоянии этого процессора в настоящее время не определено. Таким образом, доступ к памяти в этом пустоте приведет к неисправности шины. Нет содержимого памяти для процессора. Он не знает, куда идти. Так что в этом случае ошибка является непосредственной катастрофой.
На x86 или ARM вы получаете SIGSEGV
при записи на страницу, которая не находится в какой-либо сопоставленной области, или страницу, которая была в области памяти, которая была отображена только для чтения, или чтение с адреса, который не находится ни в одной отображенной области.
END OF SEGMENT
|
SEGMENT n v SEGMENT n+1
+-----+-----+-----+-----+-----+-----+-----+-----+
| 'a' | 'a' | 'a' | 'a' | | | | |
+-----+-----+-----+-----+-----+-----+-----+-----+
^-----^-----^-----^-----^--------------------------- Writing to another segment
|
Writing here will cause SEGFAULT
SEGFAULT часто вызывается, когда адрес возврата счетчика программы (который находится в стеке за параметрами) был перезаписан, и после возврата назад для продолжения после функции счетчик программы куда-то переходит (зависит от значения, которое было записано в указатель) и не имеет доступа для чтения там.
Это было нормально, даже когда я немного переполнил буфер
Вы перезаписали только память, принадлежащую вашему процессу (без обратного адреса) в том же сегменте, но все равно это UB.
но после ввода слишком много программа упала.
Да, вы, вероятно, записали в другой сегмент или перезаписали обратный адрес.