【问题标题】:What is stack smashing and how do I fix it?什么是堆栈粉碎,我该如何解决?
【发布时间】:2019-09-19 16:30:48
【问题描述】:

这个程序的目的是通过测试它与前 11 个素数整数的可分性来确定 1 到 1000 之间的数字是否为素数。该程序在大多数输入下都能正常运行。但是,当我输入一个整数(例如 468)时,会检测到堆栈粉碎。什么是堆栈粉碎,我该如何解决?

我曾尝试研究堆栈粉碎,但找不到与我的程序相关的具体示例。我不知道我可以尝试修改程序的替代方法,因为我对使用 C 进行编程相对较新。

char divStatement[] = " is divisible by ";

if ((userInput % 31) == 0) {
    div31 = true;
    strcat(divStatement, "31, ");
}

if (div2 || div3 || div5 || div7 || div11 || div13 || div17 || div19 || div23 || div29 || div31) {
        divStatement[strlen(divStatement) - 2] = '.';
        divStatement[strlen(divStatement) - 1] = '\n';
        printf("%d%s", userInput, divStatement);
        printf("%d is not prime.\n", userInput);
}
else {
        printf("%d is prime.\n", userInput);
}

输出实际上工作正常。然而,在程序结束时,终端输出:

***stack smashing detected ***: ./a.out terminated 
Aborted

【问题讨论】:

  • 使用数组而不是不合理数量的变量全部挤在一起会好很多。
  • 无关,但这段代码需要大量重构。
  • 很确定您的strcat() 正在尝试修改只读内存,并且还可能经过数组的末尾(如果有效,它在堆栈上。)阅读如何strcat() 有效(然后在可写内存中为字符串分配更多空间。)
  • 在询问有关运行时问题的问题时,就像这个问题一样,发布minimal reproducible example 显示输入、实际输出和预期输出。注意:发布的代码应该可以干净地编译,而不是从某些代码中间截取的片段

标签: c stack-smash


【解决方案1】:
char divStatement[] = " is divisible by ";

if ((userInput % 31) == 0) {
    div31 = true;
    strcat(divStatement, "31, ");
}

divStatement 是一个 char 数组,其大小正好足以容纳 " is divisible by " 加上一个 \0 终止符。您不能将"31, " 附加到它,因为它没有任何额外的松弛空间。

一个简单的解决方法是为数组指定一个显式长度,该长度足以处理您的程序可能附加的任何内容。

char divStatement[1000] = " is divisible by ";

【讨论】:

  • 这个答案在技术上是正确的(正确的最佳方法:-)。它回答了关于“如何修复堆栈粉碎”的海报问题。但它未能回答发帖人真正应该问的问题:“如何在不先构建完整字符串的情况下打印内容。以及如何使用循环和数组”
【解决方案2】:

由于编译器使用了memory protection mechanisms,返回堆栈粉碎错误。您可以禁用此功能,但这会导致漏洞被所谓的堆栈缓冲区溢出攻击所利用(请参阅this computerfile's video on youtube)。

您没有声明具有特定大小的字符数组,而是因为您使用 string literal 对其进行初始化,所以 divStatement 字符数组的大小设置为 18 个字节(@987654328 中的字符为 17 个字节@ 和 1 为"\0",即所谓的null character 用于字符串终止)。来自 C99 标准 6.4.5/5 “String Literals - Semantics”(在this question 的回答中引用):

然后使用多字节字符序列来初始化一个静态存储持续时间和长度刚好足以包含该序列的数组。

简单地说,编译器知道内存中应该只为这个特定的字符数组保留 18 个字节,因此写入数组不应该修改超过第 18 个字节的数据(出于安全原因)。当您连接字符串时,您实际上是在尝试写入超过 18 个字节,这会触发安全机制,从而导致错误。

由于您只测试 1 到 1000 之间的数字,并且只测试前 11 个素数,因此您知道素数最多有 3 个字符长。与", " 一起增加了额外的 5 个字符,因此总共有 18 + 5 = 23 个字符。因此,通过将divStatement 显式声明为大小为23 的字符数组,即divStatement[23] = " is divisible by ",您的问题很容易解决。要获得有关您的代码的更一般的反馈,您应该查看code review community。

【讨论】:

    【解决方案3】:

    这不是您问题的答案,而是您应该提出的问题的部分答案。我厌倦了想出一个适当的解释。但是您在代码中的某处需要这样的东西:

    if ((userInput % n) == 0)
      {
        printf("%d is divisible by %d\n", userInput, n);
        is_prime == 0;
      }
    

    拥有所有 11 个第一个质数的数组可能也是一个好主意:

    const int primes[] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31};
    

    我不会通过告诉您如何将其包装在一个循环中以及如何声明/初始化 n 和 is_prime 来破坏所有 7 个乐趣。享受你的家庭作业。我希望截止日期不会太早:-)

    【讨论】:

      【解决方案4】:

      Stack Smashing 的定义,发布于:https://www.techopedia.com/definition/16157/stack-smashing

      定义 - Stack Smashing 是什么意思?

      *堆栈粉碎是一种漏洞形式,计算机应用程序或操作系统的堆栈被强制溢出。这可能会导致程序/系统的颠覆和崩溃。

      堆栈是一种先进后出电路,是一种缓冲形式,用于在其中保存操作的中间结果。为了简化,堆栈粉碎将更多的数据放入堆栈,而不是其容量。熟练的黑客可以故意将过多的数据引入堆栈。过多的数据可能存储在其他堆栈变量中,包括函数返回地址。当函数返回时,它会跳转到堆栈上的恶意代码,这可能会破坏整个系统。堆栈上的相邻数据受到影响,并迫使程序崩溃。*

      关于:

      char divStatement[] = " is divisible by ";
      

      和

      strcat(divStatement, "31, ");
      

      对strcat() 的调用试图将字符串:“31”附加到一个仅能容纳字符串:“可被”整除的数组。结果是数组溢出。这种溢出是未定义的行为。在这种情况下,它破坏了堆栈,可能就在某个堆栈帧或其他链接所在的位置

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-05-16
        • 2011-11-18
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多