【问题标题】:How to prevent some api being invoked in a source code?如何防止在源代码中调用某些 api?
【发布时间】:2011-06-30 13:28:58
【问题描述】:

为ACM比赛开发在线评委,我们应该防止用户提交的源代码中调用某些api。 例如,在源代码中不允许调用shutdown() 或socket()。如果源代码调用了api,我们应该停止编译或者编译时抛出错误,或者运行时抛出错误。

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

【问题讨论】:

  • 在 linux 上你可以使用 LD_PRELOAD 并使用一个自编译的库来覆盖所有被禁止的函数并在调用时中止。
  • 当然,你必须禁止使用inline ASM,否则你所做的一切都是徒劳的。

标签: c windows linux compiler-construction


【解决方案1】:

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

第二:如前所述,您可以使用 LD_PRELOAD 链接到受限库。沙盒是另一个选项,它也适用于其他一些被禁止的东西作为保护:设置一个 chroot 环境,您只需安装那些受限制的库,因此不可能访问非法的东西。

【讨论】:

  • DomJudge 是针对特定类型的编程竞赛的,他们可能会进行 ACM ICPC 类型竞赛或 UVa 在线评审竞赛,在这种情况下,DomJudge 会在事件时间范围内返回结果太慢(这是固定的)。
  • @Edwin:问题中确实没有足够的信息来了解约束是什么。例如,在标签中唯一提到了 C。
  • @Jonathan,同意了。答案可能很好(取决于细节),但问题过于宽泛。
【解决方案2】:

您需要使用内核强制沙箱,例如“用户模式 ​​linux”或“功能”。

原因是系统调用不需要链接库,LD_PRELOAD 对包含syscall 指令的代码无效。并且试图阻止某人将机器代码放入一个数组然后跳转到它是非常困难的,在 C 中有很多方法可以做到这一点(函数指针,堆栈粉碎攻击等)不可写代码段和非可执行数据段会有所帮助,但唯一安全的方法是使用非特权用户帐户,以便内核调用 EPERM 失败。

【讨论】:

    【解决方案3】:
    #define verboten_api(a1, a2, a3) you may not use this verboten API
    

    确保他们必须使用包含详细 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 参数,它 必须是字符串,如果存在,将在警告中打印。 deprecated 属性也可用于变量和类型(参见 第 6.36 节 [变量属性],第 341 页,参见第 6.37 节 [类型属性], 第 350 页。)

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

    这些是编译时检查 - 与运行时检查相反。它们可能也是侵入性的,除非您愿意破解使用的系统标头。此外,如果竞争对手不使用系统标头,那么他们可能会侥幸使用它们。

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

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

    将测试程序与该库链接。

    【讨论】:

    • 你能解释一下为什么你把德语单词和英语指令混在一起吗?
    • @Constantinius:为什么不呢?我喜欢“禁止”这个词;它通常被相当博学的人理解。 @MK:这部分取决于设置的对抗性。我希望“在线法官”会分析源代码和目标代码。它可以很好地提供它用来链接的库。但这取决于我们目前掌握的更多信息。
    • @Constantinius:实际上,韦伯斯特甚至列出了“verboten”:merriam-webster.com/dictionary/verboten
    • @Jonathan 在线评委通常只是在一些预定义的 inptus 上运行代码并验证输出。分析代码无助于确定它是否有效(您甚至无法判断它是否停止;)限制库对您没有任何好处,因为您仍然可以直接调用系统调用。
    • @MK:如果你的(自动的,在线的)评委是那么松懈,那么是的。有诸如-fno-asm 之类的GCC 编译器选项可以强加于编译。这取决于制定的规则。
    【解决方案4】:

    Linux 特定答案:

    nm -D _the_compiled_binary_ | grep ' U '
    

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

    【讨论】:

    • 那么一定要把dlopen列入黑名单。
    【解决方案5】:

    请记住,您不需要套接字库来访问网络,您可以使用 open() read() 和 write()。因此,您可能需要某种沙箱,而不仅仅是对代码中允许的内容进行限制。

    【讨论】:

      【解决方案6】:

      过滤源代码是不够的。即使源代码似乎没有调用 API,它也可能使用技巧来调用它。例如,一个简单的基于正则表达式的过滤器可以通过令牌粘贴来破坏。那只是在源代码级别;当你开始思考机器码的时候,还有很多其他的可能性,从简单的内联汇编到return-oriented programming,并且可以通过查看源代码时很难看到的方式来完成,如Underhanded C Contest.

      所有 API 最终都归结为内核 API,因为程序员可以简单地复制 API 实现,否则。 AFAIK 只有两种安全的方法可以防止调用内核 API:要么在内核中过滤它,要么静态证明代码不能直接调用内核。可以绕过LD_PRELOAD 等其他方式。绕过LD_PRELOAD 很容易;直接做系统调用就好了。

      要在内核中过滤 API,最新的方法是使用seccomp filters,它允许限制系统调用及其参数。有了它,您可以轻松地禁止进程,例如调用shutdown 和socket 系统调用。其他机制(命名空间、cgroup、chroot 等)可用于在过滤器之上添加其他类型的限制。

      Google 的Native Client 使用了统计证明代码安全的替代方法。它以允许简单证明执行流程无法逃离沙箱的方式限制生成的汇编代码,除非以一些明确定义的方式。作为此类规则的示例,没有指令可以跨越 32 字节边界,所有跳转目标都与 32 字节边界对齐,并且仅允许通过一对屏蔽目标低位的指令进行间接跳转跳转前的地址,所以无法跳转到指令中间。

      【讨论】:

        【解决方案7】:

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

        【讨论】:

        • 您无需#include 即可使用 API:只需声明您要调用的任何函数。
        • 另外,函数名永远不需要出现在程序中,创造性地使用宏。
        • @Ben:并不是说这个答案有效,但您的特定反对意见将通过在预处理后执行分析来克服。
        • @Steve:检查#include,正如这个答案所暗示的,意味着分析是在预处理之前完成的。
        • @Ben:因此条件虚拟语气“会”。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-02-02
        • 2019-10-20
        • 1970-01-01
        • 2015-11-18
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多