【问题标题】:How to avoid buffer overflow using scanf如何使用 scanf 避免缓冲区溢出
【发布时间】:2021-01-29 18:16:52
【问题描述】:
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char **argv) {
    char first_name[20];
    char last_name[20];
    int student_num;
    char debts[1];

    printf("Enter name:");
    scanf("%s", first_name);
    printf("Enter lastname:");
    scanf("%s", last_name);
    printf("Enter six bits length student ID:");
    scanf("%d", &student_num);
    printf("Do you have debts for university [Y/N]?");
    scanf("%s", debts);

    printf("\nYour name is %s %s.\n", first_name, last_name);
    printf("Your Student ID is %d.\n", student_num);
    printf("Debts: %s.\n", debts);
    return (EXIT_SUCCESS);
}

如何避免这段代码中的缓冲区溢出?我希望我的代码产生这样的输出:

Enter name:Enescekilokoneyasharrontannamyantoniaaquin
Enter lastname:Basenau
Enter six bits length student ID:456789
Do you have debts for university [Y/N]?YES

Your name is **Enescekilokoneyashar** (only 20 bits from name input) *Basenau*.
Your Student ID is **393299**.
Debts: **Y**.

Process returned -1073741819 (0xC0000005)   execution time : 36.336 s
Press any key to continue.

我试过用:

scanf("%19s", first_name);

但它并没有像我预期的那样工作。我需要找到另一种方法来验证输入参数,以防止缓冲区溢出攻击并将输入限制为缓冲区大小。

【问题讨论】:

  • scanf() 是一个用于解析输入的可怕函数,正如您所发现的那样。当它无法转换输入时,它会使您的输入流处于未知状态,如果它是像stdin 这样您无法返回的流,这真的很糟糕。更反常的是,如果您使用带有[f]printf() 的格式字符串编写一组字段,则不能保证相同的格式字符串在scanf() 使用时能够读取相同的数据。通常最好通过fgets()(或getline(),如果可用)读取整行并自己解析字符串。您甚至可以使用sscanf() 复制scanf()
  • (cont) 这也比scanf() 的“我是否将换行符留在缓冲区中?!?”更好地处理换行符。 scanf() 与其他输入读取函数混合时的不确定性。

标签: c buffer-overflow


【解决方案1】:

您可以继续使用scanf,但您需要将它可以占用的大小限制为目标缓冲区的大小,例如:

scanf(" %19[^\n]", first_name); //last destination buffer element is for null byte

请注意,此说明符可以解析空格,因此您可以接受包含多个单词的输入。

但是,它会在stdin 缓冲区中留下一个\n 字符,或者在您输入更大的字符串的情况下未解析的剩余字符,因此您需要在每次要解析时清除它新的输入,您可以制作一个方便的函数,当您需要这样的清晰时可以调用它,使其成为一个模式:

void clear_buffer(){
    int c;
    while ((c = getchar()) != '\n' && c != EOF){
        continue;
    }
}

你的char debts[1]缓冲区也有问题,存储字符串太小,需要至少2个字符才能存储空终止符:

char debts[2];

电话将是:

clear_buffer();
scanf(" %1[^\n]", debts);

最后要验证scanf的返回,比如scanf("%d", &amp;student_num),如果输入的是字母字符而不是数字,函数会解析失败返回0,也可以让它最多解析6个数字,类似于:

clear_buffer();
if(scanf("%6d", &student_num) == 0){ // %6d limit the input to 6 digits
    //handle error
}

Live demo

【讨论】:

    【解决方案2】:

    如果您使用的是 Windows,则可以按照 Microsoft 文档 here 中的说明使用 scanf_s()。 还有一个例子:

    char s[10];
    scanf_s("%9s", s, (unsigned)_countof(s));
    

    【讨论】:

    • scanf_s("%9s", s, (unsigned)_countof(s));scanf("%9s", s); 有何不同?如果是因为您认为 scanf() 已被弃用,那么 Microsoft 就是在骗您。 scanf() 是任何 C 实现必须 提供的函数 - 它根本没有被“弃用”。
    • 好吧,他写道scanf("%9s", s)“没有按预期工作”(无论这意味着什么)。我从来没有尝试过 scanf() 因为(正如你所说,微软认为)它已被弃用(如果微软对我撒谎那不是第一次,我真的不知道)。这就是我建议 scanf_s 的原因,因为它对我来说总是很好。
    • @AndrewHenle,是的,臭名昭著的微软诡计。
    • @Michael 阅读有关 Microsoft 对 C 标准附件 K 的实施的信息:Field Experience With Annex K — Bounds Checking Interfaces 请注意,附件 K 一开始是可选的。此外,微软在不破坏现有代码的情况下以无法修复的方式破坏了实现。我很确定这可以追溯到 Microsoft's "embrace, extend, extinguish" 心态的时代,幸运的是,这种心态似乎已经减弱了很多。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-09-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-07-26
    • 2020-01-26
    • 1970-01-01
    相关资源
    最近更新 更多