我们有一些生产中没有人关心的构建系统,这些机器运行古老版本的GCC,如GCC 3或GCC 2。
我无法说服管理层将其升级到更近期:他们说,“如果没有破产,就不要修理它”。
由于我们维护了一个非常古老的代码库(用80年代编写),这个C89代码在这些编译器上编译得很好。
但我不确定使用这些旧东西是个好主意。
我的问题是:
使用旧的C编译器会破坏已编译程序的安全性吗?
更新:
相同的代码是由Visual Studio 2008 for Windows目标构建的,MSVC还不支持C99或C11(我不知道更新的MSVC是否支持),我可以使用最新版本在我的Linux机器上构建它GCC。因此,如果我们只是放入一个较新的GCC,它可能会像以前一样好。
答案 0 :(得分:99)
实际上,我认为相反。
在许多情况下,C标准未定义行为,但很明显,在给定平台上使用“哑编译器”会发生什么。例如允许有符号整数溢出或通过两种不同类型的变量访问相同的内存。
gcc(和clang)的最新版本已经开始将这些案例视为优化机会,如果它们改变二进制在“未定义行为”条件下的行为方式,则无法照顾。如果您的代码库是由处理C的人编写的,就像“便携式汇编程序”一样,这是非常糟糕的。随着时间的推移,优化器在进行这些优化时已开始查看越来越大的代码块,这增加了二进制文件最终会执行“由哑编译器构建的二进制文件”之外的其他操作的可能性。
有一些编译器开关可以恢复“传统”行为(-fwrapv和-fno-strict-aliasing用于我上面提到的两个),但首先你需要了解它们。
虽然原则上编译器错误可能会将符合规范的代码转变为安全漏洞,但我认为在宏观方案中这种风险可以忽略不计。
答案 1 :(得分:51)
两种行动方案都存在风险。
较旧的编译器具有成熟的优点,并且其中的任何内容都可能(但无法保证)已成功解决。
在这种情况下,新的编译器是新bug的潜在来源。
另一方面,较新的编译器附带附加工具:
使用消毒剂(Address Sanitizer,Memory Sanitizer或Undefined Behavior Sanitizer)对二进制文件进行检测,然后对其进行模糊测试(例如,使用American Fuzzy Lop)已发现许多高知名度软件中的漏洞,例如,请参阅此LWN.net article
除非您升级编译器,否则您无法访问这些新工具和所有未来工具。
通过坚持使用功能不足的编译器,您可以将头放在沙子中,并且不会发现任何漏洞。如果您的产品是高价值目标,我建议您重新考虑。
注意:即使您不升级生产编译器,您也可能希望使用新的编译器来检查漏洞;请注意,由于这些是不同的编译器,但保证会减少。
答案 2 :(得分:46)
您编译的代码包含可能被利用的错误。这些错误来自三个来源:源代码中的错误,编译器和库中的错误,以及编译器变成错误的源代码中的未定义行为。 (未定义的行为是一个错误,但不是已编译代码中的错误。例如,i = i ++;在C或C ++中是一个错误,但在您编译的代码中,它可能会增加1,然后确定或设置我是一些垃圾,是一个错误)。
由于测试和修复客户错误报告导致的错误,编译代码中的错误率可能很低。因此最初可能存在大量错误,但这已经下降了。
如果升级到较新的编译器,则可能会丢失编译器错误引入的错误。但是这些错误都会成为你所知道的没有人发现并且没有人被利用的错误。但是新编译器本身可能存在错误,而且重要的是,较新的编译器更倾向于将未定义的行为转换为编译代码中的错误。
因此,您的编译代码中会出现大量新错误;黑客可以找到和利用的所有错误。除非你做了大量的测试,并且将代码留给客户以便长时间发现bug,否则它将不那么安全。
答案 3 :(得分:19)
如果它没有破坏,请不要修理它
你的老板说得对,但是,重要因素越多,就是保护输入,输出和缓冲区溢出。无论使用何种编译器,缺乏这些都是链中最薄弱的环节。
然而,如果代码库是古老的,并且已经做好工作以减轻所使用的K&amp; RC的弱点,例如缺乏类型安全性,不安全的fgets等,那么就要权衡问题&#34; < em>将编译器升级到更现代的C99 / C11标准会破坏一切吗?&#34;
前提是,有一条明确的路径可以迁移到更新的C标准,这可能会导致副作用,最好尝试旧代码库的分支,评估它并进行额外的类型检查,完整性检查,并确定升级到较新的编译器是否对输入/输出数据集有任何影响。
然后你可以向你的老板展示,&#34; 这里有更新的代码库,经过重构,更符合行业认可的C99 / C11标准...... &# 34。
这是必须权衡的赌博,非常谨慎,阻止改变可能会在该环境中显示,并可能拒绝接触更新的东西。
修改强>
只是坐了几分钟,意识到这一点,K&amp; R生成的代码可以在16位平台上运行,很有可能,升级到更现代的编译器实际上可能会破坏代码库,我在思考架构方面,将生成32位代码,这可能会对用于输入/输出数据集的结构产生有趣的副作用,这是另一个巨大因素,需要仔细权衡。
另外,由于OP提到使用Visual Studio 2008构建代码库,使用gcc可能会导致MinGW或Cygwin进入环境,这可能会对环境产生影响,除非目标是针对Linux,然后值得一试,可能需要在编译器中加入额外的开关,以尽量减少旧K&amp; R代码库的噪音,另外重要的是要进行大量的测试以确保没有功能被破坏,可能会变成是一个痛苦的运动。
答案 4 :(得分:9)
使用旧的C编译器会破坏已编译程序的安全性吗?
当然,如果旧编译器包含您知道会影响程序的已知错误,它可以。
问题是,是吗?要确切知道,您必须阅读从版本到当前日期的整个更改日志,并检查多年来修复的每个错误。
如果你没有发现会影响你的程序的编译器错误的证据,那么仅仅为了它而更新GCC似乎有点偏执。您必须记住,较新的版本可能包含尚未发现的新错误。最近在GCC 5和C11支持下做了很多改变。
话虽如此,无论编译器如何,80年代编写的代码很可能已经填满了安全漏洞,并且依赖于定义不明确的行为。我们在这里讨论的是标准前的C语言。
答案 5 :(得分:9)
存在安全风险,恶意开发人员可能会通过编译器错误进入后门。根据正在使用的编译器中已知错误的数量,后门可能看起来或多或少不显眼(无论如何,关键是代码是正确的,即使在源级别进行了复杂处理。源代码审查和测试使用一个非错误的编译器将找不到后门,因为后门在这些条件下不存在)。对于额外的拒绝点,恶意开发人员也可能自己查找以前未知的编译器错误。同样,伪装的质量将取决于所发现的编译器错误的选择。
此攻击在this article中的程序sudo上有说明。 bcrypt为Javascript minifiers写了一篇很棒的后续文章。
除了这种担忧之外,C编译器的发展一直在积极地利用未定义的行为more和more以及more,因此真正编写的旧C代码实际上是从一开始就用C编译器编译得更安全,或者在-O0编译(但是一些新的程序破解UB利用优化are introduced in new versions of compilers even at -O0)。
答案 6 :(得分:7)
较旧的编译器可能无法抵御已知的黑客攻击。例如,没有引入堆栈粉碎保护until GCC 4.1。所以,使用较旧的编译器编译的代码可能会受到新编译器防范的攻击。</ p>
答案 7 :(得分:6)
另一个需要担心的方面是开发新代码。
对于某些语言功能,较旧的编译器可能具有与程序员标准化和期望的不同的行为。这种不匹配可能会减慢开发速度并引入可被利用的细微错误。
较旧的编译器提供的功能较少(包括语言功能!),也没有进行优化。程序员会破解这些缺陷 - 例如通过重新实现缺少的功能,或编写模糊但运行速度更快的聪明代码 - 为创建微妙的错误创造新的机会。
答案 8 :(得分:5)
原因很简单,旧的编译器可能有旧漏洞和漏洞,但新编译器会有新的漏洞和漏洞利用。
你的不是&#34;修复&#34;升级到新编译器的任何错误。您切换旧错误和漏洞利用新的漏洞和漏洞利用。
答案 9 :(得分:2)
与使用新编译器相比,旧编译器中的任何错误都是众所周知和记录的概率更高,因此可以采取措施通过编码来避免这些错误。所以在某种程度上还不足以作为升级的论据。我们在工作中有相同的讨论,我们在嵌入式软件的代码库上使用GCC 4.6.1,并且由于担心新的,未记录的错误,因此很难(在管理层之间)升级到最新的编译器。
答案 10 :(得分:0)
您的问题分为两部分:
也许您可以通过在现有代码库中找到可利用的漏洞并显示较新的编译器检测到它来回答这两个问题。当然,您的管理层可能会说“您使用旧编译器发现了这一点”,但您可以指出它需要付出相当大的努力。或者,如果您能够/允许使用新编译器编译代码,则通过新编译器运行它以查找漏洞,然后利用它。您可能需要友好的黑客的帮助,但这取决于信任他们并且能够/允许向他们展示代码(并使用新的编译器)。
但是如果你的系统没有暴露给黑客,你或许应该更感兴趣的是编译器升级是否会提高你的效率:MSVS 2013 Code Analysis经常比MSVS 2010更早地发现潜在的错误,它或多或少支持C99 / C11 - 不确定它是否正式,但声明可以跟随语句,你可以在for
- 循环中声明变量。