Последствия этого переполнения буфера? - PullRequest
20 голосов
/ 20 июля 2010

Итак, я считаю, что у меня возникла небольшая проблема с переполнением буфера, которую я обнаружил при просмотре чужого кода.Это сразу показалось мне неправильным и потенциально опасным, но по общему признанию я не мог объяснить ФАКТИЧЕСКИЕ последствия этой «ошибки», если таковые имеются.

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

Код проблемы (я так думаю, в любом случае):

char* buffer = new char[strlen("This string is 27 char long" + 1)];
sprintf(buffer, "This string is 27 char long");

Теперь причина, по которой я это выделил, и я хочу пометить его как возможное переполнение буфера, связана с первым strlen.Из-за арифметики указателей, «неправильное» размещение + 1 приведет к тому, что strlen вернет 26 вместо 27 (принимая длину «его строка длиной 27 символов»).sprintf, я полагаю, затем печатает 27 символов в буфер и вызывает переполнение буфера.

Это правильная оценка?

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

Так как этодовольно простой «вопрос», было бы неплохо, если бы вы могли поддержать свой ответ с какой-то ссылкой.Хотя я ценю и приветствую ваш вклад, я не собираюсь принимать «да, это так» в качестве окончательного ответа.Заранее благодарим.




Обновление: Много хороших ответов с большим количеством дополнительной информации.К сожалению, я не могу принять их всех.Спасибо, что поделились своими знаниями и за то, что были моим «вторым мнением».Я ценю помощь.

Ответы [ 11 ]

14 голосов
/ 20 июля 2010

Ваша оценка верна. [править] с добавлением исправления, упомянутого Джеймсом Керраном. [/ edit]

Вероятно, ваше тестовое приложение не показывало проблему, потому что выделение округлено до следующего кратного 4, 8 или 16 (которые являются общими гранулярностями выделения).

Это означает, что вы должны быть в состоянии продемонстрировать с помощью строки длиной 31 символ.

В качестве альтернативы используйте «инструментальный» собственный профилировщик памяти, который может разместить защитные байты вокруг такого выделения.

6 голосов
/ 20 июля 2010

Ваша оценка верна, за исключением того, что springf поместит 28 символов в буфер, считая NUL в конце строки (поэтому вам понадобился неуместный "+1" в первую очередь)

Обратите внимание, что по моему опыту, если что-то выходит из строя вне отладчика, но работает с пошаговым переходом в отладчике, в 100% случаев вы переполняете локальный буфер. Отладчики помещают в стек намного больше, поэтому менее вероятно, что что-то важное было перезаписано.

3 голосов
/ 20 июля 2010

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

char buffer[strlen("This string is 27 char long" + 1)];

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

Чтобы использовать подобное переполнение буфера, вам нужно записать нужные данные, а затем найтиспособ «перейти» к этим данным для выполнения.

1 голос
/ 20 июля 2010

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

Купить попробуйте с переполнением стека

#include "stdio.h"
#include "string.h"

int main()
{
     unsigned int  y      = (0xFFFFFFFF);
     char buffer[strlen("This string is 27 char long" + 1)];
      unsigned int  x      = (0xFFFFFFFF);
      sprintf(buffer, "This string is 27 char long");

      printf("X (%#x) is %#x, Y (%#x) is %#x, buffer '%s' (%#x) \n", &x, x,&y, y, buffer, buffer);
      return 0;
  }

Вы увидите, что Y поврежден.

1 голос
/ 20 июля 2010

Многие исторические реализации malloc помещают бухгалтерские данные непосредственно перед и / или после выделенного блока.Возможно, вы перезаписываете такие данные, и в этом случае вы не увидите никаких ошибок / сбоев, пока не попытаетесь освободить память (или, возможно, освободить независимо от того, каким будет следующий блок).Аналогичным образом, возможно, что учетная информация для последующего распределения позже перезапишет вашу строку.

Я подозреваю, что современные реализации malloc предпринимают некоторые усилия для защиты от повреждения кучи путем заполнения выделений данными проверки целостности, так что есливам повезло, ничего плохого не произойдет, или вы можете получить предупреждающее сообщение во время последующей операции выделения / освобождения.

1 голос
/ 20 июля 2010

Вы правы, что арифметика указателя в этом примере выдает неправильную (более короткую) длину, переданную new.Наиболее вероятная причина, по которой вы не можете вызвать этот сбой, заключается в том, что существует некоторая неопределенность относительно того, какой объем буферного пространства фактически предоставляется выделением памяти.

Библиотеке разрешено предоставлять больший буфер, чем былопросил.Кроме того, также возможно, что все, что следует за вашим буфером, имеет префикс заголовка выделения, который подчиняется правилам выравнивания машинных слов.Это означает, что до следующего заголовка выделения может быть до трех байтов заполнения (в зависимости от платформы).

Даже если вы перезаписали следующий заголовок выделения (который используется для управления выделенными блоками памяти), он не будетпроявлять себя как проблему, пока владелец этого следующего блока не попытался вернуть его в кучу.

1 голос
/ 20 июля 2010

Да, вы правы.Выделенный буфер будет на 2 байта слишком мал, чтобы содержать строку.

Так как это выделяется в куче, это может привести к повреждению кучи.Однако вероятность этого зависит от того, какие другие выделения и освобождения памяти произошли до этого момента, а также от того, какой менеджер кучи используется.Подробнее см. Переполнение кучи .

0 голосов
/ 20 июля 2010

Правильное утверждение.Поскольку вы передаете адрес второго символа строки в strlen (), в результате вы получаете длину на один символ меньше.Кроме того, основная проблема связана с sprintf (), это одна из причин того, что это небезопасно.

Даже это компилируется и выполняется (также может произойти сбой).

    char* x = new char;
    sprintf(x, "This is way longer than one character");
    printf("%s", x);

ВЧтобы избежать этой опасной проблемы, вы должны использовать безопасные версии этой функции, такие как snprintf () или asprintf () в GCC или sprintf_s () в MSVC.

В качестве ссылок, пожалуйста, посмотрите Документация библиотеки GNU C в этом отношении, а также примечание по безопасности для статьи MSDN sprintf ().

0 голосов
/ 20 июля 2010

Причина, по которой строка отлично печатается в отладчике, заключается в том, что как часть sprintf, завершающий символ NULL записывается в память (в данном случае за пределами выделенного буфера), а когда дело доходит до чтения строки, NULL присутствует символ для завершения строки, как и ожидалось.

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

0 голосов
/ 20 июля 2010

Ваша настоящая проблема в том, что вы пишете

char* buffer = new char[strlen("This string is 27 char long" + 1)];

вместо

char* buffer = new char[strlen("This string is 27 char long") + 1];

То есть, на первом вы даете strlen () адрес, который не является началом вашей строки .

Попробуйте этот код:

const char szText[] = "This string is 27 char long";
char* buffer = new char[strlen(szText) + 1];
sprintf(buffer, szText);
...