使C不受缓冲区溢出和其他错误影响的选项?

使C不受缓冲区溢出和其他错误影响的选项?,c,security,buffer-overflow,C,Security,Buffer Overflow,是否有任何东西可以证明C不受缓冲区溢出的影响(编辑:以及由于C通常未经检查编译而产生的其他错误,即可能是某种边界检查)?并且兼容程度足以用于大型生产代码(编辑:用于所有代码) 我用mudflap尝试了gcc,它允许它毫无错误地运行 #include <stdio.h> int main() { int a[2]; a[-1] = 5; printf("%u\n", a[-1]); return 0; } #包括 int main() { int a[

是否有任何东西可以证明C不受缓冲区溢出的影响(编辑:以及由于C通常未经检查编译而产生的其他错误,即可能是某种边界检查)?并且兼容程度足以用于大型生产代码(编辑:用于所有代码)

我用mudflap尝试了gcc,它允许它毫无错误地运行

#include <stdio.h>
int main()
{
    int a[2];
    a[-1] = 5;
    printf("%u\n", a[-1]);
    return 0;
}
#包括
int main()
{
int a[2];
a[-1]=5;
printf(“%u\n”,a[-1]);
返回0;
}
因此,似乎mudflap与我尝试过的其他版本一样不完整,只会降低开发的可能性。此外,它似乎旨在调试而不是生产使用。我想知道可证明的不可利用性。这是可以做到的。你有没有想过为什么它没有被广泛使用?对于这类漏洞利用所造成的数十亿美元(如果不是万亿美元的话)的损失来说,一次轻微的性能损失(甚至慢10倍,但可能慢2倍)似乎是一个很小的代价

编辑:澄清:

所谓“缓冲区溢出”,我的意思不是程序员的代码允许溢出,而是编译器允许目标变量/array/(m)allocblock之外的内存被它写入(或读取)(比如:int a,b;*(&b-1)应该被编译器捕获,而不仅仅是a)

所谓“证明”,我的通俗意思是“旧的简单Pascal不允许缓冲区溢出,几乎100%的确定性,我们可以说它已经证明了”,尽管它可能使用不安全的系统函数,但如果它们也是在边界检查Pascal中编写的,那么它们也不会发生溢出。我用“证明”一词来区别各种不完善的强化工具

“可利用性”指的是“缓冲区溢出可利用性”,这是一个简单的问题,在其他语言中以牺牲速度和内存为代价来解决


“你是认真的吗?如果它存在,我们早就这么做了。”-这就是我好奇的地方。技术就在这里-fat指针(C标准允许编译器生成任意大小的指针)具有完整的每指针边界检查。但我找不到更多关于它的概念证明、讨论和论文,而我想要的是一个完整的C编译器来实现这一点,并用它构建一个完整的Linux发行版。没有人会很快用一种更安全的语言重写所有东西(Linux、Apache等)(遗憾的是,他们一直在用C编写新东西),但我们可以让C/C++更安全,并重新编译所有东西。至少对于需要安全性高于一切的用途。

当然,只要你愿意放弃一些东西:

  • 指针数学
  • 未经检查的第三方库(有点破坏了这一点,不是吗?)
  • 以空结尾的C字符串
您只需滚动自己的分配器和解除定位器,将每个分配的大小写入头,然后使用宏进行指针/数组查找,以检查分配大小:

void *myalloc(size_t size) {
    if (size == 0) return NULL;
    void *data = malloc(size + sizeof(size_t));
    if (data == NULL) return NULL;
    *((size_t *)data) = size;
    return data + sizeof(size_t);
}

void mydealloc(void *data) {
    free(data - sizeof(size_t));
}

#define item(data, index) ({ \
    __typeof__(data) item_data = data; \
    __typeof__(index) item_index = index; \
    assert(item_index >= 0 && (item_index + 1) * sizeof(*item_data) <= *((size_t *)((void *)item_data - sizeof(size_t)))); \
    *(item_data + item_index); \
})
void*myalloc(大小){
如果(size==0),则返回NULL;
void*data=malloc(尺寸+sizeof(尺寸t));
if(data==NULL)返回NULL;
*((size_t*)数据)=大小;
返回数据+sizeof(size\u t);
}
void mydealloc(void*数据){
免费(数据-sizeof(size_t));
}
#定义项(数据、索引)({\
___uuu(数据)项的类型_u数据=数据\
___uuu(索引)项的类型_u索引=索引\

assert(item_index>=0&&(item_index+1)*sizeof(*item_data)这个问题有多种解决方案,基本上不使用C语言。它们主要做的是以某种运行时成本跟踪“危险的”指针访问(静态分析无法证明的是安全的)

  • 定义类C语言。(研究)
  • 声称在全C模式下运行(研究)
  • 为C的子集工作(研究)
  • 声称的管理费用为67%(研究,但不包括)
  • ,它可以诊断堆、堆栈和线程中的内存访问错误,用于完整的C语言。从我的公司购买,请参阅bio。[诊断玩具程序不会有问题]
当然,您可能会争辩说,所有这些都是编译C“checked”的解决方案,您在问题中似乎对此表示反对。我认为,在最坏的情况下,这些只是构建过程中的另一个步骤


真正的问题是这些解决方案在时间和空间上都有可测量的开销。在构建嵌入式系统时,这些额外的成本表现为在分配的时间内获得更昂贵的处理器来完成工作,和/或额外的内存来跟踪麻烦的指针。大多数制造商在选择即使感觉到一个坏程序的可能性很低(或者更糟糕的是,“在我卖掉所有这些之前,没有人会注意到!”),以及绝对真实的额外成本,也会倾向于优化成本,现在您可以在不进行运行时检查的情况下重新编译原始C程序。“便宜”会影响“质量”或“进度”.我们在航空座椅舒适性和软件安全性方面看到了这一点。

我们无法修复C,因此处理此类已知危险是专业程序员的责任。C的优点是它一直存在,因此所有的弱点、陷阱和定义不清的行为都是众所周知的,并有文档记录

您可以采取哪些措施来防止此类错误:

  • 将编译器设置为尽可能挑剔。在GCC中,对于这个特定的bug,它似乎没有任何帮助…据说有一些选项称为-Warray bounds和-fbounds check,但这些选项似乎不起作用,至少在GCC 4.9.1/Mingw64中不起作用。无论如何,您可以通过始终使用
    std=cxx-pedantic编译来捕获许多其他常见的bug-错误-Wall-Wextra
  • 使用静态分析工具。这些工具至少应该能够找到所有与边界检查相关的编译时错误
  • 对于外部工具,一种更便宜的替代方法是使用防御性编程。在这种情况下,您可能会通过断言发现错误。例如:

    #include <stdio.h>
    
    #define ARR_SIZE 2
    #define INDEX -1
    
    int main()
    {
        _Static_assert(INDEX < ARR_SIZE, "Array index too large.");
        _Static_assert(INDEX >= 0,       "Array index too small.");
    
        int a[ARR_SIZE];
        a[INDEX] = 5;
        printf("%u\n", a[-1]);
        return 0;
    }