Последствия этого переполнения буфера?

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

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

Проблемный код (я думаю, что это, так или иначе):

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 символов в буфер и вызвал переполнение буфера.

Это - корректная оценка?

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

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




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

20
задан KevenK 20 July 2010 в 14:40
поделиться

11 ответов

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

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

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

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

14
ответ дан 30 November 2019 в 00:47
поделиться

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

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

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

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

3
ответ дан 30 November 2019 в 00:47
поделиться

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

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

6
ответ дан 30 November 2019 в 00:47
поделиться

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

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

1
ответ дан 30 November 2019 в 00:47
поделиться

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

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

1
ответ дан 30 November 2019 в 00:47
поделиться

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

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

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

1
ответ дан 30 November 2019 в 00:47
поделиться

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

0
ответ дан 30 November 2019 в 00:47
поделиться

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

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

#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
ответ дан 30 November 2019 в 00:47
поделиться

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

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);
0
ответ дан 30 November 2019 в 00:47
поделиться

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

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

0
ответ дан 30 November 2019 в 00:47
поделиться

Правильное утверждение. Поскольку вы передаете адрес второго символа строки в 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 Library по этому вопросу, а также обратите внимание на статью MSDN о безопасности sprintf().

0
ответ дан 30 November 2019 в 00:47
поделиться