【问题标题】:Overflow in C function strcpy() [duplicate]C函数strcpy()中的溢出[重复]
【发布时间】:2017-02-23 16:59:33
【问题描述】:

我在 Linux 环境中用 C 语言编程,并且 我很困惑为什么这段代码中没有出现分段错误:

int main(){
 char buffer[4];
 char tmp="qqqqqqqqqqqqqqqqqqqqqqqq";
 char *r;
 r=strcpy(buffer,tmp);
 return 0;}

我使用的变量 tmp 比缓冲区长,尽管它我可以正确地标记缓冲区变量而不会出现任何错误。

此外,我不明白为什么在这种情况下:

int main(){
 static char buffer[4];
 int i=0;
 while(i<5){
    (*(buffer+i)='a');
      i++;}
 return 0;}

只有当我没有声明缓冲区为静态时才会发生分段错误。

提前谢谢你。

【问题讨论】:

  • UB 并不意味着您会遇到 SegFault,请参阅 this answer
  • char tmp="qqqqqqqqqqqqqqqqqqqqqqqq"; 错误:您将const char * 分配给char,不要忽略编译器警告
  • while(i&lt;5){ 也是错误的,buffer 的类型是 char[4],这意味着有效的索引是 0 到 3。
  • 如果缓冲区溢出,不能保证会自动生成段错误;这就是缓冲区溢出攻击起作用的原因:-(。答案在平台上有所不同,但基本上变量的类型(静态或否,函数本地,malloc()ed)会改变它在内存中填充的位置,这可能会更宽松或更严格,具体取决于硬件辅助(例如 NC)、堆栈粉碎保护和其他因素。简而言之:不要指望内存保护可以让您免于各种形式的愚蠢:-/
  • 第一个例子还能编译吗?调用 strcpy(buffer, tmp) 应该会产生编译器错误,因为 tmp 被声明为 char 而不是 char*

标签: c


【解决方案1】:

在第一种情况下,buffer 足以容纳 4 个字符,通常这意味着它可以容纳 3 个字符 + 1 个空字符。 strcpy 不允许您防止溢出,而 strncpy 可以。这是一个简单的写作问题:

const char *tmp = "your string"; // const char *, not char
char buffer[4];
strncpy(buffer, tmp, (sizeof buffer) - 1); // sizeof char array == number of characters buffer can store
buffer[3] = '\0';//add terminating nul char

在第二种情况下,最大的问题是您的while 循环正在访问一个超出范围的索引(i&lt;5 意味着最后一次迭代将具有 i == 4)。数组是零索引的,所以buffer 的最后一个有效索引是 3。将循环更改为:

while(i<3) {
    buffer[i++] = 'a';
}
buffer[i] = '\0';

您可以通过正确初始化 buffer 来取消 nul 字符:

char buffer[4] = "";

所以我可能会这样写:

int main ( void )
{
    const char *tmp = "some long string";
    char buffer[4] = "";
    strncpy(buffer, tmp, (sizeof buffer) - 1);
    return 0;
}

【讨论】:

    【解决方案2】:

    C 让你有能力射自己的脚。

    有责任确保接收缓冲区足够大以容纳传递给strcpy 的源字符串的内容。 (不要忘记为 nul 终止符留出空间)。这就是为什么喜欢他们目前的工作的人会使用strncpy,它允许您对要复制的字符数设置上限。

    目前您的程序的行为是未定义。分段错误是一种可能的表现。

    【讨论】:

      【解决方案3】:

      由于您的第一个示例甚至不应该编译,我将假设这是问题中的一个简单错字,并使用以下代码而不是您的第一个示例:

      int main(){
       char buffer[4];
       char *tmp="qqqqqqqqqqqqqqqqqqqqqqqq"; // this should be a pointer
       char *r;
       r=strcpy(buffer,tmp);
       return 0;}
      

      关于未定义行为的每个人的 cmets 都是正确的,因为它不必导致 seg。错了,但我认为这些 cmets 没有抓住你的问题的重点。所以我将跳过你不应该编写这样的代码的明显原因,并专注于为什么当你使用 static 关键字时它似乎工作(因为没有更好的词)。

      static 关键字在语义上会改变 buffer 的生命周期,但在更低的位置,它也会改变 buffer 在内存中的存储位置。

      您可能听说过the heap and the stack,如果您还没有听说过,您可能应该阅读它们,因为它们是C 编程的关键概念,但there are more memory regions than just those two in a C program。堆栈和堆用于动态内存,但 静态内存 存储在程序的 databss 段中。

      在不同的内存区域溢出缓冲区会对程序的行为产生不同的影响,这完全取决于缓冲区内存位置周围存储的内容。

      如果没有 static 关键字,在您的第一个示例中,buffer 被放置在堆栈上,并且被您的函数局部变量以及其他信息包围,例如函数返回后在代码中执行的点. understand the stack frame a.k.a. Call stack 很重要。由于我怀疑您的缓冲区溢出调查受到缓冲区溢出攻击的启发,我建议您阅读 this explanation 了解它们的工作原理。

      当您溢出堆栈上的缓冲区时,一种可能的结果是您的程序试图从错误的点继续,这甚至可能不是可执行代码。如果你的程序试图执行不是代码的东西,那么操作系统就会介入并像出轨的丈夫/妻子一样抛弃你。但请注意,这只是一种可能性。

      但是,通过创建缓冲区static,您将把它从堆栈中取出,并将其放在离可执行代码很远并且很可能被其他数据包围的其他地方。当您溢出此缓冲区时,您正在破坏数据而不是代码,因此现在程序的行为完全取决于哪些数据已被破坏以及您的程序是否会因此而做一些疯狂的事情。它可能只是表现得很奇怪,但不会崩溃,或者它可能会立即崩溃,具体取决于更改的内容。

      未定义的行为只是从标准的角度来看是未定义的。当你在 C 中使用未定义的行为时,你得到的行为取决于你的编译器和你的机器,它们可以做任何他们喜欢的事情。计算机本质上是确定性的,它们所做的一切都是由它们运行的​​代码定义的,因此如果你深入挖掘,就有可能定义未定义的行为。但是避免它会更安全,它会让你的代码在任何地方都可以工作,而不仅仅是在你的机器上那个晦涩/过时的编译器版本......

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-07-22
        • 1970-01-01
        • 2021-11-11
        • 2017-12-06
        • 1970-01-01
        • 1970-01-01
        • 2021-03-11
        • 2015-07-07
        相关资源
        最近更新 更多