
FOSDEM 2026会议上,安全软件的创造者们在保护用户的努力中遭遇了一个意想不到的对手:现代编译器。
现代编译器将代码优化为最高效的形式,但在这个过程中,它们可能会破坏安全防护措施。
"现代软件编译器正在破坏我们的代码,"René Meusel在2月1日的FOSDEM演讲中分享了他的担忧。
Meusel管理着Botan加密库,同时也是Rohde & Schwarz网络安全公司的高级软件工程师。
作为Botan的维护者,Meusel深知加密可能被破解的各种方式。仅仅让数学运算正确是不够的,软件还需要安全地进行加密和解密。
编写执行这些任务的代码可能比一些人想象的更加棘手。而编译器并没有提供帮助。
阻断侧信道攻击
Meusel提供了一个他在实现简单登录系统时遇到的问题示例。
用户输入密码,系统逐字符与数据库进行比对。一旦第一个字符不匹配,就返回错误信息。
对于试图入侵的密切观察者来说,系统返回错误所用的时间表明了用户已经正确输入了多少个密码字符。响应时间越长,表明猜中的密码部分越多。
这种侧信道泄露在过去曾被用来促进暴力破解攻击。它只需要一个高分辨率时钟来识别响应时间的细微差别。
幸好密码学家天生具有偏执特质。他们已经创建了预防性函数来均衡这些响应时间,使其不那么容易泄露信息。这些恒定时间实现"使运行时间独立于密码",Meusel说。
问题解决了吗?如果编译器插手就不一定了
GNU C编译器在处理布尔值方面表现出色,可能过于聪明了。就像微软回形针助手那样聪明。
Meusel通过GCC15.2(使用-std=c++23 -O3参数)运行了一个恒定时间实现。
检查字符的循环在字符正确时提前退出,所以GCC认为函数的其余部分不需要了。但是真正修复时间问题的其余代码被丢弃了,侧信道漏洞再次暴露。谢谢你,GCC。
Meusel没有深入解释为什么C优化器对布尔比较有偏见,但优秀的C程序员都知道要警惕布尔逻辑的激进优化,这可能对最终产品造成危害。
布尔判断意味着分支,这对硬件来说是昂贵的,所以编译器宁愿将你的有分支代码转换为无分支控制流逻辑,这样很酷,对吧?
那是你那里的布尔逻辑吗?
Meusel建议,诀窍是向编译器隐藏这个小程序的语义。
第一步是用两位整数替换循环中的布尔值,并使用一些位移或按位操作来掩码输入(Meusel在他的演讲中提供了必要的代码,所以可以查看幻灯片了解所有技术细节)。
你可能认为这样就够了。
但GCC比这更聪明。它能看出你试图进行偷偷摸摸的布尔比较。
所以你需要对输入和输出都应用混淆函数。但这不是为了程序本身的好处,而是因为这些是编译器可能用来"搞砸我们"的其他值,Meusel说。
最后,你需要通过一些内联汇编代码传递这个值,这些代码绝对什么都不做,只是返回相同的值。实际上,它警告不理解汇编的编译器,不要搞乱这些值,无论它们看起来多么像布尔值。
"这就是现在做这些事情的方式,"他补充道。
加密编码者的处境
"现在你可能会说,'嗯,这变得有点复杂,很容易出错'...你说得没错。"Meusel说。
要求普通程序员理解内联汇编,或者任何这些本质上晦涩的混淆技术,这公平吗?更不用说未来维护这些软件的人了?而且AI需要多少Token才能绕过所有这些伪装?
但这就是加密库编码者的处境,寻找方法抵御侧信道攻击,同时向无情的编译器隐藏解决方案。
其他编译器无疑也有自己的怪癖。谁知道Intel C++编译器或Clang中隐藏着什么恐怖。随着每一代新编译器的出现,都会带来新的优化问题需要应对,Meusel感叹道。
了解地形,结伴而行
从Meusel的演讲中可以得出几个要点,主要的一个是:也许应该关闭GCC的优化按钮?
尽管如此,编译器构建者可能需要考虑代码效率之外的其他因素。
"他们想让你的代码运行得更快,而且他们确实很擅长这个,但他们没有将你的实现的任何其他质量要求纳入考虑,"Meusel说。
正如一位听众建议的,也许有一天编译器可以接受提示,指定代码的哪些区域不要修改。
在那之前,安全软件设计师需要牢记他们正在设计的系统的所有部分,包括使用的开发工具以及它们的工作方式。
对于他们,Meusel推荐了valgrind,一个开源的内存调试工具。例如,它可以警告你程序是否依赖于未定义的值。
最后,实现安全很困难,不仅仅因为涉及的数学运算。单打独斗太困难了。不要自己重新发明轮子;而是加入一个项目,Meusel建议。
Q&A
Q1:GNU C编译器为什么会破坏加密代码的安全性?
A:GNU C编译器在优化代码时会自动去除它认为不必要的部分。当它遇到恒定时间实现的加密代码时,会错误地优化掉防止侧信道攻击的关键代码部分,从而重新暴露安全漏洞。
Q2:什么是侧信道攻击?它是如何工作的?
A:侧信道攻击是通过观察系统的响应时间来获取信息的攻击方式。例如在密码验证中,系统返回错误的时间长短可以透露已正确猜中的密码字符数量,攻击者可以利用高分辨率时钟检测这些细微的时间差别。
Q3:如何防止编译器破坏加密代码的安全措施?
A:需要使用多种技术来隐藏代码的真实意图:用整数替换布尔值、使用位操作进行掩码、应用混淆函数,以及通过内联汇编代码来警告编译器不要优化这些关键值。这些方法虽然复杂,但是保护加密安全的必要手段。