如何防止在源代码中调用一些API?

为了开发ACM竞争的在线裁判,我们应该防止在用户提交的源代码中调用一些API。 例如,不允许在源代码中调用shutdown()socket() 。 如果源代码调用api,我们应该停止编译它或者在编译期间抛出错误,或者在运行时抛出错误。

我不知道如何在Linux或Windows上执行此操作; 你们可以给我一些build议吗?

首先:我建议不要再发明轮子。 已经有了裁判系统,也许你应该先看看他们(比如这里我们使用了DomJudge作为ACM竞赛裁判系统)。

第二:如已经建议的那样,您可以使用LD_PRELOAD链接到受限制的库。 另外一个选项,也可以用来防范其他一些被禁止的东西,那就是一个沙箱:建立一个chroot环境,在那里你只需要安装这些受限制的库,所以不可能访问非法的东西。

您需要使用内核强制的沙箱,例如“user-mode linux”或“capabilities”。

原因是系统调用不需要链接库, LD_PRELOAD对包含syscall指令的代码无效。 为了防止某人把机器代码放到一个数组中,然后跳到这个数组中,难以置信地困难,在C中有很多方法可以做到这一点(函数指针,堆栈碎片攻击等)。不可写代码段和非可写代码段可执行数据段将有所帮助,但唯一安全的方法是使用非特权用户帐户,以便内核无法通过EPERM调用。

 #define verboten_api(a1, a2, a3) you may not use this verboten API 

确保他们必须使用包含verboten API的头文件。

GNU提供了一个“不推荐”的属性。 从GCC 4.6.1手册:

deprecated
deprecated (msg)
如果函数在源文件中的任何位置使用,则不建议使用的属性会导致警告。 这在识别将在未来版本的程序中删除的函数时非常有用。 该警告还包括已弃用函数声明的位置,以使用户能够轻松地找到有关函数为什么不推荐使用的更多信息,或者他们应该做些什么。 请注意,警告只发生在用途上:

 int old_fn () __attribute__ ((deprecated)); int old_fn (); int (*fn_ptr)() = old_fn; 

会在第3行发出警告,但不会发生第2行。必须是字符串的可选msg参数将显示在警告中(如果存在)。 不建议使用的属性也可以用于变量和类型(请参见章节6.36 [变量属性],第295页,请参见部分6.37 [类型属性],第295页。

请注意,GCC提供了拒绝使用不推荐使用的函数编译代码的选项。

这些是编译时检查 – 而不是运行时检查。 他们可能也是侵入性的,除非你愿意破解使用的系统头。 另外,如果竞争者不使用系统标题,那么他们可能会逃避使用它们。

考虑创建一个静态库,它与它们的代码链接,定义了被禁止的函数,但是每个函数的实现是一个总是失败的断言:

 int verboten_api(int x, int y, char *z) { assert("function verboten_api() called" == 0); return -1; } 

将测试程序与该库链接。

Linux的具体答案:

 nm -D _the_compiled_binary_ | grep ' U ' 

将列出二进制使用(调用)的所有动态符号。

请记住,您不需要套接字库来访问网络,可以使用open()read()和write()来完成。 所以你可能需要某种类型的沙箱,而不仅限于代码中允许的内容。

过滤源代码是不够的。 即使源代码似乎没有调用API,也可能使用技巧来调用它。 例如,一个简单的基于正则表达式的过滤器可以通过令牌粘贴被击败。 这只是在源代码级别; 当你开始思考机器代码时,还有很多其他的可能性,从简单的内联汇编到面向返回的编程 ,并且可以在查看源代码时很难看到的方式完成,如无知的C比赛 。

所有API最终都会减少到内核API,因为程序员可以简单地复制API实现。 有AFAIK只有两个安全的方法来防止内核API被调用:要么在内核中过滤它,要么静态证明代码不能直接调用内核。 其他方式像LD_PRELOAD可以绕过。 绕过LD_PRELOAD很容易; 只是直接进行系统调用。

要在内核中过滤API,最新的方法是使用seccomp过滤器 ,它允许限制系统调用及其参数。 有了它,你可以很容易地禁止一个进程,例如永远不能调用shutdownsocket系统调用。 其他机制(名称空间,cgroups,chroot等)可以用来在过滤器之上添加其他种类的限制。

Google的Native Client使用统计学证明代码的替代方法是安全的。 它以一些简单的证明来限制生成的汇编代码,除了一些明确定义的方式之外,执行流程不能逃离沙盒。 作为这些规则的一个例子,没有指令可以跨越一个32字节的边界,所有的跳转目标都被对齐到一个32字节的边界,并且间接跳转只能通过一对指令屏蔽目标的低位跳转前的地址,所以没有办法跳到一条指令的中间。

首先你必须定义你不想被访问的API 。 我想这可能更容易做一个静态代码分析,并提出一个错误,如果一些不需要的#includes发生。