C语言非常强大,并被广泛使用——特别是在Linux内核中——同时却非常危险。其中一位Linux工程师概述了开发人员如何应对编程语言的安全漏洞。

你可以用C语言做几乎任何事情,但这并不意味着你应该做。C语言代码运行很快,但它没有安全带。即使你是C语言专家,就像大多数Linux内核开发人员一样,你仍然可以犯下严重的错误。

除了误用指针别名之外,C语言本身还有一些基本的,未修复的错误,等待着那些粗心大意的人。谷歌Linux内核安全工程师Kees“Case”Cook在加拿大温哥华举行的Linux安全峰会研讨会上发表了这些弱点

“C语言是一个奇特的汇编程序。它几乎是机器代码,”库克说,他对数百名同行的观众说道,他们理解并赞赏由C语言开发的应用程序运行速度。然而,坏消息是“C语言伴随着一些令人担忧的包袱,未定义的行为以及导致的其他弱点 安全漏洞 和脆弱的基础设施。”

即使您将C语言用于开发其他项目,也值得关注其安全性方面的挑战。

保护Linux内核

随着时间的推移,库克和他一起工作的人发现了许多C语言自身的问题。为了解决这些弱点,内核自我保护项目在保护Linux内核免受攻击方面工作缓慢而稳定。在这个过程中,它已经努力地从Linux中删除麻烦的代码。

库克说,这很棘手,因为“内核希望为内存管理,中断处理,调度等做特定于架构的事情。” 许多代码都是挑剔的工作,必须仔细检查。例如,“没有用于设置页表或切换到64位模式的C API,”他说。

凭借其操作包和弱标准库,C包含大量未定义的行为。库克同意并引用了拉斐·莱维恩的博客文章“ 具有未定义操作,一切皆有可能。 ”

库克举了具体的例子。“'未初始化'变量的内容是什么?无论以前在内存中是什么!Void指针没有类型,但我们可以通过它们调用类型函数?当然!程序集并不关心:一切都可以成为一个地址来调用!为什么确实memcpy()没有'最大目的地长度'的论点?只要按我说的话做;内存区域都是一样的!“

什么时候应该忽略警告

其中一些特性相对容易处理。库克评论道,“Linus [Torvalds]喜欢总是初始化局部变量的想法。所以,你应该'做到这一点'。”

这个建议有一个警告。如果在switch中初始化局部变量,则会收到警告,Statement will never be executed [-Wswitch-unreachable]”。您可以忽略这个警告,因为编译器就是这样处理这种语句的。

但不要忽视每一个警告。“可变长度数组VLAs总是很糟糕,”库克说。常见的问题包括堆栈耗尽,线性溢出和跳过保护页面。此外,Cook发现,VLAs很慢删除VLAs可将性能提高13%。既快又更安全——这是一场胜利。

虽然VLAs几乎已经从内核中消除了,但库克说,它们仍然存在于某些代码中。幸运的是,使用-Wvla编译器标志很容易找到VLAs 

总而言之,请停止使用VLAs!

在C的语义中隐藏了一种不同的问题。也就是说,当有人从switch语句中省略“break”时,程序员是否意味着它?在switch中省略break可能导致代码从多个条件执行; 这是一个众所周知的问题。

如果您在现有代码中搜索break / switch语句,则可以使用-Wimplicit-fallthrough添加新的switch语句。现代编译器解析这样的编译器标志。您还可以使用“fallthrough”注释标记nonbreaks

库克还发现,对slab memory allocation边界检查是比较慢的。例如,strcpy()-family检查会有着2%的性能损失。其它方案最带有自己的问题。你看,strncpy()并不总是以NULL终止。库克哀怨地问道:“我们可以获得更好的API吗?”

在Q&A会议期间,一位Linux开发人员问道:“我们可以摆脱旧的,糟糕的API吗?”库克回答说,Linux支持弃用API的概念。但是,Torvalds抛弃了这个想法,如果需要任何API需要被弃用,它应该被完全抛弃。但是,摆脱API是一个“政治上的烫手山芋”。所以,就目前而言,我们坚持使用它们。

长期解决方案?更多拥有安全经验的开源开发人员

库克看到前方漫长的艰难道路。虽然有时候提出Linux C方言的想法很有吸引力,但这种情况不会发生。危险代码问题背后的真正问题是“人们不想做清理代码的工作——不只是坏代码,而是C本身,”他说。与所有开源项目一样,“我们需要更多专门的软件开发人员,审核人员,测试人员和管理人员。”

危险的C语言:领导者的经验教训

  • C语言成熟且功能强大,并带来技术和安全方面的挑战。
  • Linux开发人员特别注意使C语言更安全(但同样强大),因为操作系统的大部分内容都用它来开发。
  • Google Linux内核安全工程师确定了特定的语言缺陷,并解释了开发人员如何解决这些问题。