【问题标题】:How does C treat Buffer overflows?C如何处理Buffer溢出?
【发布时间】:2016-03-24 17:46:33
【问题描述】:

我知道在 C 语言中,有些数组可以在声明时指定长度。我想知道这些长度声明是否只是供其他程序员查看和理解使用,或者编译器是否可以通过禁止读取超过缓冲区长度的字符来保护代码。当我读入一个字符串时,它会继续运行,并开始覆盖存储在我要读入的缓冲区之后声明的变量中的数据。是否有安全的方法来读取数据?

char arr[5];                                                                
char buff[5] = "cat";                                                                                                                                        
printf("The buffer holds: %s\n", buff);                                     
printf("Input a word to be held in \"arr\": ");                             

scanf("%s", arr);                                                           

printf("The array holds:  %s\n", arr);                                      
printf("The buffer holds: %s\n", buff);                                     
printf("%c\n", arr[9]);      

如果读入 arr 的字符串足够长,“cat”将被覆盖,并且编译标志似乎都没有做任何事情(我使用 -Wextra -Wall -Werror -std=c99 编译)唯一抱怨的是瓦尔格林。如何在 C 中编写安全的数组代码?

【问题讨论】:

  • 你行事有条不紊。 C 标准规定编译器供应商没有义务为您辩护。越界写入是未定义的行为。
  • 您是否尝试添加-O2
  • lm gt fy...safe scanf
  • 射你的脚
  • 感谢大家的回答!你们真的为答案付出了一些努力,而且你们每个人都提出了一个有趣的观点(黑客、编译器行为、替代解决方案),所以很难选择一个好的答案。我想我最终会使用不同的语言来轻松检查用户输入的错误,然后使用 C 语言进行内部工作,以确保变量等在界限内。

标签: c arrays buffer-overflow


【解决方案1】:

从某种意义上说,C 语言本身既不能保护你,也不能保护你免于超出数组的界限。更准确地说,C 编译器不需要执行边界检查,但允许这样做。 (很少有编译器会利用该权限。很少默认情况下会这样做。)

例如,如果你写:

int arr[10];
arr[20] = 42;

行为是未定义。这并不意味着您的程序会崩溃。这并不意味着错误会或不会被检测到。引用 ISO C 标准,

行为,在使用不可移植或错误的程序构造或 错误数据,本国际标准对此没有规定 要求

典型的 C 编译器可能会生成采用基地址 arr 的代码,向其添加 20 * sizeof (int) 的偏移量,然后尝试将 42 存储在结果位置。如果没有显式或隐式检查,这可能会破坏其他一些数据结构,它可能会写入您的进程拥有但不用于其他任何内容的内存,或者它可能会终止您的程序。 (或者#include <stdjoke.h>它可以让恶魔从你的鼻子里飞出来。)

但是符合标准的 C 编译器可以添加代码来检查索引是否在 0 到 9 的范围内,如果不是,则采取一些明智的措施。 C 不禁止边界检查;它只是不需要它。

在这种特殊情况下,可以(但不是必需)在编译时检测到数组访问超出范围,因此编译器可以发出编译时警告。 (如果在运行时才知道索引值,则这是不可能的。)

最终,避免越界访问的责任落在了程序员身上。不要假设编译器会为您检查它——也不要假设它不会。

【讨论】:

    【解决方案2】:

    C 遵循“程序员最了解”和“我不会牵着你的手”的哲学

    这就是 C 语言如此之快的原因,它不需要做任何检查。

    为了安全的用户输入,你可以使用 fgets

    类似的东西:

    fgets(arr, sizeof(arr), stdin);
    

    arr 会将输入保持到指定的大小。有关更多信息,我推荐 fgets 的手册页 http://linux.die.net/man/3/fgets

    您可能需要多次调用它才能从标准输入中获取所有输入。

    【讨论】:

    • 感谢替代方法!往往是这样的东西让我想为用户提供只需单击的按钮以最大程度地减少错误检查......或者改为使用 C++ 来处理字符串和字符串流等内容。
    【解决方案3】:

    C 中的数组大小仅告诉编译器为数组保留多少内存。 C 不会插入代码来检查您是否超出了数组边界。 int a[5]; 中的大小“5”没有存储在编译程序中。它仅在源代码中。其他能看源码的程序员可以看;没有其他人可以。

    由于 C 不会检查您的操作并握住您的手(请参阅 Lyle Rolleman 的回答),因此 C 不会“检测”缓冲区溢出。因此,发生这种情况时行为是未定义的(所谓的“未定义行为”或 UB)。经常发生的是堆栈被覆盖,堆栈上是调用者的返回地址。这被覆盖了,当当前函数想要返回时,它跳转到“无处”(或某处,因为这种行为被黑客的“堆栈漏洞”使用,他们小心地覆盖堆栈,所以跳转到“他们的位置”) .

    【讨论】:

      【解决方案4】:

      C 不能保护您不超过数组的末尾。不过有一些方法可以检测到它。看这篇文章

      Setting up a bounds-protected array

      试试这个代码

      #include <assert.h>
      #include <stdio.h>
      #include <stdlib.h>
      #include <string.h>
      
      #define ARRAY_SIZE 100
      
      int main(void) {
        size_t i = 0;
        char   arr1[ARRAY_SIZE];
        char * arr2 = malloc(ARRAY_SIZE );
        for(i = 0; i < 200; i++) {
          arr1[i] = '1';
          arr2[i] = '2';
        }
      
        for(i = 0; i < 200; i++) {
          printf("%zu arr1[i]=%c  \n", i, arr1[i]);
          printf("%zu arr2[i]=%c  \n", i, arr2[i]);
        }
        return 0;
      }
      

      使用以下编译时选项(这只适用于 gcc,即 clang 不会出错)

      gcc -O3 -Wall -std=c11 -pedantic array_overflow_at_03.c
      

      然后尝试使用

      gcc -Wall -std=c11 -pedantic array_overflow_at_03.c
      

      每种方法都有其优点,您的应用程序需求将决定使用哪一种。

      【讨论】:

      • 感谢您的参考,哈利。问题是这种方法是事后的,即在发生未经授权的访问之后。只有使用内存管理系统(硬件)才能在事实上进行保护。但是,如何保护每个变量?您可能无法检查所有非法访问是不可能的。那么,什么时候访问是非法的?
      • 哈,不错!当 i == 101 时,iarr1[i] = '1'; 覆盖,这会将 i 重置为 49 ('1'),并且循环将永远运行。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-09-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多