使用过时的C编译器会带来安全风险吗?


139

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

而且我不能说服管理层将其升级到最新版本:他们说,“如果还没有破产,就不要修复它”。

由于我们维护了非常古老的代码库(写于80年代),因此C89代码在这些编译器上可以很好地进行编译。

但是我不确定使用这些旧内容是个好主意。

我的问题是:

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

更新:

Visual Studio 2008为Windows目标构建了相同的代码,MSVC尚不支持C99或C11(我不知道是否有更新的MSVC支持),并且可以使用最新的GCC在Linux机器上构建它。因此,如果我们只使用更新的GCC,它的构建可能会和以前一样好。


5
有趣的问题-这可能也值得快速阅读-developers.slashdot.org/story/13/10/29/2150211/… ..因此,较新的编译器在优化时也可能损害安全性。
尼尔

6
那些旧的gcc版本是否支持针对ASLR编译为PIC / PIE?他们支持堆叠金丝雀吗?W ^ X(NX)?如果不是这样,则缺少缓解漏洞的方法是升级的很好理由。
EOF

12
仅查看来自gcc 4.x的警告,可能会立即发现您不知道的全部现有安全漏洞。
OrangeDog

7
@OrangeDog:为什么要使用gcc 4.x?gcc6是当前发行版,而gcc 5已经存在了一段时间。但是,是的,解决-O3 -Wall -Wextra -fsanitize=undefined现代gcc和clang 所发现的任何问题应该会有所帮助。
彼得·科德斯

4
@OrangeDog GCC已进入市场版本号。由于GCC 5更改了默认的C和C ++标准以及libstdc ++ ABI,因此应受到主要版本的重视。GCC 6应该被称为5.1。
zwol

Answers:


102

其实我会相反。

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

最新版本的gcc(和clang)已开始将这些情况视为优化机会,如果它们改变了二进制文件在“未定义行为”条件下的行为,就不会在意。如果您的代码库是由将C视为“便携式汇编程序”的人员编写的,那么这非常糟糕。随着时间的流逝,优化器在进行这些优化时已开始查看越来越大的代码块,从而增加了二进制程序最终会执行“笨拙的编译器构建的二进制程序所要做的事情”以外的工作的可能性。

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

虽然从原理上讲,编译器错误可能会将兼容的代码变成一个安全漏洞,但我认为在总体方案中,这样做的风险可以忽略不计。


13
但是,这种说法是双向的。如果编译器具有可预见的“未定义行为”,那么就可以说是更容易使恶意使用它...
安德烈

18
@Andre 无论如何,编译后的代码具有可预测的未定义行为。也就是说,一旦代码被编译,在该特定的编译版本中,任何不可预测的行为现在都是可预测的。
user253751 '16

6
people who treated C like a "portable assembler"是不是C呢?
Max

10
@Max该答案正好警告以下事实:由于现代优化程序,“便携式汇编程序”概念至少在实践中已过时。我认为,从概念上讲,它从来都不是正确的。
Theodoros Chatzigiannakis,2016年

6
对于那些依赖不确定行为然后后来开始收获的人,这里没有同情。这并不意味着较新的编译器本质上就不太安全-这意味着不兼容的代码是定时炸弹。责任应相应地分配。
underscore_d

52

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


较旧的编译器具有成熟性的优势,并且可能会(但不能保证)成功解决所有损坏的问题。

在这种情况下,新的编译器可能会导致新的错误。


另一方面,较新的编译器附带了其他工具

  • GCC和Clang现在都具有清除程序,可以对运行时进行检测,以检测各种未定义的行为(Google编译器小组的Chandler Carruth去年声称,他希望它们已经完全覆盖)。
  • 至少Clang具有强化功能,例如Control Flow Integrity旨在检测控制流的劫机现象,此外还有强化工具可防止堆栈崩溃攻击(通过将堆栈的Control Flow部分与Data部分分开) ; 强化功能通常具有较低的开销(<1%CPU开销)
  • Clang / LLVM还在libFuzzer上工作,libFuzzer是一种工具,用于创建检测的模糊单元测试,以智能地探索被测函数的输入空间(通过调整输入以采用尚未探索的执行路径)。

使用清除器(地址清除器,内存清除器或未定义的行为清除器)对二进制文件进行检测,然后对其进行模糊处理(例如,使用American Fuzzy Lop),已经发现了许多知名软件中的漏洞,请参阅此LWN.net文章

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

通过使用性能低下的编译器,您会陷入困境,并且手指间没有发现漏洞。如果您的产品是高价值目标,我敦促您重新考虑。


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


1
+1麻烦地提到了新编译器可以更安全的情况,而不是用其他答案的“ b-但我的老UB很好”。这是他们提供的许多其他改进的基础,这些改进与安全性没有直接关系,但可以为合理的现代化提供更多动力。
underscore_d

尽管感觉像是在倡导“默默无闻的安全性”。影响旧编译器的错误已广为人知。我同意新的编译器会引入错误,但是这些错误尚未像以前的版本那样公开,如果您经常更新应用程序,则这是安全的。
The6P4C

钱德勒·卡鲁斯(Chandler Carruth)真可爱,谈到了如此美妙的事情。如果可以的话,我愿意和他结婚。
丹尼尔·卡米尔·科扎尔

46

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

由于测试和由于客户错误报告而导致的错误修复,编译后代码中的错误率可能较低。因此,最初可能存在大量错误,但现在已经减少了。

如果升级到较新的编译器,则可能会丢失由编译器错误引入的错误。但是这些错误全都是您所知没有发现也没有被利用的错误。但是,新的编译器可能会单独存在错误,重要的是,较新的编译器更倾向于将未定义的行为转换为已编译代码中的错误。

因此,您的已编译代码中将出现很多新错误;黑客可以找到并利用的所有错误。而且,除非您进行大量测试,并且让代码长时间留给客户查找错误,否则它的安全性将降低。


6
因此,换句话说...没有一种简单的方法可以告诉编译器引入了哪些问题,并且通过切换全部,您会得到一组不同的未知问题?
加里·杰里米(Jeremy Kato)

1
@JeremyKato:嗯,在某些情况下,您还会遇到另一套已知问题。我不确定编译器本身存在哪些已知的安全漏洞,但是为了举一个具体的示例,假设更新到新的编译器意味着还能够采用最新的libc(而使用旧的libc则意味着无法为此),那么您将知道要解决此缺陷,位于getaddrinfo()access.redhat.com/articles/2161461。该示例实际上不是编译器安全漏洞,但是十多年以来,肯定会存在一些已知的固定漏洞。
史蒂夫·杰索普

2
呵呵,实际上,该漏洞只是在2008年才引入的,因此发问者可能会对此感到放心。但是我的意思不是关于那个特定的示例,而是确实存在一些旧工具链将在您的代码中放入的已知错误。因此,当您进行更新时,确实会引入一组新的未知数,但这并不是您要做全部。基本上,您只需要猜测自己是否“更安全”,而不是留下最新工具链已修复的已知严重缺陷,或者对自己代码中所有未定义的行为再次掷骰子就将带来未知的后果。
史蒂夫·杰索普

19

如果它坏了,请不要修复

您的老板听起来很正确,但是,更重要的因素是保护输入,输出和缓冲区溢出。从那个角度来看,无论使用哪种编译器,缺少这些始终都是链中最薄弱的环节。

但是,如果代码库是古老的,并且已进行工作以缓解所使用的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支持),并且可以使用最新的GCC在Linux机器上构建它。因此,如果我们只使用更新的GCC,它的构建可能会和以前一样好。
Calmarius

@Calmarius感谢您的注意,也许最好编辑您的问题以包括评论,那很重要:)而且应该去过那里; D
t0mm13b

@ Calmarius编辑了我的答案,这是我对新更新的问题的思考。
t0mm13b

2
“可能会在16位平台上运行,可能是,升级到更现代的编译器实际上可能会破坏代码库,在架构,32位代码方面进行思考”,我认为问题不在于代码移植到新的实现定义的问题上参数。
Pascal Cuoq

同意 这可能是一个运行时,此漏洞可能被编译器故障而被创建。但是由于缓冲区和堆栈溢出等原因,代码中更有可能包含运行时漏洞。因此,当您花费时间使代码库更安全时,您应该将其投入到诸如检查输入字符串的长度以确保它们不超出程序限制之类的事情上。获取更新的编译器无济于事。用具有本地字符串和数组对象的语言从头开始重写代码会很有帮助。但是您的老板不会为此付费。
琼斯

9

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

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

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

如果没有发现会影响程序的编译器错误的证据,则仅出于此目的而更新GCC似乎有点偏执。您必须记住,较新的版本可能包含尚未发现的新错误。最近在GCC 5和C11支持下进行了很多更改。

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


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

9

存在安全风险,恶意开发人员可以通过编译器错误来潜入后门。根据使用中的编译器中已知错误的数量,后门看起来或多或少不起眼(无论如何,关键是即使在源代码级别,即使卷积了代码,代码也是正确的。)非Buggy编译器将找不到后门,因为在这些情况下后门不存在)。为了获得额外的否认性,恶意开发人员还可以自行寻找以前未知的编译器错误。同样,伪装的质量将取决于发现的编译器错误的选择。

这种攻击在程序须藤说明这篇文章。bcrypt为Javascript缩小程序编写了出色的后续文章

除了这个问题,C编译器的进化一直利用未定义行为更多更多积极的,所以这是写在诚信旧的C代码实际上更安全从时间一个C编译器来编译,或者编译-O0(但是,即使在-O0处,新版本的编译器中也引入了一些新的程序,它破坏了UB开发的优化)。


7

较早的编译器可能没有针对已知黑客攻击的保护措施。例如,直到GCC 4.1才引入堆栈防砸保护。是的,以较新的编译器防御的方式,使用较旧的编译器编译的代码可能很容易受到攻击。


6

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

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

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


5

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

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


3
这些看起来非常简单:新的编译器可能有其弱点,但我希望它们的弱点少于旧的编译器,并且它很可能会检测到此后已知的几个代码漏洞。
PJTraill '16

但是新的编译器可能有未知的新弱点。编译器本身并不是需要更新的安全风险。您并没有减少表面积。您将已知问题集换为未知问题集。
coteyr

自早期GCC以来,用于帮助发现错误的工具已得到了极大的改进,并且这些工具(静态分析,插装代码动态分析/消毒器,模糊器等)也已应用于编译器代码,以帮助提高质量。在GCC 2时代要找到所有类别的错误要困难得多。各个版本上的编译器错误比较-请参阅第7页:cs.utah.edu/~regehr/papers/pldi11-preprint.pdf GCC 4.5和LLVM 2.8(发布时最新)错误最少。
Jetski S型

2

好了,与使用新的编译器相比,旧编译器中的任何错误都广为人知并被记录在案,因此可以采取措施通过围绕它们进行编码来避免这些错误。因此,以某种方式不足以作为升级的理由。我们在工作时也进行了相同的讨论,我们在嵌入式软件的代码库上使用GCC 4.6.1,并且由于担心出现新的,未记录的错误,因此(在管理方面)极不情愿地升级到最新的编译器。


0

您的问题分为两部分:

  • 明确:“使用较旧的编译器是否有更大的风险”(或多或少与标题中的一样)
  • 暗示:“我如何说服管理层进行升级”

也许您可以通过在现有代码库中发现可利用的漏洞并表明较新的编译器已检测到它,来回答这两个问题。当然,您的管理人员可能会说“您发现使用旧的编译器”,但是您可以指出这需要花费大量的精力。或者,如果您能够/允许使用新的编译器编译代码,则可以通过新的编译器运行它以发现漏洞,然后加以利用。您可能需要友好的黑客的帮助,但这取决于信任他们并能够/允许向他们显示代码(并使用新的编译器)。

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.