前车之鉴:苹果的GoToFail漏洞

摘要: 新近发现的iOS和OS X安全缺陷揭示出编码规范、单元测试、系统测试、代码复查方案、错误管理策略和工具部署方面的缺陷冰山。

ios

新近发现的iOS和OS X安全缺陷揭示出编码规范、单元测试、系统测试、代码复查方案、错误管理策略和工具部署方面的缺陷冰山。

ZDNet的Larry Seltzer 称这一缺陷“震撼且尴尬”,苹果同时为iOS 6发布补丁更是从侧面证实了其严重性。“苹果并不愿意iOS用户继续使用iOS 6系统,但尽管如此,他们还是修复了缺陷。这足以说明其严重程度。”Larry说。

到这篇文章完成时,苹果已经为iOS和OS X发布了软件更新,用以修复该问题:一个能让攻击者拦截、获取并修改加密数据的通信漏洞。但从这个故事中,我们还是能够学到某些东西。

谷歌的Adam Langley解释说,这一缺陷位于苹果开源发布的SSL/TLS协议实现——SecureTransport框架中,缺陷的引发原因在于部分代码不可达。试看如下代码:

static OSStatus

SSLVerifySignedServerKeyExchange(SSLContext *ctx, bool isRsa, SSLBuffer signedParams,

uint8_t *signature, UInt16 signatureLen)

{

OSStatus err;

...

if ((err = SSLHashSHA1.update(&hashCtx, &serverRandom)) != 0)

goto fail;

if ((err = SSLHashSHA1.update(&hashCtx, &signedParams)) != 0)

goto fail;

goto fail;

if ((err = SSLHashSHA1.final(&hashCtx, &hashOut)) != 0)

goto fail;

...

fail:

SSLFreeBuffer(&signedHashes);

SSLFreeBuffer(&hashCtx);

return err;

}


注意其中有两个连续的goto fail语句。由于if语句没有加大括号,因此执行时,第二处goto fail会让程序直接跳转到fail标签,从而避开if语句之后的验证。这会导致在跳转的那一刻err变量不含任何错误信息,方法也无法返回任何。Adam Langley继续解释道:

签名验证将检查ServerKeyExchange消息中的签名。DHE和ECDHE密文族将这一技术用于连接过程中的临时密钥交换。服务器发起请求:“这里是临时密钥和签名,你可以通过我的证书证实我是发起者。”如果这时临时密钥和证书链之间的关联被破坏,所有的验证都会失效。这种情况下虽然仍然可以向客户端发送正确的证书链,但握手过程可能用错误的私钥签名,甚至没有签名!我们无法证实服务器端是否持有与证书中公钥配对的私钥。

诚如Larry Seltzer所言,因为苹果没有给出更多细节,我们无从知道这一bug是如何被发现的,但这件事让他开始好奇苹果的代码复查实践。他还指出,虽然这个错误一眼就能够发现,但当你面对足有1970行长度的整个文件时,将其识别出来就会很难。

Twitter上#gotofail主题的很多评论者公然视“使用goto”为罪魁祸首,最著名的当属Dijkstra论文中对goto“有害”的定性。这点是无可争议的,荷兰代尔夫特理工大学的软件工程教授 Arie van Deursen试着通过返回错误代码这种惯用法实现一种类似C中的异常机制来解释goto的用法,这样一来,那种惯用法就成了罪魁祸首。

Van Deursen写道,实际上,返回错误代码惯用法普遍存在于含bug的文件中,并且本身存在一定的问题。2005年van Deursen与Magiel Bruntink,Tom Tourwe合著论文的主要成果之一就是,通过审查某相当规模的代码库发现“每千行代码中因为return-error-code惯用法而导致的缺陷密度达2.1”。 这么高的缺陷密度的罪魁祸首是未检查调用、未正确传播的返回值以及未正确处理的错误条件导致的,落实了“这一惯用法相当容易出错”的罪名。

苹果前职员Kevin Marks在van Deursen的跟帖后留言指出,可以使用预处理宏使返回错误代码的方式更安全。这方面的例子包括BailOSErr以及致力于实现C的异常机制方面的工作就是这样用的例子。

说起错误代码的管理,Chris Leishman在评论van Deursen观点时称,错误指示变量被初始化为success,这才是两行goto真正引发漏洞的主要原因。如果错误指示变量的初始化改为OSStatus err = OSUnknownError,系统执行会更安全。

另一方面,Plausible Labs的软件工程师Landon Fuller提供了bug所影响那部分代码的可测性分析并证实SSLVerifySignedServerKeyExchange能够进行隔离性单元测试。C. Keith Ray强调了TDD的一个观点:“如果你要编写一条if语句,那你必须事先准备一个调用这条语句的测试。你需要在条件为真和为假的情况下分别测试”。

Arie van Deursen指出这一事件还有更多值得争议的地方。

他最先注意到含bug的文件“明显不是自动规范化的,其中有大量格式不一的空格、制表符和代码注释”,而“正确的缩进能立刻显示正在发生的可疑事”,并让bug查找更容易。除此之外,他进一步建议“将编码格式作为一项安全特性”,而“将空格作为一项安全关注点”。

Langley在其博客中写道,他认为代码复查是防止这类问题发生的有效手段。但Arie van Deursen指出,先前微软有一项代码复查研究曾证实以下观点:

复查并不是像很多项目成员所想的那样,通常情况下都能找出缺陷;而找出深层、微妙或是“宏”层次的问题就更罕见了。也许依靠这种程度的代码复查来保证质量境况堪忧。

 

最后一点,这种情况下工具也没起到作用,如Langley所说,Clang的-Wall参数不会发现两行goto和其后的死代码。据Simon Nicolussi所言,Clang提供了能发现该问题的-Weverything标识,但GCC默认去除了这一标识。这点也为Peter Nelson——另一位特定-Wunreachable-code参数存在指出者所证实。Van Deursen指出主要问题在于死代码的删除从根本上讲是一个不可判定问题,这需要完备性和误报间的权衡,也正是因为这一原因,缺省情况下才不含不可达检验。(infoq/Sergio De Simone)

查看英文原文:Lessons Learned from Apple's GoToFail Bug

感谢邵思华对本文的审校。

技术控是百度新闻与钛媒体合作,专门为技术爱好者打造的栏目

本文系作者 infoq 授权钛媒体发表,并经钛媒体编辑,转载请注明出处、作者和本文链接
分享到:

第一时间获取TMT行业新鲜资讯和深度商业分析,请在微信公众账号中搜索「钛媒体」或者「taimeiti」,或用手机扫描左方二维码,即可获得钛媒体每日精华内容推送和最优搜索体验,并参与编辑活动。

infoq
infoq

InfoQ是一家国际性的公司,在加拿大、美国、中国和罗马尼亚均设有办公室。目前,我们运作两大品牌产品:InfoQ网站,以及QCon大会。InfoQ目前设有英文站、中文站、日文站和巴西葡文站。

评论(0

Oh! no

您是否确认要删除该条评论吗?

分享到微信朋友圈