使用过时的 C 编译器是否存在安全风险?

我们在生产中有一些没有人关心的构建系统,这些机器运行 GCC 的古老版本,如 GCC 3或 GCC 2。

我无法说服管理层把它升级到更新的版本: 他们说,“如果没有坏,就不要修理它”。

由于我们维护一个非常古老的代码库(编写于80年代) ,这个 C89代码在这些编译器上编译得很好。

但我不确定用这些旧东西是个好主意。

我的问题是:

使用旧的 C 编译器会损害已编译程序的安全性吗?

更新:

同样的代码是 Visual Studio 2008为 Windows 目标编写的,MSVC 还不支持 C99或 C11(我不知道新的 MSVC 是否支持) ,我可以使用最新的 GCC 在我的 Linux 机器上编写它。因此,如果我们只是降低一个新的海湾合作委员会,它可能会建立一样好,因为以前。

10445 次浏览

使用旧的 C 编译器会损害已编译程序的安全性吗?

当然可以,如果旧编译器包含您知道会影响程序的已知错误。

问题是,是吗?为了确定,您必须阅读从您的版本到当前日期的整个更改日志,并检查多年来修复的每一个 bug。

如果您没有发现编译器错误的证据会影响您的程序,那么仅仅为了更新 GCC 而更新它似乎有点偏执。您必须记住,新版本可能包含尚未发现的新 bug。最近在支持 GCC 5和 C11的情况下做了很多改变。

也就是说,80年代编写的代码很可能已经充满了安全漏洞,并且依赖于定义不明确的行为,而不管编译器是什么。我们现在讨论的是标准化前的 C 语言。

恶意开发人员可以通过编译器漏洞从后门溜进来,这是一种安全风险。根据编译器中使用的已知 bug 的数量,后门程序可能看起来或多或少不那么显眼(在任何情况下,关键是代码是正确的,即使在源代码级别是复杂的。使用没有 bug 的编译器进行源代码检查和测试将不会发现后门,因为在这些情况下后门并不存在)。为了获得额外的否认点,恶意开发人员还可能自己寻找以前未知的编译器错误。再次,伪装的质量将取决于选择编译器错误发现。

Bcrypt 为 Javascript 迷你版编写了一个很好的后续程序。

除此之外,C 语言编译器的发展趋势是积极地利用未定义行为 更多更多以及 更多,所以那些真诚地编写的旧的 C 语言代码实际上可以更安全地用当时的 C 语言编译器进行编译,或者在 -00时进行编译(但是有一些新的程序破解 UB-利用优化 在新版本的编译器中引入-O0)。

如果没坏,就别修了

你的老板说的没错,但是,更多的 很重要因素,是保护输入,输出,缓冲区溢出。无论使用何种编译器,从这个角度来看,缺乏这些必然是链中最薄弱的环节。

然而,如果代码库是古老的,并且已经采取措施来减轻所使用的 K & R C 的弱点,例如缺乏类型安全性、不安全的 fgets 等,那么就要权衡问题“ 将编译器升级到更现代的 C99/C11标准会打破一切吗?

如果有一条清晰的路径可以迁移到新的 C 标准,这可能会引起副作用,那么最好尝试一下旧的代码库,评估它,并进行额外的类型检查,健全性检查,并确定升级到新的编译器是否对输入/输出数据集有任何影响。

然后你可以把它展示给你的老板,“ 这是更新后的代码库,经过重构,更符合行业公认的 C99/C11标准..。”。

这是一个需要权衡的赌博,非常小心抗拒改变可能会在那种环境下出现,并且可能拒绝接触新的东西。

剪辑

只是坐了几分钟,意识到这么多,K & R 生成的代码可以运行在一个16位的平台上,机会是,升级到更现代的编译器实际上可以打破代码基础,我认为在架构方面,32位代码将生成,这可能有有趣的副作用,用于结构的输入/输出数据集,这是另一个 巨大因素,仔细权衡。

另外,由于 OP 提到使用 Visual Studio 2008来构建代码库,使用 gcc 可能会导致将 MinGW 或 Cygwin 引入到环境中,这可能会对环境产生影响,除非,目标是 Linux,那么值得一试,可能需要包含额外的编译器开关,以尽量减少旧的 K & R 代码库的噪音,另一个重要的事情是进行大量的测试,以确保功能不被破坏,可能会是一个痛苦的练习。

编译后的代码包含可以利用的错误。这些错误来自三个方面: 源代码中的错误、编译器和库中的错误以及源代码中未定义的行为,这些行为被编译器变成了一个错误。(未定义的行为是一个错误,但在编译的代码中还不是一个错误。例如,在 C 或 C + + 中,i = i + + 是一个 bug,但是在您编译的代码中,它可能会将 i 增加1,变为 OK,或者将 i 设置为一些垃圾,变为 bug)。

由于测试和修复客户错误报告导致的错误,编译代码中的错误率可能很低。所以最初可能有大量的 bug,但现在已经减少了。

如果升级到较新的编译器,可能会丢失由编译器错误引入的错误。但据你所知,这些漏洞都是没人发现也没人利用的漏洞。但是新的编译器可能有自己的 bug,更重要的是新的编译器有一个更强的倾向,就是把未定义的行为变成编译代码中的 bug。

因此,您的编译代码中将有大量新的 bug; 所有这些 bug 都是黑客可以发现和利用的。除非你做了大量的测试,并且让你的代码长时间留给客户去发现 bug,否则它的安全性就会降低。

与使用新的编译器相比,旧编译器中的任何 bug 都有更高的可能性是众所周知的,并且有文档记录,因此可以采取行动避免这些 bug,围绕它们编写代码。所以在某种程度上,这还不足以作为升级的理由。在我工作的地方,我们进行了同样的讨论,我们在嵌入式软件的代码库中使用 GCC 4.6.1,由于担心新的、没有文档记录的 bug,管理层非常不愿意升级到最新的编译器。

事实上,我的观点正好相反。

在很多情况下,C 标准没有定义行为,但是很明显在给定的平台上使用“哑编译器”会发生什么。例如允许有符号整数溢出或通过两种不同类型的变量访问相同的内存。

最近版本的 gcc (和 clang)已经开始将这些情况视为优化机会,不在乎它们是否改变了二进制在“未定义行为”条件下的行为。如果代码库是由把 C 当作“便携汇编程序”的人编写的,那么这种情况就非常糟糕。随着时间的推移,优化者在进行这些优化时,开始关注越来越大的代码块,这增加了二进制文件最终做一些事情的可能性,而不是“愚蠢的编译器构建的二进制文件会做的事情”。

有一些编译器开关可以用来恢复“传统”行为(上面提到的两个开关-fwrapv 和-fno-strong-aliase) ,但是首先您必须了解它们。

虽然原则上,编译器的错误可能会把顺从的代码变成一个安全漏洞,但我认为这种风险在大体上是可以忽略不计的。

这两种做法都存在风险。


较老的编译器具有成熟的优势,不管它们中有什么问题,都可能(但不能保证)被成功地解决了。

在这种情况下,新的编译器可能是新 bug 的潜在来源。


另一方面,新的编译器带有 附加工具:

  • GCC 和 Clang 现在都具有 消毒剂功能,它可以检测运行时来检测各种未定义的行为(来自 Google Compiler 团队的 Chandler Carruth 去年声称他预计这些行为已经完全覆盖)
  • Clang,至少,特性 硬化,例如 控制流完整性是关于检测控制流的高端攻击,也有硬化工具来防止堆栈粉碎攻击(通过将堆栈的控制流部分与数据部分分离) ; 硬化特性通常是低开销的(< 1% CPU 开销)
  • Clang/LLVM 也在开发 LibFuzzer,这是一个用于创建检测模糊单元测试的工具,可以巧妙地探索被测函数的输入空间(通过调整输入以采用尚未探索的执行路径)

使用消毒器(地址消毒器、内存消毒器或未定义行为消毒器)检测二进制文件,然后对其进行模糊处理(例如使用 美国绒毛罗布) ,已经发现了许多知名软件的漏洞,例如这个 LWN.net 文章

除非升级编译器,否则您无法访问这些新工具以及所有未来的工具。

停留在一个功能不足的编译器上,你就是把头埋在沙子里,祈祷没有漏洞被发现。如果你的产品是一个高价值的目标,我敦促你重新考虑。


注意: 即使您不升级生产编译器,您也可能想使用一个新的编译器来检查漏洞; 请注意,由于这些是不同的编译器,所以这种保证会降低。

较老的编译器可能没有针对已知黑客攻击的保护。栈粉碎保护,例如,没有引入 直到海湾合作委员会4.1。因此,使用较老的编译器编译的代码可能会以较新的编译器可以防止的方式出现漏洞。

另一个需要担心的方面是 发展新规范

较老的编译器对于某些语言特性的行为可能不同于程序员所标准化和期望的行为。这种不匹配可能会减慢开发速度,并引入可以利用的细微错误。

较老的编译器提供的特性较少(包括语言特性!)也不要优化。程序员会用他们的方法来解决这些缺陷,例如重新实现缺失的特性,或者编写晦涩但运行速度更快的聪明代码,为创建微妙的 bug 创造新的机会。

没有

原因很简单,旧的编译器可能有旧的错误和漏洞,但新的编译器将有新的错误和漏洞。

您没有通过升级到新的编译器来“修复”任何 bug。将旧的 bug 和漏洞转换为新的 bug 和漏洞。

你的问题分为两部分:

  • 显式: “使用旧的编译器是否存在更大的风险”(或多或少与您的标题一样)
  • 含蓄: “我怎样才能说服管理层升级”

也许您可以通过在现有代码库中找到一个可利用的缺陷并显示较新的编译器可能已经检测到它来回答这两个问题。当然,您的管理层可能会说“您在旧编译器中发现了这一点”,但是您可以指出,这需要付出相当大的努力。或者你通过新的编译器运行它来找到漏洞,然后利用它,如果你能够/允许用新的编译器编译代码的话。您可能需要一个友好的黑客的帮助,但这取决于是否信任他们,是否能够/允许他们显示代码(并使用新的编译器)。

但是如果你的系统没有暴露在黑客面前,你可能会对编译器升级是否会提高你的效率更感兴趣: MSVS 2013代码分析经常比 MSVS 2010更早发现潜在的 bug,而且它或多或少支持 C99/C11——不确定它是否正式支持,但是声明可以跟在声明之后,你可以在 for循环中声明变量。