当前位置:  开发笔记 > 编程语言 > 正文

如何创建轻量级C代码沙箱?

如何解决《如何创建轻量级C代码沙箱?》经验,为你挑选了4个好方法。

我想构建一个C预处理器/编译器,允许从本地和在线资源收集功能.即:

#fetch MP3FileBuilder http://scripts.com/MP3Builder.gz
#fetch IpodDeviceReader http://apple.com/modules/MP3Builder.gz

void mymodule_main() {
  MP3FileBuilder(&some_data);
}

这很容易.

困难的部分是我需要一种可靠的方法来"沙箱化"导入的代码,直接或不受限制地访问磁盘或系统资源(包括内存分配和堆栈).我想要一种方法来安全地运行不受信任的C代码(模块)的小片段,而没有将它们放在单独的进程,VM或解释器中的开销(尽管可以接受单独的线程).

要求

我需要对其访问数据和资源(包括CPU时间)进行配额.

我将阻止直接访问标准库

我想阻止创建无限递归的恶意代码

我想将静态和动态分配限制为特定限制

我想捕获模块可能引发的所有异常(例如除以0).

模块只能通过核心接口与其他模块交互

模块只能通过核心接口与系统(I/O等)进行交互

模块必须允许位操作,数学,数组,枚举,循环和分支.

模块不能使用ASM

我想限制指针和数组访问为模块保留的内存(通过自定义safe_malloc())

必须支持ANSI C或子集(见下文)

系统必须是轻量级和跨平台的(包括嵌入式系统).

系统必须与GPL或LGPL兼容.

我很乐意接受C的一个子集.我不需要像模板或类这样的东西.我主要对高级语言不能像快速数学,位操作以及二进制数据的搜索和处理这样的事情感兴趣.

这是现有C代码可以不加修改地重复使用,以创建一个模块的意图.目的是要求模块符合一组规则和限制,旨在将模块限制为基本逻辑和转换操作(例如视频转码或压缩操作).

这种编译器/预处理器的理论输入是单个ANSI C文件(或安全子集),带有module_main函数,NO包含或预处理器指令,没有ASM,它将允许循环,分支,函数调用,指针数学(限于分配给模块的范围),位移,位域,强制转换,枚举,数组,整数,浮点数,字符串和数学.其他任何东西都是可选的.

示例实施

这是一个伪代码片段,可以更好地解释这一点.这里一个模块超出了它的内存分配配额,并且还创建了无限递归.

buffer* transcodeToAVI_main( &in_buffer ) {
    int buffer[1000000000]; // allocation exceeding quota
    while(true) {} // infinite loop
    return buffer;
}

这是一个转换版本,我们的预处理器添加了观察点来检查内存使用和递归,并将整个事件包装在异常处理程序中.

buffer* transcodeToAVI_main( &in_buffer ) {
    try {
        core_funcStart(__FILE__,__FUNC__); // tell core we're executing this function
        buffer = core_newArray(1000000000, __FILE__, __FUNC__); // memory allocation from quota
        while(true) {
           core_checkLoop(__FILE__, __FUNC__, __LINE__) && break; // break loop on recursion limit
        } 
        core_moduleEnd(__FILE__,__FUNC__);
    } catch {
        core_exceptionHandler(__FILE__, __FUNC__);
    }
    return buffer;
}

我意识到执行这些检查会影响模块性能,但我怀疑它仍将胜过高级或VM语言以完成它要解决的任务.我不是试图阻止模块直接做危险的事情,我只是试图以受控的方式强迫那些危险的事情发生(比如通过用户反馈).即:"模块X超出了它的内存分配,继续或中止?".

UPDATE

到目前为止,我得到的最好的是使用带有边界检查的自定义编译器(如黑客TCC)和一些自定义函数和循环代码来捕获递归.我仍然想听听我需要检查的其他内容或者有什么解决方案的想法.我想在使用之前删除ASM并检查指针解决了以下前面答案中表达的许多问题.我添加了一笔赏金来撬开SO社区的更多反馈.

对于我正在寻找的赏金:

针对上面定义的理论系统的潜在攻击的细节

可能优化检查每次访问的指针

概念的实验性开源实现(如Google Native Client)

支持各种操作系统和设备的解决方案(无基于操作系统/硬件的解决方案)

支持大多数C操作的解决方案,甚至是C++(如果可能的话)

对于可以与GCC一起使用的方法(即预处理器或小型 GCC补丁)的额外功劳.

我还要考虑任何能够最终证明我正在尝试的东西根本无法完成的人.你需要非常有说服力,因为到目前为止,没有任何反对意见真正确定了为什么他们认为这是不可能的技术方面.在辩护那些说没有这个问题的人最初被认为是一种安全运行C++的方法.我现在已经将需求缩减到C的有限子集.

我对C的理解可以归类为"中间",我对PC硬件的理解可能比"高级"低一步.如果可以,尝试指导您的答案.由于我不是C专家,我将主要基于给出答案的投票以及答案对我的要求有多接近.您可以通过为您的索赔(受访者)和投票(其他人)提供充分的证据来提供帮助.一旦赏金倒计时达到6小时,我会给出一个答案.

最后,我相信解决这个问题将是在日益网络化和偏执的世界中保持C的相关性的重要一步.随着其他语言缩小性能差距和计算能力的增长,证明C开发的额外风险(就像现在的ASM一样)将变得越来越难.我相信你的答案将比得分SO点有更大的相关性,所以即使赏金已经过期,请尽可能贡献.



1> Rutger Nijlu..:

由于C标准太宽泛而不允许,您需要反过来:指定您需要的C的最小子集,并尝试实现它.即使ANSI C已经过于复杂并且允许不必要的行为.

最有问题的C方面是指针:C语言需要指针arithmitic,并且不检查那些.例如:

char a[100];
printf("%p %p\n", a[10], 10[a]);

将打印相同的地址.自从a[10] == 10[a] == *(10 + a) == *(a + 10).

在编译时无法检查所有这些指针访问.这与向编译器询问"程序中的所有错误"一样复杂,这需要解决暂停问题.

由于您希望此函数能够在同一进程中运行(可能在不同的线程中),因此您可以在应用程序和"安全"模块之间共享内存,因为这是拥有线程的重点:共享数据以便更快地访问.但是,这也意味着两个线程都可以读写相同的内存.

而且由于你无法证明指针结束的编译时间,你必须在运行时这样做.这意味着像'a [10]'这样的代码必须被翻译成类似'get_byte(a + 10)'的东西,此时我不再称它为C.

Google Native Client

所以,如果这是真的,谷歌怎么做呢?嗯,与此处的要求(跨平台(包括嵌入式系统))相比,Google专注于x86,除了页面保护之外,还有分段寄存器.这允许它创建一个沙箱,其中另一个线程不以相同的方式共享相同的内存:沙箱通过分段限制为仅更改其自己的内存范围.此外:

安装了一系列安全的x86汇编构造

gcc被更改为发出那些安全的结构

此列表以可验证的方式构建.

加载模块后,此验证完成

所以这是特定于平台的,并不是一个"简单"的解决方案,尽管是一个有效的解决方案 阅读他们的研究论文.

结论

所以无论你走哪条路,你都需要从可以验证的新东西开始,然后才能开始调整现有的编译器或生成新的编译器.但是,尝试模仿ANSI C需要考虑指针问题.Google模仿他们的沙箱不是在ANSI C上,而是在x86的一个子集上,这使得他们可以在很大程度上使用现有的编译器,但缺点是与x86绑定.



2> Matt J..:

我想你会在阅读谷歌在设计Native Client时所做的一些实现问题和选择,这是一个在浏览器中执行x86代码(安全地,我们希望)的系统.您可能需要进行一些源代码重写或源代码到源代码编译,以使代码安全,如果不是,但是如果它试图做任何太奇怪的事情,您应该能够依赖NaCL沙箱来捕获生成的汇编代码.



3> Josh Kelley..:

如果我打算这样做,我会调查两种方法之一:

使用CERN的CINT在解释器中运行沙盒代码,并查看限制解释器允许的内容.这可能不会给出非常好的表现.

使用LLVM创建C++代码的中间表示,然后查看在沙盒Java样式的VM中运行该字节码是否可行.

但是,我同意其他人的说法,这可能是一个非常复杂的项目.看看网络浏览器遇到的错误或挂起插件会破坏整个浏览器的问题.或者查看Wireshark项目的发行说明; 似乎几乎每个版本都包含其协议解析器中的问题的安全修复程序,然后影响整个程序.如果C/C++沙箱是可行的,我希望这些项目现在可以锁定到一个.



4> Will Hartung..:

这不是微不足道的,但并不难.

您可以在沙箱中运行二进制代码.每个操作系统都会整天都这样做.

他们将不得不使用您的标准库(与通用C lib相比).您的标准库将强制执行您想要强制执行的任何控件.

接下来,您需要确保它们无法在运行时创建"可运行代码".也就是说,堆栈不可执行,它们不能分配任何可执行的内存等.这意味着只有编译器(您的编译器)生成的代码才是可执行的.

如果您的编译器以加密方式签署其可执行文件,您的运行时将能够检测到被篡改的二进制文件,并且根本不加载它们.这可以防止他们将内容"戳"到您根本不希望他们拥有的二进制文件中.

使用受控编译器生成"安全"代码和受控系统库,即使使用实际的机器语言代码,也应该提供合理控制的沙箱.

想要施加内存限制吗?把登记入住malloc.想限制分配多少堆栈?限制堆栈段.

操作系统整天使用虚拟内存管理器创建这些受限制的环境,因此您可以在现代操作系统上轻松完成这些操作.

使用现成的虚拟机和字节码运行时,是否值得这样做是值得的,我不能说.

推荐阅读
Life一切安好
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有