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

Cal*_*ius 137 c security gcc

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

而且我无法说服管理层将其升级到更近期:他们说,"如果没有破产,就不要修理它".

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

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

我的问题是:

可以使用旧的C编译器破坏编译程序的安全性吗?

更新:

相同的代码是由Visual Studio 2008 for Windows目标构建的,而MSVC还不支持C99或C11(我不知道更新的MSVC是否支持),我可以使用最新的GCC在我的Linux机器上构建它.因此,如果我们只是放入一个较新的GCC,它可能会像以前一样好.

plu*_*ash 103

实际上我会反驳.

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

gcc(和clang)的最新版本已经开始将这些案例视为优化机会,如果它们改变了二进制文件在"未定义行为"条件下的行为方式,则无法照顾它们.如果您的代码库是由处理C的人编写的,就像"便携式汇编程序"一样,这是非常糟糕的.随着时间的推移,优化器在进行这些优化时开始考虑越来越大的代码块,这增加了二进制文件最终会做"除了由哑编译器构建的二进制文件"之外的其他事情的机会.

有编译器开关来恢复"传统"行为(-fwrapv和-fno-strict-aliasing用于我上面提到的两个),但首先你要了解它们.

虽然原则上编译器错误可能会将符合规范的代码转变为安全漏洞,但我认为在宏观方案中这种风险可以忽略不计.

  • @Andre无论如何,*编译代码*具有可预测的未定义行为.也就是说,一旦编译了代码,任何不可预测的行为现在都可以预测,在那个特定的编译版本中. (18认同)
  • 但这种说法有两种作用.如果编译器具有可预测的"未定义行为",那么可以更容易地恶意使用它...... (13认同)
  • @Max这个答案准确地警告说,由于现代优化器,"便携式汇编器"概念在实践中至少已经过时了.而且我认为从一开始就没有概念上的正确. (10认同)
  • `那些把C视为"便携式汇编程序"的人不是C的东西吗? (6认同)
  • 对于那些依赖未定义行为然后开始收获他们所播种的人的人来说,没有同情.这并不意味着较新的编译器本质上不太安全 - 这意味着不合规的代码是定时炸弹.责任应该相应地分摊. (6认同)
  • @underscore_d责备是有趣的和游戏,但当有人不得不花费美元和工时来改变事物时,拥有像这样的代码的现实很快就会超过纯度.真正的问题不是责备,而是前进的最佳途径. (6认同)
  • @underscore_d 你为什么要责怪标准之前的代码不符合标准? (2认同)

Mat*_* M. 52

两种行动方案都存在风险.


较旧的编译器具有成熟的优点,并且其中的任何内容都可能(但不能保证)成功解决.

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


另一方面,较新的编译器带有额外的工具:

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

使用消毒剂(Address Sanitizer,Memory Sanitizer或Undefined Behavior Sanitizer)对二进制文件进行检测,然后对其进行模糊处理(例如使用American Fuzzy Lop)已经发现了许多高知名度软件中的漏洞,例如参见LWN.net这篇文章.

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

通过坚持一个功能不足的编译器,你可以把头放在沙子和交叉的手指上,没有发现任何漏洞.如果您的产品是高价值目标,我建议您重新考虑.


注意:即使您没有升级生产编译器,您也可能希望使用新的编译器来检查漏洞; 请注意,由于这些是不同的编译器,但保证会减少.


gna*_*729 46

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

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

如果升级到较新的编译器,则可能会丢失编译器错误引入的错误.但是这些错误都会成为你所知道的没有人发现并且没有人被利用的错误.但是新编译器本身可能存在错误,而且重要的是,较新的编译器更倾向于将未定义的行为转换为编译代码中的错误.

所以你的编译代码中会有很多新bug; 黑客可以找到和利用的所有错误.除非你做了大量的测试,并且将代码留给客户以便长时间发现bug,否则它将不那么安全.

  • 换句话说......没有简单的方法可以告诉编译器引入了什么问题,通过切换所有你做的是获得一组不同的未知问题? (6认同)
  • 嘿,实际上这个缺陷只是在2008年推出,所以提问者可能是安全的.但我的观点并不是关于那个特定的例子,而是存在一个旧的工具链会在你的代码中加入的已知错误.因此,当您更新它时,您确实引入了一组新的未知数,但这不是*您所做的全部*.你基本上只需要猜测你是否"更安全"留在最新工具链修复的已知关键缺陷中,或者在你自己的代码中对所有未定义的行为再次掷骰子的未知后果. (2认同)

t0m*_*13b 19

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

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

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

前提是,有一条明确的路径可以迁移到更新的C标准,这可能会产生副作用,最好是尝试旧代码库的分支,评估它并进行额外的类型检查,完整性检查,并确定是否升级到较新的编译器对输入/输出数据集有任何影响.

然后你可以向你的老板展示," 这是更新的代码库,重构,更符合行业认可的C99/C11标准...... ".

这是必须权衡的赌博,非常谨慎,对改变的抵制可能会在那种环境中表现出来并且可能拒绝接触新的东西.

编辑

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

此外,由于OP提到使用Visual Studio 2008来构建代码库,使用gcc可能会导致MinGW或Cygwin进入环境,这可能会对环境产生影响,除非目标是针对Linux,那么它将是值得一试,可能要包括额外的开关到编译器,以尽量减少旧K&R代码库的噪音,另一个重要的是进行了大量的测试,以确保没有功能被打破,可能结果是一个痛苦的运动.

  • "可能是在16位平台上运行,很有可能,升级到更现代的编译器实际上可能打破代码库,我在思考架构,32位代码"我不认为问题是关于**移植**代码到新的实现定义参数. (2认同)

Lun*_*din 9

可以使用旧的C编译器破坏编译程序的安全性吗?

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

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

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

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

  • 我不认为这是偏执狂; 我认为OP试图发明理由说服他的老板.OP实际上可能需要一个新的编译器,因为它们可以提供更好的asm(包括使用LTO进行跨文件优化),具有更多有用的诊断/警告,并允许使用现代语言功能和语法.(例如C11 stdatomic). (6认同)

Pas*_*uoq 9

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

这种攻击在程序须藤说明这篇文章.bcrypt为Javascript minifiers写了一篇很棒的后续文章.

除了这个问题,C编译器的进化一直利用未定义行为更多更多积极,这是写在真诚地如此旧的C代码实际上更安全从时间一个C编译器来编译,或者编译-O0(但是在新版本的编译器中甚至在-O0中引入了一些新的破坏程序的UB漏洞利用优化).


DrM*_*eod 7

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


小智 6

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

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

较旧的编译器提供较少的功能(包括语言功能!),也不进行优化.程序员将破解这些缺陷 - 例如重新实现缺失的功能,或者编写模糊但运行速度更快的聪明代码 - 为创建微妙的错误创造新的机会.


cot*_*eyr 5

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

您不是通过升级到新编译器来"修复"任何错误.您切换旧错误和漏洞利用新的漏洞和漏洞利用.

  • 这些看起来非常简单:新的编译器可能有它的弱点,但我希望它们比旧的编译器要少,并且它可能会检测到自那时以来已知的几个代码漏洞. (3认同)