【问题标题】:Replace deprecated gets()替换不推荐使用的gets()
【发布时间】:2016-11-17 14:02:09
【问题描述】:

我正在使用 CMU-Cambridge 的 SLM 工具包对语言数据进行一些基线语言建模,但是当我运行其中一个构建的可执行文件时,我的系统在尝试执行其中一个命令时检测到缓冲区溢出。

基于this StackOverflow question,我注意到__gets_chk+0x179 导致了问题,并且我在源代码中发现了两次gets/fgets(evallm.c,也可用in this GitHub project someone made)但我不知道如何以适当/安全的方式修复它们。

错误信息的相关部分:

*** buffer overflow detected ***: /home/CMU-Cam_Toolkit_v2/bin/evallm terminated
======= Backtrace: =========
/lib/x86_64-linux-gnu/libc.so.6(__gets_chk+0x179)[0x7f613bc719e9]
Aborted

broken code

# declaration of input string variable
char input_string[500];

# occurence 1
...
while (fgets (wlist_entry, sizeof (wlist_entry),context_cues_fp)) { ... } 
...

# occurence 2
...
while (!feof(stdin) && !told_to_quit) {
    printf("evallm : ");
    gets(input_string);
....

这个错误基本上是我给evallm命令的input_string太长了。通常它是从命令行调用的,你可以交互地传递参数。但是,我将所有参数与命令一起传送(如文档示例中所示),但显然有时我的参数名称占用了太多字节。当我将 input_string 的数组长度从 500 更改为 2000 时,问题就解决了(所以我猜错误是由于发生 2)。但我真的很想通过用getline() 替换gets() 来解决它,因为这似乎是正确的方法。还是用fgets() 替换它也是一种解决方案?如果是这样,我应该使用什么参数?

但是,在尝试替换 gets() 时,我总是遇到编译错误。我不是 C 程序员(Python、Java),也不熟悉 getline() 的语法,所以我在努力寻找正确的参数。

【问题讨论】:

  • 你也可以使用 fgets,gets 的唯一区别是它有长度。
  • 与您的gets/fgets 问题无关,但您可能有兴趣阅读Why is “while ( !feof (file) )” always wrong?
  • @Someprogrammerdude 感谢您的提醒!该代码已经存在了很长一段时间(1999 年),所以我可以想象一些已弃用或“过时”的编程概念仍然存在。我会调查的!
  • “旧时编程概念”(你的意思是“风格,顺便说一句)”和“有问题的”/不安全代码之间有明显的区别。人们可以用汇编语言编写非常安全的代码。
  • @mbauwens:请点击我提供的链接。

标签: c getline buffer-overflow gets


【解决方案1】:

在您的特定情况下,您知道 input_string 是一个 500 字节的数组。 (当然,您可以将 500 替换为例如 2048)

我是个偏执狂,擅长防御性编程,我会在任何输入之前将该缓冲区归零,例如

memset(input_string, 0, sizeof(input_string));

即使fgets 失败,缓冲区也会被清除。在大多数情况下,这原则上是无用的。但是你有一些极端的情况,而邪恶就在细节中。

因此请阅读fgets(3) 的文档并将gets 调用替换为

fgets(input_string, sizeof(input_string), stdin);

(您实际上应该处理极端情况,例如fgets 失败和输入行长于input_string ....)

当然,您可能希望将终止的换行符归零。为此,添加

int input_len = strlen(input_string);
if (input_len>0) input_string[input_len-1] = '\0`;

(如评论所述,您可能会减少清除 input_string 的频率,例如在开始时和 fgets 失败时)

请注意,getline(3) 是特定于 POSIX 的,并且正在管理堆分配的缓冲区。阅读C dynamic memory allocation。如果您不熟悉 C 编程,那对您来说可能会很棘手。顺便说一句,您甚至可以考虑使用 Linux 特定的 readline(3)

重点是你对 C 编程的熟悉程度。

注意:在 C 中,# 不会开始注释,而是一个 preprocessor 指令。

【讨论】:

  • 更简单的方法是将第一个条目归零 iff fgets 失败。 memset 每次调用都可能是一个重大的性能问题。
  • 另一方面,如果它是人工输入的,你几乎不在乎。你会关心标准输入是否被重定向(例如在管道中)
  • 时刻准备着。它看起来不像只需要控制台输入。它不会引起更多的努力,只是检查结果,无论如何你都必须这样做。
  • 如果代码使用fgets(input_string, sizeof(input_string), stdin); int input_len = strlen(input_string); ... 而不是gets(input_string);,也建议char input_string[500]; --> char input_string[500+1];一致地处理有效的极端测试用例。
【解决方案2】:

您将gets 替换为fgets

几乎就这么简单,区别(除了参数)是fgets 可能在缓冲区末尾有一个换行符。 (注意我说它可能在那里。)

我推荐this fgets reference

【讨论】:

  • ...您使用该换行符的存在/不存在来检查您是否已获得整行,或者是否达到了大小限制。
  • OP特别提到getline,在我看来比fgets更安全。在推荐fgets 时,此答案含蓄地建议反对getline。这是故意的吗?如果是,您必须解释原因。
  • 当我用gets 查找缓冲区溢出问题时,最常见的答案是用getline 替换它。除此之外,我对其中一个没有真正的偏好。只是一个安全的替代方案,可以防止脚本中止。
  • @anatolyg 我推荐fgets,因为它是一个标准的 C 函数。 getline 函数是 POSIX 函数,在任何地方都不可用。但除此之外,我同意,如果可移植性不是问题,getline 可能是更好的选择。
  • @anatolyg getline() 有一个乍一看还不错的特性:根据输入的大小自动分配缓冲区。然而,随着各种恶意用户试图破坏代码,这将gets() 的缓冲区溢出问题与允许(邪恶)用户用 long 行压倒内存资源进行交换。在用户输入长度上使用一个合理的合理上限是合理的。不幸的是,我还没有看到 C 独立函数也可以处理它。
猜你喜欢
  • 2015-09-02
  • 1970-01-01
  • 1970-01-01
  • 2021-07-31
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多