C如何处理缓冲区溢出?

时间:2016-03-24 17:46:34

标签: c arrays buffer-overflow

据我所知,在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编译)唯一抱怨的是Valgrind的。如何在C中编写安全数组代码?

4 个答案:

答案 0 :(得分:3)

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

例如,如果你写:

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

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

  

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

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

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

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

最终,避免越界访问的责任落在你这个程序员身上。不要以为编译器会为你检查它 - 并且不要认为它不会。

答案 1 :(得分:1)

C遵循&#34的理念;程序员最了解&#34;并且&#34;我不能牵着你的手#34;

这就是为什么C如此之快,它不必进行任何检查。

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

类似于:

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

arr将输入保持为指定的大小。有关详细信息,我建议使用fgets的手册页 http://linux.die.net/man/3/fgets

您可能需要对此进行多次调用才能从stdin获取所有输入。

答案 2 :(得分:0)

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

执行此操作的每种方法都有其优点,您的应用程序需求将确定使用哪种方法。

答案 3 :(得分:0)

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

由于C没有检查你做了什么并握住你的手(见Lyle Rolleman的答案),C不会&#34;#34;检测&#34;缓冲区溢出。因此,当发生这种情况时,行为是未定义的(所谓的&#34;未定义的行为&#34;或UB)。经常发生的是堆栈被覆盖,堆栈上是返回地址给调用者。这被覆盖,当前函数想要返回时,它会跳到&#34;无处&#34; (或者某个地方,因为这种行为被&#34;堆栈漏洞使用&#34;来自黑客,他们小心地覆盖堆栈,所以跳转到了#34;他们的地方&#34;)。