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

c++ c printf buffer-overflow

1411 просмотра

11 ответа

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

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

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

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

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

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

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




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

Автор: KevenK Источник Размещён: 12.11.2019 09:13

Ответы (11)


14 плюса

Решение

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

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

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

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

Автор: peterchen Размещён: 20.07.2010 02:22

6 плюса

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

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

Автор: James Curran Размещён: 20.07.2010 02:21

3 плюса

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

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

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

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

Автор: Scharron Размещён: 20.07.2010 02:24

1 плюс

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

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

Автор: torak Размещён: 20.07.2010 02:23

1 плюс

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

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

Автор: R.. Размещён: 20.07.2010 02:32

1 плюс

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

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

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

Автор: Amardeep AC9MF Размещён: 20.07.2010 02:26

1 плюс

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

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

#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 поврежден.

Автор: Yousf Размещён: 20.07.2010 02:38

0 плюса

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

Автор: Jens Gustedt Размещён: 20.07.2010 02:33

0 плюса

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

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);
Автор: Poni Размещён: 20.07.2010 02:37

0 плюса

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

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

Автор: TheJuice Размещён: 20.07.2010 02:59

0 плюса

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

Автор: Hamid Nazari Размещён: 20.07.2010 03:07
32x32