繁体   English   中英

如何防止scanf导致C中的缓冲区溢出?

[英]How to prevent scanf causing a buffer overflow in C?

我使用这个代码:

while ( scanf("%s", buf) == 1 ){

防止可能的缓冲区溢出以便可以传递随机长度的字符串的最佳方法是什么?

我知道我可以通过调用来限制输入字符串,例如:

while ( scanf("%20s", buf) == 1 ){

但我更希望能够处理用户输入的任何内容。 或者这不能使用 scanf 安全地完成,我应该使用 fgets?

Kernighan 和 Pike 在他们的书The Practice of Programming (非常值得一读)中讨论了这个问题,他们通过使用snprintf()创建具有正确缓冲区大小的字符串以传递给scanf()系列函数来解决这个问题. 有效:

int scanner(const char *data, char *buffer, size_t buflen)
{
    char format[32];
    if (buflen == 0)
        return 0;
    snprintf(format, sizeof(format), "%%%ds", (int)(buflen-1));
    return sscanf(data, format, buffer);
}

请注意,这仍然将输入限制为作为“缓冲区”提供的大小。 如果您需要更多空间,那么您必须进行内存分配,或者使用为您进行内存分配的非标准库函数。


请注意, POSIX 2008 (2013) 版本的scanf()系列函数支持用于字符串输入( %s%c%[ )的格式修饰符m (赋值分配字符)。 它不采用char *参数,而是采用char **参数,并为它读取的值分配必要的空间:

char *buffer = 0;
if (sscanf(data, "%ms", &buffer) == 1)
{
    printf("String is: <<%s>>\n", buffer);
    free(buffer);
}

如果sscanf()函数无法满足所有转换规范,则在函数返回之前释放它为%ms类的转换分配的所有内存。

如果您使用的是 gcc,您可以使用 GNU-extension a说明符让 scanf() 为您分配内存以保存输入:

int main()
{
  char *str = NULL;

  scanf ("%as", &str);
  if (str) {
      printf("\"%s\"\n", str);
      free(str);
  }
  return 0;
}

编辑:正如乔纳森指出的那样,您应该查阅scanf手册页,因为说明符可能不同( %m )并且您可能需要在编译时启用某些定义。

大多数时候fgetssscanf的组合可以完成这项工作。 另一件事是编写自己的解析器,如果输入格式正确。 另请注意,您的第二个示例需要进行一些修改才能安全使用:

#define LENGTH          42
#define str(x)          # x
#define xstr(x)         str(x)

/* ... */ 
int nc = scanf("%"xstr(LENGTH)"[^\n]%*[^\n]", array); 

以上丢弃了输入流,但不包括换行符 ( \\n ) 字符。 您需要添加一个getchar()来使用它。 还要检查您是否到达了流末尾:

if (!feof(stdin)) { ...

就是这样。

直接使用scanf(3)及其变体会带来许多问题。 通常,用户和非交互式用例是根据输入行定义的。 如果没有找到足够多的对象,则很少出现这样的情况,更多的行将解决问题,但这是 scanf 的默认模式。 (如果用户不知道在第一行输入数字,则第二行和第三行可能无济于事。)

至少如果你fgets(3)你知道你的程序需要多少输入行,并且你不会有任何缓冲区溢出......

制作一个为字符串分配所需内存的函数并没有那么多工作。 这是我前段时间写的一个小c函数,我总是用它来读取字符串。

它将返回读取的字符串,或者如果发生内存错误则返回 NULL。 但请注意,您必须 free() 您的字符串并始终检查它的返回值。

#define BUFFER 32

char *readString()
{
    char *str = malloc(sizeof(char) * BUFFER), *err;
    int pos;
    for(pos = 0; str != NULL && (str[pos] = getchar()) != '\n'; pos++)
    {
        if(pos % BUFFER == BUFFER - 1)
        {
            if((err = realloc(str, sizeof(char) * (BUFFER + pos + 1))) == NULL)
                free(str);
            str = err;
        }
    }
    if(str != NULL)
        str[pos] = '\0';
    return str;
}

限制输入的长度肯定更容易。 您可以通过使用循环来接受任意长度的输入,一次读取一点,根据需要为字符串重新分配空间......

但这需要大量工作,因此大多数 C 程序员只是以任意长度截断输入。 我想您已经知道这一点,但是使用 fgets() 不会允许您接受任意数量的文本 - 您仍然需要设置限制。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM