Подтвердить что ты не робот

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

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

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

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

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 char long" ). sprintf, я полагаю, затем печатает 27 char в буфер и вызвал переполнение буфера.

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

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

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




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

4b9b3361

Ответ 1

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

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

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

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

Ответ 2

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

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

Ответ 3

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

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

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

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

Ответ 4

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

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

Ответ 5

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

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

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

Ответ 6

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

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

Ответ 7

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

Купите попытку с переполнением стека

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

Ответ 8

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

Ответ 9

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

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);

Ответ 10

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

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

Ответ 11

Правильная инструкция. Поскольку вы передаете адрес второго символа строки в 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().