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

Posted

技术标签:

【中文标题】使用过时的 C 编译器是不是存在安全风险?【英文标题】:Is using an outdated C compiler a security risk?使用过时的 C 编译器是否存在安全风险? 【发布时间】:2016-09-25 13:16:03 【问题描述】:

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

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

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

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

我的问题是:

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

更新:

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

【问题讨论】:

有趣的问题 - 这可能也值得快速阅读 - developers.slashdot.org/story/13/10/29/2150211/… .. 所以较新的编译器在优化时也可能会损害安全性。 那些旧的 gcc 版本是否支持为 ASLR 编译为 PIC/PIE?他们支持堆栈金丝雀吗? W^X (NX)?如果不是,那么缺乏针对漏洞的缓解措施是升级的一个很好的理由。 仅查看 gcc 4.x 的警告可能会立即揭示大量您不知道的现有安全漏洞。 @OrangeDog:为什么选择 gcc 4.x? gcc6 是当前的发布系列,而 gcc 5 已经存在了一段时间。但是,是的,使用现代 gcc 和 clang 解决 -O3 -Wall -Wextra -fsanitize=undefined 发现的任何问题应该会有所帮助。 @OrangeDog GCC 已转到营销版本号。 GCC 5 值得大版本升级,因为它们改变了默认的 C 和 C++ 标准以及 libstdc++ ABI。 GCC 6 应该被称为 5.1。 【参考方案1】:

其实我会反对。

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

最新版本的 gcc(和 clang)已开始将这些情况视为优化机会,而不关心它们是否会改变二进制文件在“未定义行为”条件下的行为方式。如果您的代码库是由将 C 视为“便携式汇编程序”的人编写的,那么这非常糟糕。随着时间的推移,优化人员在进行这些优化时开始查看越来越大的代码块,这增加了二进制文件最终会执行“由愚蠢的编译器构建的二进制文件”会做的事情之外的事情的机会。

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

虽然原则上编译器错误可能会将合规代码变成安全漏洞,但我认为这种风险在总体方案中可以忽略不计。

【讨论】:

但是这个论点是双向的。如果编译器具有可预测的“未定义行为”,那么可以说更容易恶意使用它...... @Andre 编译的代码 无论如何都有可预测的未定义行为。也就是说,一旦代码被编译,任何不可预测的行为现在都是可以预测的,在那个特定的编译版本中。 @Max 这个答案准确地警告了这样一个事实,即由于现代优化器,“便携式汇编器”概念至少在实践中已经过时。我会争辩说,从一开始它在概念上从来就不是正确的。 对于那些依赖未定义行为并随后开始收获他们播种的东西的人,这里没有任何同情。这并不意味着较新的编译器本质上不那么安全——这意味着不兼容的代码是一个定时炸弹。责任应该相应地分配。 @underscore_d 责备是所有的乐趣和游戏,但是当有人不得不投入金钱和工时来改变事情时,拥有这样的代码体的现实很快就会超过纯度。真正的问题不是责备,而是前进的最佳途径。【参考方案2】:

这两种行动都存在风险。


较旧的编译器具有成熟的优势,其中的任何问题都可能(但不能保证)已成功解决。

在这种情况下,新的编译器是新错误的潜在来源。


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

GCC 和 Clang 现在都具有 sanitizers 功能,可以检测运行时以检测各种未定义的行为(Google 编译器团队的 Chandler Carruth 去年声称,他希望它们已经达到覆盖范围) Clang 至少具有强化的功能,例如Control Flow Integrity 是关于检测控制流的劫持,还有一些强化工具可以防止堆栈粉碎攻击(通过分离控制-从数据部分流出堆栈的一部分);强化功能通常开销较低( Clang/LLVM 也在开发 libFuzzer,这是一个创建仪器化模糊单元测试的工具,可以巧妙地探索被测函数的输入空间(通过调整输入以采用尚未探索的执行路径)

使用清理程序(地址清理程序、内存清理程序或未定义行为清理程序)检测您的二进制文件,然后对其进行模糊测试(例如使用American Fuzzy Lop)已发现许多知名软件中的漏洞,例如参见@987654324 @。

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

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


注意:即使您不升级生产编译器,您也可能希望使用新的编译器来检查漏洞;请注意,由于它们是不同的编译器,因此保证会有所减少。

【讨论】:

+1 麻烦提到新编译器可以更安全的情况,而不是在其他答案的“b-but my good old UB”潮流中堆积。这是他们提供的许多其他改进的基础,这些改进与安全性没有直接关系,但为合理的现代性提供了更多动力。 虽然这感觉像是在倡导“通过默默无闻的安全”;影响旧编译器的错误是已知的和公开的。虽然我同意新编译器会引入错误,但这些错误尚未像以前版本那样公开,如果您经常更新应用程序,这是一些安全性。 Chandler Carruth 太可爱了,会讲出如此美妙的事情。如果可以,我会嫁给他。【参考方案3】:

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

由于测试和修复客户错误报告导致的错误,编译代码中的错误率可能很低。因此,最初可能存在大量错误,但已经下降。

如果您升级到较新的编译器,您可能会丢失由编译器错误引入的错误。但据您所知,这些漏洞都是没有人发现和利用的漏洞。但是新编译器本身可能有错误,重要的是,较新的编译器更倾向于将未定义的行为变成编译代码中的错误。

所以你的编译代码中会有很多新的错误;黑客可以发现和利用的所有漏洞。除非您进行大量测试,并将您的代码留给客户很长时间以查找错误,否则它的安全性会降低。

【讨论】:

所以换句话说......没有简单的方法来判断编译器引入了哪些问题,而通过切换你所做的只是得到一组不同的未知问题? @JeremyKato:嗯,在某些情况下,您遇到了一组不同的已知问题。我不确定编译器本身存在哪些已知的安全漏洞,但为了一个具体的例子,假设更新到新的编译器也意味着能够采用最新的 libc(而使用旧的意味着不能要做到这一点),那么你就会知道你正在修复getaddrinfo(): access.redhat.com/articles/2161461 中的这个缺陷。该示例实际上并不是编译器安全漏洞,但 10 多年来肯定会有一些已知的已修复漏洞。 嘿,实际上这个漏洞是在 2008 年才引入的,所以提问者可能不会受到它的影响。但我的意思不是关于那个特定的例子,而是确实存在旧工具链会放入您的代码中的已知错误。因此,当您更新时,您确实会引入一组新的未知数,但这并不是您所做的全部。您基本上只需要猜测您是否“更安全”留在最新工具链修复的已知严重缺陷中,或者对您自己代码中的所有未定义行为再次掷骰子的未知后果。【参考方案4】:

如果它没有坏,不要修理它

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

但是,如果代码库很古老,并且已经采取措施减轻所使用的 K&R C 的弱点,例如缺乏类型安全、不安全的 fget 等,那么请权衡一下“是否会升级更现代的 C99/C11 标准的编译器破坏了一切?"

如果有一条明确的路径可以迁移到新的 C 标准,这可能会产生副作用,最好尝试对旧代码库进行分叉,对其进行评估并进行额外的类型检查、健全性检查并确定如果升级到较新的编译器对输入/输出数据集有任何影响。

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

这是一场必须权衡的赌博,非常小心拒绝改变可能会在那种环境中表现出来,并且可能拒绝接触更新的东西。

编辑

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

此外,由于 OP 提到使用 Visual Studio 2008 构建代码库,使用 gcc 可能会导致将 MinGW 或 Cygwin 引入环境,这可能会对环境产生影响,除非目标是 Linux,然后值得一试,可能必须在编译器中添加额外的开关,以最大限度地减少旧 K&R 代码库上的噪音,另一件重要的事情是进行大量测试以确保没有功能被破坏,结果可能是痛苦的运动。

【讨论】:

Visual Studio 2008 为 Windows 目标构建了相同的代码,MSVC 还不支持 C99 或 C11(我不知道更新的 MSVC 是否支持),我可以在我的Linux box 使用最新的 GCC。因此,如果我们只是加入一个更新的 GCC,它可能会像以前一样构建。 @Calmarius 感谢您的提醒,也许最好编辑您的问题以包含评论,这很重要 :) 应该在那里 ;D @Calmarius 编辑了我的答案,这是我对新更新问题的想法。 “可以在 16 位平台上运行,很可能,升级到更现代的编译器实际上可能会破坏代码库,我正在考虑架构,32 位代码”我不认为问题是关于将代码移植到新的实现定义的参数。 同意。 可能运行时漏洞可能由编译器错误造成。但由于缓冲区和堆栈溢出等原因,代码包含运行时漏洞的可能性更有可能。因此,当您花时间使此代码库更安全时,您应该花时间检查输入字符串的长度,以确保它们不会超出程序的限制。获得更新的编译器不会有太大帮助。用原生字符串和数组对象的语言从头开始重写代码会有很大帮助。但你的老板不会为此买单。【参考方案5】:

存在安全风险,恶意开发人员可以通过编译器错误潜入后门。根据正在使用的编译器中已知错误的数量,后门可能看起来或多或少不显眼(无论如何,关键是代码在源代码级别是正确的,即使是复杂的。源代码审查和测试使用没有错误的编译器不会找到后门,因为在这些情况下不存在后门)。对于额外的否认点,恶意开发人员还可能自己寻找以前未知的编译器错误。同样,伪装的质量将取决于所发现的编译器错误的选择。

this article 中的程序 sudo 说明了这种攻击。 bcrypt 为javascript minifiers 写了一篇精彩的后续文章。

除了这个问题之外,C 编译器的发展一直是积极地利用未定义的行为 more 和 more 和 more,因此真诚编写的旧 C 代码实际上会更安全地编译当时的 C 编译器,或在 -O0 编译(但一些新的程序破坏 UB-exploiting 优化are introduced in new versions of compilers even at -O0)。

【讨论】:

【参考方案6】:

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

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

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

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

话虽如此,80 年代编写的代码很可能已经充满了安全漏洞和对定义不明确的行为的依赖,无论编译器如何。我们在这里谈论的是准标准 C。

【讨论】:

我不认为这是妄想症;我认为 OP 正试图编造理由来说服他的老板。可能 OP 实际上想要一个新的编译器,因为它们可以制作更好的 asm(包括使用 LTO 进行跨文件优化),具有更有用的诊断/警告,并允许现代语言功能和语法。 (例如 C11 标准原子)。【参考方案7】:

较旧的编译器可能无法抵御已知的黑客攻击。例如,堆栈粉碎保护没有引入until GCC 4.1。所以,是的,用旧编译器编译的代码可能会受到新编译器的保护。

【讨论】:

【参考方案8】:

另一个需要担心的方面是新代码的开发

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

较旧的编译器提供的功能较少(包括语言功能!)并且也没有进行优化。程序员将破解这些缺陷——例如。通过重新实现缺失的功能,或编写晦涩但运行速度更快的巧妙代码——为创建细微错误创造新机会。

【讨论】:

【参考方案9】:

没有

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

您不会通过升级到新的编译器来“修复”任何错误。您将旧错误和漏洞利用转换为新漏洞和漏洞利用。

【讨论】:

这些看起来很简单:新编译器可能有它的弱点,但我希望它们比旧编译器要少,而且它很可能会检测到从那时起已知的几个代码漏洞. 但是新的编译器可能有未知的新弱点。单独的编译器不是需要更新的安全风险。你没有减少你的表面积。您将一组已知问题换成一组未知问题。 自早期的 GCC 时代以来,帮助查找错误的工具得到了极大的改进,这些工具(静态分析、检测代码动态分析/清理器、模糊器等)也已应用于编译器代码,以帮助提高质量。在 GCC 2 时代,要找到所有类别的错误要困难得多。不同版本的编译器错误比较 - 请参见第 7 页:cs.utah.edu/~regehr/papers/pldi11-preprint.pdf GCC 4.5 和 LLVM 2.8(最新发布)具有最少的模糊测试错误。【参考方案10】:

与使用新编译器相比,旧编译器中的任何错误更有可能为人所熟知并记录在案,因此可以采取措施通过围绕这些错误进行编码来避免这些错误。因此,以某种方式不足以作为升级的论据。我们在我工作的地方也有同样的讨论,我们在嵌入式软件的代码库上使用 GCC 4.6.1,并且由于担心新的、未记录的错误,非常不愿意(在管理层中)升级到最新的编译器。

【讨论】:

【参考方案11】:

您的问题分为两部分:

明确:“使用旧的编译器是否有更大的风险”(或多或少如您的标题中所述) 隐含:“我如何说服管理层升级”

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

但是,如果您的系统没有暴露在黑客面前,您或许应该对编译器升级是否会提高您的效率更感兴趣:MSVS 2013 代码分析通常比 MSVS 2010 更快地发现潜在错误,并且或多或少支持C99/C11 - 不确定它是否正式,但声明可以跟在语句之后,你可以在for-loops 中声明变量。

【讨论】:

以上是关于使用过时的 C 编译器是不是存在安全风险?的主要内容,如果未能解决你的问题,请参考以下文章

Android 应用安全风险与防范

转android出现注: 某些输入文件使用或覆盖了已过时的 API。 注: 有关详细信息, 请使用 -Xlint:deprecation 重新编译。 注: 某些输入文件使用了未经检查或不安全的操作(代

任何 C++11 线程安全保证是不是适用于使用 C++11 编译/链接的第三方线程库?

我的 Visual C++ 编译器编译过时的源代码

GCC 安全编译选项

GCC 安全编译选项