有害的C源文件检查?

6
有没有一种方法可以通过编程方式检查单个C源文件是否具有潜在的危害性?
我知道没有任何检查能够达到100%的准确性 - 但我至少希望进行一些基本检查,如果找到某些表达式/关键字,则会引起警觉。有什么想法可以寻找?
注意:我将要检查的文件相对较小(最多几百行),实现数值分析函数,所有函数都在内存中运行。代码中不应使用外部库(除了math.h)。此外,不应使用I / O(函数将在内存中运行)。
考虑到以上情况,我能否进行一些程序化的检查,以尝试至少检测出有害代码?
注意:由于我不期望进行任何I / O操作,如果代码执行I / O操作,则被视为有害。

你可以检查 #include 指令,看看是否包含了除“白名单”以外的头文件。 - Rafe Kettler
静态代码分析。简单地检查禁止使用的函数或任何不允许的 #include,如果有什么问题,请制作白名单并检查是否存在于您的白名单中,例如所有包含必须是 math.h,函数调用应该是本地文件函数或 math.h 函数等... - Jesus Ramos
@unkulunkulu,谢谢;当我们声明printf时,它可以正常工作--无需导入<stdio.h> - user3262424
只需在沙盒中运行它。这是唯一(实际)确保安全的方法。 - Antimony
在Linux上也有prctl(PR_SET_SECCOMP) - Vi.
显示剩余4条评论
3个回答

8
是的,有编程方法可以检测你关心的条件。
在我看来,你最好使用静态分析工具来验证代码的预处理版本是否:
1. 不调用除自身定义和标准库中的非 I/O 函数之外的任何函数, 2. 不对指针进行任何不良操作。
通过预处理,您可以消除检测宏、可能存在的坏宏内容以及实际使用宏的问题。此外,您不想浏览所有标准 C 标头中的宏定义;它们会因为包含所有历史垃圾而伤害您的灵魂。
如果代码只调用自己的函数和标准库中可信赖的函数,则不会调用任何不良操作。(注意:它可能通过指针调用某些函数,因此此检查需要函数指向分析或同意间接函数调用是被禁止的,这实际上对于执行数值分析的代码来说可能是合理的)。
检查指针是否存在不良操作的目的是为了防止滥用指针制造不良代码并将控制权传递给它。这首先意味着“不要从 int 强制转换为指针”,因为您不知道 int 来自哪里 :-}。
对于who-does-it-call检查,您需要解析代码并解析每个符号的名称/类型,然后检查调用站点以确定它们去哪里。如果允许使用指针/函数指针,则需要进行完整的指向分析。
标准静态分析器工具公司(Coverity、Klocwork)中的一个可能提供某种限制代码块可以调用哪些函数的方法。如果这不起作用,您将不得不退回到更通用的分析机器上,例如我们的DMS软件重工具包及其C前端。DMS提供了可定制的机器来构建任意静态分析器,对于作为前端提供给它的语言描述。DMS可以配置为执行测试1)(包括预处理步骤);它还具有完整的指向和函数指向分析器,可用于指向检查。
对于2)“不恶意使用指针”,标准的静态分析工具公司提供了一些指针检查。然而,这里他们面临着一个更困难的问题,因为他们正在静态地尝试推理一个图灵机。他们的解决方案要么是错过案例,要么是报告错误的结果。我们的CheckPointer工具是一种动态分析,也就是说,它会在代码运行时观察代码,如果有任何试图滥用指针的情况,CheckPointer将立即报告违规位置。哦,是的,CheckPointer禁止从int到指针的转换:-} 所以CheckPointer不会提供静态诊断“这段代码可能会作弊”,但如果它实际上尝试作弊,你将得到一个诊断。由于所有这些检查都需要付出一定代价,因此CheckPointer的开销相当高,所以你可能需要运行一段时间的代码来获得一些信心,以确保不会发生任何不良情况,然后停止使用它。

编辑:另一位帖子写道对于静态定义的缓冲区,你不能做太多事情。CheckPointer将执行这些测试以及更多其他测试。


1
还有一个问题是堆栈溢出和无限循环是否被认为是有害的。如果是这样,就需要添加对这些情况的检查,这也可能非常棘手。定义“有害”并不是一件简单的事情。 - luiscubal
1
已经确定无法得到完美答案。可以通过坚持不使用直接或间接递归来消除堆栈溢出,这可以通过构建调用图(DMS可以实现此操作)并检查循环来检测。无限循环可能更难停止,但如果感到不安,可以通过构建自己的计时器机制来控制它。 - Ira Baxter
上次评论时忘了提到,限制本地函数变量的大小/数量(例如不要使用int x [10000000];)可能也是一个好主意。我并不是要对这个答案进行负面批评(我已经点赞了),只是提供补充信息。 - luiscubal
使用太多本地变量会导致堆栈溢出吗?我想这可能会发生,但通常当函数被调用时,它会分配必要的堆栈空间;如果它使用了大量的空间,程序很可能会立即陷入页面错误。尽管如此,这仍然是真实存在的问题。像DMS这样的静态分析工具可以配置为检查过大的分配或荒谬的本地变量数量。 - Ira Baxter
Ira,感谢您详细的回答。这些工具是免费的吗?我的回答主要是针对可能的编程思路--也就是说,在编写.c源文件时,我应该从程序上寻找什么(比如,编写Python脚本)。 - user3262424
这两个工具都是商业软件。在我看来,CheckPointer的价格适中。如果你的代码比较小,CheckPointer的评估版本可能会运行它们。DMS的价格要高得多,市场也小得多。但价值相应更大;你可以尝试使用Python脚本编写某种类型的检查器,但你会发现,像C这样的真实语言需要像DMS这样的引擎所拥有的所有机制来处理,而这些机制并不容易构建。人们试图通过滥用其他引擎(如GCC)来解决这个问题。Clang是最近的选择。 - Ira Baxter

3
如果您想确保代码没有调用任何不允许的东西,那么编译这段代码并通过 nm 检查其链接的内容。因为您在使用“编程”方法时遇到了困难,所以只需使用 python/perl/bash 来编译,然后扫描目标文件的名称列表即可。
对于静态定义的缓冲区,您无法做太多事情,但是可以链接到类似 electric-fence 的内存分配器,以防止动态分配的缓冲区溢出。
您还可以将 C 文件编译和链接到一个驱动程序中,在 valgrind 下运行时向其提供典型数据,这可以帮助检测编写得不好或恶意代码。
然而,最终您总会遇到“这个例程是否终止”的问题,这是著名的不可判定问题。解决这个问题的一种实用方法是编译程序,并从驱动程序中运行它,在合理的时间内设置 alarm 后退出。 编辑:下面是使用 nm 的示例:
创建一个 C 片段,定义调用 fopen 的函数 foo:
#include <stdio.h>
foo() {
   FILE *fp = fopen("/etc/passwd", "r");
}

使用-c进行编译,然后查看生成的目标文件:

$ gcc -c foo.c
$ nm foo.o
0000000000000000 T foo
                 U fopen

在这里,您将看到foo.o目标文件中有两个符号。一个是定义的foo,是我们编写的子程序的名称。另一个是未定义的fopen,当目标文件与其他C文件和必要的库链接在一起时,将被链接到其定义。使用此方法,您可以立即查看编译对象是否引用其自身定义之外的任何内容,并根据您的规则,可以认为是“不良”的。

马克,谢谢。你能详细说明一下 nm 是什么以及如何使用它吗? - user3262424
@user3262424 - 更新了文本,展示了nm的使用示例。此外,还有关于它的手册页面(例如man nm)。 - unpythonic

0
你可以对“不良”函数调用进行一些明显的检查,例如网络IO或汇编块。除此之外,我想不出你只能使用C文件做什么。
考虑到C的性质,你几乎必须编译才能开始。宏等使得对C代码进行静态分析变得非常困难。

Andrew White,谢谢。有没有好的方法来识别“宏”?另外,我如何识别“汇编块”? - user3262424
只需查找 # 或者使用自动展开宏的 gcc -E 即可。 - Andrew White
谢谢Andrew。你能提供更多关于gcc -E的信息以及它如何在这种情况下有所帮助吗? - user3262424
它基本上做了我说的事情,它将宏替换为它们所评估的内容,这将有助于稍微清晰化代码。 - Andrew White
谢谢。有没有一种编程的方法来识别代码中的宏? - user3262424

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接