我想构建一个C预处理器/编译器,允许从本地和在线资源收集功能.即:
#fetch MP3FileBuilder http://scripts.com/MP3Builder.gz
#fetch IpodDeviceReader http://apple.com/modules/MP3Builder.gz
void mymodule_main() {
MP3FileBuilder(&some_data);
}
Run Code Online (Sandbox Code Playgroud)
这很容易.
困难的部分是我需要一种可靠的方法来"沙箱化"导入的代码,直接或不受限制地访问磁盘或系统资源(包括内存分配和堆栈).我想要一种方法来安全地运行不受信任的C代码(模块)的小片段,而没有将它们放在单独的进程,VM或解释器中的开销(尽管可以接受单独的线程).
要求
我很乐意接受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;
}
Run Code Online (Sandbox Code Playgroud)
这是一个转换版本,我们的预处理器添加了观察点来检查内存使用和递归,并将整个事件包装在异常处理程序中.
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;
}
Run Code Online (Sandbox Code Playgroud)
我意识到执行这些检查会影响模块性能,但我怀疑它仍将胜过高级或VM语言以完成它要解决的任务.我不是试图阻止模块直接做危险的事情,我只是试图以受控的方式强迫那些危险的事情发生(比如通过用户反馈).即:"模块X超出了它的内存分配,继续或中止?".
UPDATE
到目前为止,我得到的最好的是使用带有边界检查的自定义编译器(如黑客TCC)和一些自定义函数和循环代码来捕获递归.我仍然想听听我需要检查的其他内容或者有什么解决方案的想法.我想在使用之前删除ASM并检查指针解决了以下前面答案中表达的许多问题.我添加了一笔赏金来撬开SO社区的更多反馈.
对于我正在寻找的赏金:
对于可以与GCC一起使用的方法(即预处理器或小型 GCC补丁)的额外功劳.
我还要考虑任何能够最终证明我正在尝试的东西根本无法完成的人.你需要非常有说服力,因为到目前为止,没有任何反对意见真正确定了为什么他们认为这是不可能的技术方面.在辩护那些说没有这个问题的人最初被认为是一种安全运行C++的方法.我现在已经将需求缩减到C的有限子集.
我对C的理解可以归类为"中间",我对PC硬件的理解可能比"高级"低一步.如果可以,尝试指导您的答案.由于我不是C专家,我将主要基于给出答案的投票以及答案对我的要求有多接近.您可以通过为您的索赔(受访者)和投票(其他人)提供充分的证据来提供帮助.一旦赏金倒计时达到6小时,我会给出一个答案.
最后,我相信解决这个问题将是在日益网络化和偏执的世界中保持C的相关性的重要一步.随着其他语言缩小性能差距和计算能力的增长,证明C开发的额外风险(就像现在的ASM一样)将变得越来越难.我相信你的答案将比得分SO点有更大的相关性,所以即使赏金已经过期,请尽可能贡献.
Rut*_*ing 16
由于C标准太宽泛而不允许,您需要反过来:指定您需要的C的最小子集,并尝试实现它.即使ANSI C已经过于复杂并且允许不必要的行为.
最有问题的C方面是指针:C语言需要指针arithmitic,并且不检查那些.例如:
char a[100];
printf("%p %p\n", a[10], 10[a]);
Run Code Online (Sandbox Code Playgroud)
将打印相同的地址.自从a[10] == 10[a] == *(10 + a) == *(a + 10).
在编译时无法检查所有这些指针访问.这与向编译器询问"程序中的所有错误"一样复杂,这需要解决暂停问题.
由于您希望此函数能够在同一进程中运行(可能在不同的线程中),因此您可以在应用程序和"安全"模块之间共享内存,因为这是拥有线程的重点:共享数据以便更快地访问.但是,这也意味着两个线程都可以读写相同的内存.
而且由于你无法证明指针结束的编译时间,你必须在运行时这样做.这意味着像'a [10]'这样的代码必须被翻译成类似'get_byte(a + 10)'的东西,此时我不再称它为C.
Google Native Client
所以,如果这是真的,谷歌怎么做呢?嗯,与此处的要求(跨平台(包括嵌入式系统))相比,Google专注于x86,除了页面保护之外,还有分段寄存器.这允许它创建一个沙箱,其中另一个线程不以相同的方式共享相同的内存:沙箱通过分段限制为仅更改其自己的内存范围.此外:
所以这是特定于平台的,并不是一个"简单"的解决方案,尽管是一个有效的解决方案 阅读他们的研究论文.
结论
所以无论你走哪条路,你都需要从可以验证的新东西开始,然后才能开始调整现有的编译器或生成新的编译器.但是,尝试模仿ANSI C需要考虑指针问题.Google模仿他们的沙箱不是在ANSI C上,而是在x86的一个子集上,这使得他们可以在很大程度上使用现有的编译器,但缺点是与x86绑定.
我想你会在阅读谷歌在设计Native Client时所做的一些实现问题和选择,这是一个在浏览器中执行x86代码(安全地,我们希望)的系统.您可能需要进行一些源代码重写或源代码到源代码编译,以使代码安全,如果不是,但是如果它试图做任何太奇怪的事情,您应该能够依赖NaCL沙箱来捕获生成的汇编代码.
我偶然发现了Tiny C Compiler (TCC)。这可能是我需要的:
* SMALL! You can compile and execute C code everywhere, for example on rescue disks (about 100KB for x86 TCC executable, including C preprocessor, C compiler, assembler and linker).
* FAST! tcc generates x86 code. No byte code overhead. Compile, assemble and link several times faster than GCC.
* UNLIMITED! Any C dynamic library can be used directly. TCC is heading torward full ISOC99 compliance. TCC can of course compile itself.
* SAFE! tcc includes an optional memory and bound checker. Bound checked code can be mixed freely with standard code.
* Compile and execute C source directly. No linking or assembly necessary. Full C preprocessor and GNU-like assembler included.
* C script supported : just add '#!/usr/local/bin/tcc -run' at the first line of your C source, and execute it directly from the command line.
* With libtcc, you can use TCC as a backend for dynamic code generation.
Run Code Online (Sandbox Code Playgroud)
这是一个非常小的程序,它使黑客成为一个可行的选择(黑客 GCC?,这辈子都没有!)。我怀疑它将为构建我自己的受限编译器奠定良好的基础。我将取消对无法确保安全的语言功能的支持,并包装或替换内存分配和循环处理。
libtcc 也是一个很棒的功能,因为我可以在内部管理代码编译。
我不希望它很容易,但它让我希望我可以在风险较小的情况下获得接近 C 的性能。
不过还是想听听其他的想法。
这不是微不足道的,但并不难.
您可以在沙箱中运行二进制代码.每个操作系统都会整天都这样做.
他们将不得不使用您的标准库(与通用C lib相比).您的标准库将强制执行您想要强制执行的任何控件.
接下来,您需要确保它们无法在运行时创建"可运行代码".也就是说,堆栈不可执行,它们不能分配任何可执行的内存等.这意味着只有编译器(您的编译器)生成的代码才是可执行的.
如果您的编译器以加密方式签署其可执行文件,您的运行时将能够检测到被篡改的二进制文件,并且根本不加载它们.这可以防止他们将内容"戳"到您根本不希望他们拥有的二进制文件中.
使用受控编译器生成"安全"代码和受控系统库,即使使用实际的机器语言代码,也应该提供合理控制的沙箱.
想要施加内存限制吗?把登记入住malloc.想限制分配多少堆栈?限制堆栈段.
操作系统整天使用虚拟内存管理器创建这些受限制的环境,因此您可以在现代操作系统上轻松完成这些操作.
使用现成的虚拟机和字节码运行时,是否值得这样做是值得的,我不能说.