Объяснение переполнения буфера в C - PullRequest
0 голосов
/ 24 августа 2018

Я пытаюсь понять переполнения буфера.Это мой код:

#include <stdio.h>

int main() 
{
    char buf[5] = { 0 };
    char x = 'u';

    printf("Please enter your name: ");
    gets(buf);

    printf("Hello %s!", buf);

    return 0;
}

Массив buf имеет размер пять и инициализируется 0es.Итак (с нулевым окончанием) у меня есть место для четырех символов.Если я ввожу пять символов (например, стек), я перезаписываю нулевой символ завершения, и printf должно вывести «Hello stacku!»из-за следующей переменной x.Но это не так.Он просто печатает «стек».Может кто-нибудь объяснить, почему?

Ответы [ 3 ]

0 голосов
/ 24 августа 2018

Как указывалось в другом ответе, вовсе не гарантируется, что x будет находиться сразу после buf в памяти.Но даже если это произойдет: gets перезапишет его .Помните: gets не может знать, насколько велик целевой буфер.(Это его роковой недостаток.) ​​Он всегда записывает всю читаемую строку, плюс завершающий \0.Так что если x окажется сразу после buf, то если вы введете пятисимвольную строку, printf, скорее всего, напечатает ее правильно (как вы видели), и если вы должны были проверить x.значение после этого:

printf("x = %d = %c\n", x, x);

это, вероятно, покажет вам, что x было 0 сейчас, а не 'U'.

Вот как первоначально может выглядеть память:

     +---+---+---+---+---+
buf: |   |   |   |   |   |
     +---+---+---+---+---+

     +---+
  x: | U |
     +---+

Итак, после того, как вы наберете «стек», оно будет выглядеть так:

     +---+---+---+---+---+
buf: | s | t | a | c | k |
     +---+---+---+---+---+

     +---+
  x: |\0 |
     +---+

А если вы наберете «слон», оно будет выглядеть так:

     +---+---+---+---+---+
buf: | e | l | e | p | h |
     +---+---+---+---+---+

     +---+
  x: | a | n   t  \0
     +---+

Нет необходимостискажем, эти три символа n, t и \0 могут вызвать еще больше проблем.

Вот почему люди говорят, что не следует использовать gets, никогда.Его нельзя безопасно использовать.

0 голосов
/ 24 августа 2018

Локальные переменные обычно создаются в стеке.В большинстве реализаций стеки растут вниз, а не вверх, так как память выделяется.Таким образом, вполне вероятно, что buf находится по более высокому адресу, чем x.Вот почему при переполнении buf оно не перезаписывается x.

. Вы можете подтвердить это, написав buf[-1]='v';printf("%c\n",x);, хотя это может повлиять на заполнение.Также может быть полезно сравнить адреса с printf("%i\n",buf - &x); - если результат положительный, то buf имеет более высокий адрес, чем x.

Но все это в значительной степени зависит от реализации, иможет меняться в зависимости от различных параметров компилятора.Как уже говорили другие, вы не должны полагаться ни на что из этого.

0 голосов
/ 24 августа 2018

Краткое объяснение состоит в том, что если вы объявили «x» в строке исходного кода после «buf», это не означает, что компилятор поместил их рядом друг с другом в стеке. С указанным кодом 'x' вообще не используется, поэтому, вероятно, он не был помещен в любом месте . Даже если вы как-то использовали 'x' (и это должно было бы быть способом, предотвращающим его вставку в регистр), есть большая вероятность, что компилятор отсортирует его ниже 'buf' именно так, чтобы не перезаписывается кодом, переполняющим 'buf'.

Вы можете заставить эту программу перезаписывать 'x' конструкцией struct, например,

#include <stdio.h>

int main() 
{
    struct {
        char buf[5];
        char x[2];
    } S = { { 0 }, { 'u' } };

    printf("Please enter your name: ");
    gets(S.buf);

    printf("Hello %s!\n", S.buf);
    printf("S.x[0] = %02x\n", S.x[0]);

    return 0;
}

, поскольку поля struct всегда располагаются в памяти в порядке их появления в исходном коде. 1 В принципе между S.buf может быть заполнение и S.x, но char должно иметь требование выравнивания 1, поэтому, вероятно, ABI этого не требует.

Но даже если вы сделаете , что , оно не выведет «Hello stacku!», Потому что gets всегда записывает завершающий NUL. Смотреть:

$ ./a.out 
Please enter your name: stac
Hello stac!
S.x[0] = 75

$ ./a.out 
Please enter your name: stack
Hello stack!
S.x[0] = 00

$ ./a.out 
Please enter your name: stacks
Hello stacks!
S.x[0] = 73

Посмотрите, как она всегда печатает набранный вами текст, но x[0] перезаписывается, сначала с NUL, а затем с '?' *

(Вы уже читали Разбить стек ради удовольствия и прибыли ? Вам следует.)


1 Сноска для педантов: если задействованы битовые поля, порядок полей в памяти частично определяется реализацией. Но это не важно для целей этого вопроса.

...