在 scanf() 之后使用 printf() 时的异常行为

An unusual behaviour when using printf() after scanf()

我正在使用 scanf() 从用户那里获取密码并将其存储在大小为 20 字节的 password 变量中。我根据 correctPassword 检查输入的密码,如果匹配,布尔变量 pass 将更改为 true.

因此,当我输入超过 20 个字符的密码时,缓冲区溢出发生并且 pass 的值变为非零(即 true)。但是,当我使用 printf() 打印变量 pass 的地址时,即使我使用的密码超过 20 个字符,也不会发生缓冲区溢出。

这里是导致溢出的代码:

#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <string.h>

int main (int argc, char *argv[]) {
    char password[20];
    char correctPassword[] = "random";
    bool pass = false;
    
    printf("enter your password: ");
    scanf("%s", password);

    if (strcmp(password, correctPassword) == 0) {
        // compare the two strings,strcmp() returns 0 if two strings values are the same.
        pass = true;
    }
    if (pass) {
        printf("Connecting you to the central system...\n");
    } else {
        printf("Password is wrong! entry denied\n");
    }
    
    printf("%d\n", pass);

    return 0;
}

在这种情况下输入的密码长度为 40 个字符(a 的 ASCII 值为 97),pass 的值变为 97(真)。

enter your password: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Connecting you to the central system...
97

这里是相同的代码,但最后多了一行来打印变量 pass:

的地址
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <string.h>

int main (int argc, char *argv[]) {
    char password[20];
    char correctPassword[] = "random";
    bool pass = false;
    printf("enter your password: ");
    scanf("%s", password);

    if (strcmp(password, correctPassword) == 0) {
        // compare the two strings,strcmp() returns 0 if two strings values are the same.
        pass = true;
    }
    if (pass) {
        printf("Connecting you to the central system...\n");
    } else {
        printf("Password is wrong! entry denied\n");
    }

    printf("%d\n", pass);
    printf("%x\n", &pass);

    return 0;
}

在这种情况下,输入的密码长度为 40 个字符,但 pass 仍然是 0(错误)。

enter your password: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Password is wrong! entry denied
0
61fec4

所以,我的问题是如何只添加

printf("%x\n", &pass);

不会像预期的那样导致缓冲区溢出?

scanf("%s", password) 是一个安全漏洞:任何超过 19 个字节的输入字都会导致 scanf() 超出数组末尾的写入,从而触发未定义的行为。

未定义的行为可能会产生不可预知的副作用,这些副作用可能是可见的,也可能是不可见的。 97 的输出是轻微的副作用,但是足够长的输入会破坏 return 地址,并在从 main() 和巧妙构造的 returning 时导致分段错误输入可能允许攻击者执行任意代码。更改程序可能会改变副作用,因为局部变量的分配可能会有所不同,例如当您获取 &pass 的地址时。 None 这是可以预见的。

还要注意 printf("%x\n", &pass); 也有未定义的行为,因为 %x 需要一个 unsigned int,而不是 bool *,你应该写 printf("%p\n", (void *)&pass);

有一种简单的方法可以通过将长度字段传递给 scanf() 来防止这种情况:

    scanf("%19s", password);

但是请注意,您还应该检查 return 值以检测文件过早结束,并且您还应该刷新输入行的其余部分。

这是修改后的版本:

#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main (int argc, char *argv[]) {
    char password[20];
    char correctPassword[] = "random";
    bool pass = false;
    int c;

    printf("enter your password: ");
    if (scanf("%19s", password) != 1) {
        printf("invalid input\n");
        return 1;
    }
    /* read and discard the rest of the line */
    while ((c = getchar()) != EOF && c != '\n')
        continue;

    if (strcmp(password, correctPassword) == 0) {
        // compare the two strings,strcmp() returns 0 if two strings values are the same.
        pass = true;
    }
    if (pass) {
        printf("Connecting you to the central system...\n");
    } else {
        printf("Password is wrong! entry denied\n");
    }

    printf("%d\n", pass);

    return 0;
}