研究!rsc公司

关于编程的想法和链接,通过

RSS(RSS)

Debian/OpenSSL灾难的教训
发布于2008年5月21日星期三。

上周,Debian在2006年9月宣布他们意外地破坏了OpenSSL伪随机数生成器试图让Valgrind的警告保持沉默。这样做的一个影响是ssh-密钥程序安装在最新的Debian系统上(以及像Ubuntu这样的Debian-derived系统)只能生成给定类型和大小的32767个不同SSH密钥,所以有很多人和同样的钥匙.

许多人都被指责过,但谁犯了这个错误并不真正有趣:每个人犯错误。有趣的是这种情况鼓励犯错误,这使它成为可能近两年来都没有注意到。

要做到这一点,您必须了解所涉及的代码和错误的详细信息;这些需要理解一点关于熵和随机数生成器。


熵是衡量一段数据令人惊讶程度的指标。Jon Lester没有在昨晚的红袜队比赛中投球,但是这并不奇怪,他只在每五场比赛中投球,所以大多数他不投球的夜晚。然而,在周一晚上,他确实投球了,并且(令人惊讶的是)投了一个反对票。赛前没有人预料到这一点:这是一个高熵事件。

熵可以用比特来衡量:一个完美的硬币翻转产生1比特的熵。一个有偏见的硬币产生的收益更少:投掷一个上来的硬币时只有25%的收益时间只为尾部产生约0.4比特,为头部产生约2比特,或平均0.8比特的熵。确切的细节并不太重要。重要的是熵是对数据的不可预测性。

理想的随机字节序列熵等于它的长度,但大多数时候我们必须用低熵序列凑合一下。例如,今年美国职业大联盟无提名者的日期将是非常不可预测的序列,但也很短。另一方面,收集莱斯特推荐的日期今年的红袜队是可以预见的基本上每五天一次,但仍有意外事件发生比如受伤和下雨,这些都是不可预测的。no-hitter序列非常不可预测,但非常短。莱斯特的首发顺序只是有点不可预测,但太多了总熵越长。

计算机面临着同样的问题:它们可以访问长时间低熵(大多可预测)源,如设备中断或从声卡或视频卡进行静态采样,但它们需要高熵(完全不可预测)的字节序列,其中每一位完全不可预测。要将前者转换为后者,需要一个安全的伪随机数字生成器使用作为“熵榨汁器”的确定性函数这需要大量的低熵数据,称为熵池,然后挤压生产少量的高熵随机字节序列。确定性程序不能产生熵,所以出来的熵不能大于熵的量生成器刚刚将熵池压缩为更集中的形式。加密散列函数,如MD5和SHA1事实证明,这是一款很好的熵榨汁机。它们让每个输入位对每个输出位,因此无论哪个输入位是在不可预测的情况下,输出具有一致的高熵。(事实上,这就是哈希函数的本质,这就是为什么密码学家只是说散列函数,而不是像熵榨汁机这样的虚构术语。)

熵的坏处在于你不能测量它,除非在上下文中:如果您读取32位数字/开发/随机就像我刚才做的那样,你可能会对4016139919感到满意,但如果你再读十遍,你会不高兴的继续领取4016139919。(从技术上讲,最后一句话完全有缺陷,但你明白了。另请参见这个漫画这个漫画.)熵榨汁机的优点是只要有池中某处的熵,他们会把它提取出来。向池中添加更多数据也无妨:它会增加集体熵,否则就没有效果。


开放SSL

OpenSSL实现了这样一个基于散列的熵榨汁器。它提供了一个功能RAND_add(buf,n,e)添加了一个长度缓冲器n个和熵e(电子)到熵池。在内部,入口池只是一个MD5或SHA1哈希状态:RAND_添加电话MD_更新添加字节到正在运行的哈希计算。参数e(电子)是由调用者关于中包含的熵缓冲器.OpenSSL使用它来保持对总金额的持续估计缓冲区中的熵。OpenSSL还提供了RAND_字节(buf,n)那个返回高熵随机字节序列。如果运行估计表明不足池中的熵,RAND_字节返回错误。这是完全合理的。

OpenSSL中特定于Unix的代码为熵榨汁机提供了种子用一些狡猾的代码。的本质RAND_轮询在里面rand_unix(通用)。c(c)是(我的话):

char buf[100];fd=打开(“/dev/random”,O_RDONLY);n=读取(fd,buf,sizeof buf);闭合(fd);RAND_add(buf,大小buf,n);

请注意RAND_添加对…说使用整个缓冲区,但仅预期n个字节熵。RAND_加载文件做类似的从文件中读取时,会出现未初始化的引用明确标记为故意(实际代码):

i=弗雷德(buf,1,n,in);如果(i<=0)中断;/*即使n!=i、 使用完整数组*/RAND_ad(buf,n,i);

这里的理由是包括未初始化的片段在缓冲区的末尾,实际可能会增加熵,无论如何,诚实地说声称的熵量不会打破熵池估计。

同样,函数RAND_字节(buf,n),其主要目的是获取n个高熵来自榨汁机的字节,添加缓冲器到熵池(其行为类似RAND_add(buf,n,0))在它填满之前缓冲器.

那么,至少在三个不同的地方,OpenSSL开发人员明确选择使用未初始化的缓冲区作为可能的熵源。虽然这在理论上是可以辩护的(不会造成伤害),这主要是伏都教和一厢情愿思想的结合,这使得代码难以理解和分析。

特别是RAND_字节惯例原因每个呼叫站点的问题如下:

char buf[100];n=RAND_bytes(buf,大小buf);

这个成语在Valgrind(和它的商业表亲Purify)中引起了很多警告有一个#ifdef(如果定义)要关闭该行为,请执行以下操作:

#ifndef净化MD_更新(&m,buf,j);/*purify投诉*/#结尾

其他两种情况很少发生:在里面RAND_轮询,必须有一个从中读取/开发/随机什么时候内核几乎没有多余的随机性,或致电RAND_加载文件在文件上据报道只有一种尺寸斯达但是在中返回的字节数较少阅读.当它们发生时MD_更新,里面的那个RAND_添加,在堆栈跟踪上由Valgrind报道。


Debian公司

Debian维护人员面临Valgrind的使用报告调用时未初始化的数据MD_更新,它用于将缓冲区混合到内部的熵池中二者都RAND_添加RAND_字节.通常,您可能会在调用堆栈中查找得更远,但有多个实例,具有不同的调用方。唯一常见的部分是MD_更新.自从第二次MD_更新已经知道了是非本质的-它可以安全地禁用以在Purify-it下运行理所当然地认为,第一个可能也是不必要的。每个调用的上下文看起来基本相同(源代码因素考虑得不太好)。

Debian维护人员知道他不理解代码,所以他寻求帮助开放ssl-dev邮件列表:

主题:随机数生成器、未初始化数据和valgrind。日期:2006-05-01 19:14:00使用valgrind,它可以显示关于执行条件基于统一值的跳跃。这些统一化的价值观是在随机数生成器中生成。它添加了一个池的未初始化缓冲区。有问题的代码如下2crypto/rand/md_rand.c中的代码片段:247:MD_更新(&m,buf,j);467:#ifndef净化MD_更新(-m,buf,j);/*purify投诉*/#结尾由于瓦尔格林的工作方式(以及必须工作的方式)首先使用单位化价值的地方,是报告的错误可能完全不同,也可能是很难找到问题所在。...我目前认为最好的选择是实际发表意见这两行代码。但我不知道这有什么影响真的对RNG有影响。我看到的唯一影响是游泳池可能接收较少的熵。但另一方面,我甚至没有确定一些统一化数据的熵有多大。你们觉得删除这两行代码怎么样?

他得到了两个实质性的答复。第一个回复来自电子邮件地址openssl.org网站似乎是开发人员之一:

列表:openssl-dev主题:回复:随机数生成器、未初始化数据和valgrind。日期:2006-05-01 22:34:12>我目前认为最好的选择是实际发表意见>这两行代码。但我不知道这有什么影响>真的对RNG有影响。我看到的唯一影响是游泳池>可能会收到较少的熵。但另一方面,我甚至没有>确定一些统一化数据的熵有多大。>不多。如果它有助于调试,我支持删除它们。(然而,我最后一次检查时,瓦尔格林德报告了数千个虚假信息错误消息。情况好转了吗?)

第二个来自不在openssl.org网站谁回答了开发者的问题:

列表:openssl-dev主题:Re:随机数生成器,未初始化的数据和valgrind。日期:2006-05-02 6:08:10>不多。如果它有助于调试,我支持删除它们。>(然而,我最后一次检查时,瓦尔格林德报告了数千个虚假信息>错误消息。情况好转了吗?)我最近用-DPURIFY=1在Debian上编译了vanilla OpenSSL 0.9.8a带有valgrind版本3.1.1的GNU/Linux“sid”能够调试一些应用程序同时使用TLS/SSL作为S/MIME而没有任何警告或错误关于OpenSSL代码。没有-DPURIFY你真的被淹没了警告。所以是的,我认为不要使用未初始化的内存(它只是一个行,另一个事件已经被注释掉)帮助了valgrind。

两者本质上都是说,“继续,删除MD_更新行。”Debian维护人员这样做了,导致RAND_添加不添加对熵池进行任何操作,但仍会更新熵估计。还有其他MD_更新调用没有使用缓冲器,而这些仍然存在。唯一的一个那有点不可预测RAND_字节在每次调用时将当前进程ID添加到熵池中。这就是为什么OpenSSH仍然可以生成32767个可能的SSH密钥指定类型和大小(每个pid一个),而不是一个。


故障级联

就像任何真正的惨败一样,一连串的错误决策所有人都排好队来实现这一目标:

阅读各种博客,你会发现各种相关方的情报被侮辱了,但这真的是过程的失败,而不是智慧的失败。维护人员知道他不是中的代码专家问题,向专家寻求帮助,却得到了不好的信息。


经验教训

在过去的十年里,我花了很多时间来维护这两者计划9和aPlan 9软件到Unix的端口.我编辑了很多我不完全理解的代码,我已经回答了一些关于我理解的代码的问题当其他人来问的时候。我在编辑过程中犯了不少尴尬的错误不熟悉的代码,我可能已经给出了我的份额无意中错误的建议。即使是最好的程序员也总是会犯错误。惨败的教训,对我来说,可以采取哪些步骤来犯错误频率更低,更容易找到。

对我来说,我学到的教训是:

在我自己的软件维护中,我想我正在做一个前三节做得很好,但不是很好最后两个。对于一个低调的人来说,这样说很诱人像计划9这样的项目,实际上并不需要它们,但更重要的是很可能是懒惰而不是事实。


链接和注释

这里是Debian公告.我还没有看到任何关于卢西亚诺·贝洛是如何发现这只虫子的报道。如果你这样做了,请发表评论。

这是一份原件Debian错误报告关于Valgrind的投诉。原来的海报实际上有一个正确的修正;维护者错误地(但合理地)争辩因为还有其他情况也会导致未初始化的引用,bug必须位于RAND_添加自身。

我一直在说RAND_添加RAND_字节,但在代码中,实际功能是ssleay_rand_添加ssleay_rand_字节,英寸md_土地。c(c).后者用作前者的实现。RAND_轮询在中rand_unix(通用)。c(c)、和RAND_加载文件在中随机文件。c(c).

(评论最初通过Blogger发布。)

  • 杰克·纳尔逊 (2008年5月21日上午6:32)“如果它有助于调试”甚至与“继续并删除它以进行生产部署”不太一样,并且这样描述它是虚伪的。

  • 莱斯特 (2008年5月21日上午6:46)另一种策略可能是对缓冲区中剩余的“未接触”字节进行异或。这可能对每个人都有用。

  • 俄罗斯考克斯 (2008年5月21日上午6:52)这很有趣。我读到的讨论是关于清理代码的生产版本,以便更容易调试。直到你发表评论,我才注意到,但当然可以把讨论理解为只是为了调试而进行的临时更改。

    这只是强调了邮件列表讨论不能替代代码审查。

  • 托尼特曼 (2008年5月21日上午7:27)首先,我不明白为什么Debian首先需要更改openssl。也许我不理解这一部分。

    其次,valgrid/purize是一个工具。盲目地遵循这个或任何其他源代码分析工具的建议并不是任何真正的包开发人员上游的人的业务。对不起的。如果你不理解某事,不要改变它。

    我以前曾与狂热者一起工作过,他们将Java源代码分析器当作武器使用。这是一个笑话,他们是一个玩笑。

    用你的头。思考、思考、思考。只是因为valgrind/prify说它做得不对。Lint也是这样,其他所有源代码分析工具也是如此。

  • 马赫特 (2008年5月21日上午7:50)依赖魔法是一种不好的形式,不记录魔法是自杀。

    一个简单的

    //使用未初始化的数据作为熵

    在有人过来说“让我们把堆归零”以获得额外的安全性之前,这会有所帮助。

  • 乔什·里奇 (2008年5月21日上午8:03)谢谢你的来信。它很有趣,写得很好。现在,我对盲目更新我的debian机器而不进行进一步挖掘并不感到难过。

  • 贾斯汀·梅森 (2008年5月21日上午8:46)很好地说明了“聪明代码”的危险性。

    关于邮件列表不能很好地替代适当的代码审查——这是真的,但开源项目只是简单地使用列表,没有其他选择。从经验来看,一个运行正常、成熟的开源项目可能会有跨越多个大洲的开发人员,鉴于此,这种并行代码审查的机会最多每隔几年就会出现一次。

    也许随着基于谷歌Mondrian的开源代码审查工具的新趋势,我们可能很快就会推出一种比补丁和漏洞跟踪器或邮件列表更好的分布式代码审查机制。希望在这里。

    顺便说一句,我想指出,因为我在其他地方看到过一些评论,表明这种问题永远不会出现在专有软件中——遗憾的是,情况并非如此。我曾在一些公司工作过,在那里旧软件进入了其生命周期的“维护阶段”,并立即被移交给了一个由刚起步的大学初级开发人员组成的团队,他们很少或根本没有提前接触过产品,对代码知识甚少,对如何系统地修复错误了解甚少,并且没有与原始开发人员联系进行审查。正如你可能想象的那样,结果有时是灾难性的。。。但由于它是专有软件,它远没有开源领域偶尔发生的灾难那么明显。

  • 加博 (2008年5月21日上午9:29)另外,我们不要忘记这个补丁的原因:消除一些valgrind警告。

    我认为debian不应该仅仅因为一些code-checker工具显示了一些警告就对包(尤其是如此重要的中央包)进行修补。

    如果openssl会崩溃,或者以其他方式无法使用,那么好吧,做点什么吧。但只是为了一个警告?简单地向上游提交补丁会是一个更好的解决方案。

  • 杰夫·沃尔登 (2008年5月21日上午9:58)不要编写过于聪明的代码是一个好主意,但更好的做法是确保您编写的每一段代码都有测试。如果您知道什么是正确的行为,请确保该行为是可复制的,并且不会进入发布。(请注意,即使对于随机性算法也可以这样做,尽管这是非确定性的,如回归-211590.js。这在这里可能会有效,尽管这可能不太可能。)这并不是干净代码(更重要的是,干净API)的真正替代品,但至少现在或最终重写代码时不会倒退。

  • 我们是Dave (2008年5月21日上午10:07)托尼特曼、马赫特和加博都取得了优异的成绩。

    还有两点尚未得到足够重视--

    *OpenSSL的家伙们的“大教堂”态度——直接来自BSD魅力学校——必须承担一些责任。每个项目都需要一个一站式反馈机制,由多个核心维护人员遵循。像Debian维护者这样的超级用户是开源的alpha和omega;把它们当作一种不方便的分心方式不仅是粗鲁的,而且是自我毁灭的。

    *Debian对修补的态度仍然是一个等待发生的意外。许多其他供应商也有类似的错误态度。修补应该是分销商最终绝望的行为,而不是市场差异或政策问题。如果您需要更改,请向上游提交,如果上游拒绝更改,请找出原因。

  • 俄罗斯考克斯 (2008年5月21日上午10:39)@Jeff Walden:关于这个bug,最有趣和微妙的一点是PRNG会通过我能想象的任何单进程随机性测试。(当然回归-211590.js.)

    PRNG仍在生成一个单独的字节流,该字节流是加密随机的。问题是,如果你运行它,我运行它,我们很有可能得到相同的字节流。

    我很想知道一个看似合理的测试会发现这个问题。我想不出一个。

  • 山姆 (2008年5月21日下午4:29)卢西亚诺偶然发现了这个错误:他需要为自己的一个项目生成许多素数,并对自己得到许多重复感到惊讶。

  • 俄罗斯考克斯 (2008年5月21日下午4:40)@山姆:你有一个链接来描述卢西亚诺到底在做什么吗?我相信你,但用一种显而易见的方法(使用一个过程)生成大量素数是不会出现这种情况的。

  • 亚伦·丹尼 (2008年5月21日10:42 PM)关于您不应该听取源代码分析工具的抱怨通常是正确的,但在这种特定情况下是错误的。在C语言中,从未初始化的内存中读取会调用未定义的行为。瓦尔格林德恰当地警告说,发生了令人难以置信的事情。

  • 萨姆·霍泽瓦尔 (2008年5月22日凌晨1:43)@恐怕你必须向卢西亚诺询问细节。我只有几天前关于#debian-devel的对话:

    这是怎么发现的?检查,还是有人两次生成了相同的密钥?
    <卢西亚诺>真的出了事故。我需要很多素数。。。0:-)
    你每次都得到相同的数字?
    塞塞,不是每次都这样:P

  • 克拉根 (2008年5月22日凌晨3:01)我看到很多评论指责库尔特想关闭瓦尔格林德
    向上。例如:

    >其次,valgrid[sic]/prify是一个工具。盲目追随
    >这个或任何其他源代码分析器工具的建议不是
    >任何处于真实包裹上游的人的业务
    >开发人员。

    撇开Valgrind和Purify都没有分析源代码,
    有一个很好的理由修改代码以关闭它:如果它
    发出许多警告,但这些警告并不表示
    程序,则无法使用它来查找实际错误。Valgrind是一个
    非常有用的工具,值得花点功夫来实现
    工作,以及软件的生产版本的工作,尤其是
    安全关键型软件,如OpenSSL,其中缓冲区溢出或
    double-free可能会导致未经认证的远程磁孔。

    我认为卢西亚诺正在生成一系列SSL证书。这个
    显而易见的方法是使用shell脚本;我不知道
    他就是这么做的。他说他在
    24小时生成证书!

  • 克拉根 (2008年5月22日凌晨3:01)哇,Blogger的评论真糟糕。对此我很抱歉。

  • (2008年5月22日上午5:53)最近github.com上的人也注意到了这一点,他们注意到看似无关的用户生成了相同的公共SSH密钥。

    (此处简要提及。)

  • 哑光 (2008年5月22日上午7:14)将getpid()放入混合是(很可能)明智的设计决策,否则,如果程序派生()s并继续使用相同的池,那么子级和父级将生成相同的“随机”字节。

  • Resuna公司 (2008年5月22日上午7:31)对我来说,最大的失败是没有记录你的聪明。有时你必须聪明,使用聪明的代码,但你应该解释为什么要这样做。如果所有这些注释都是作者认为自己聪明的地方,并解释了原因,我宁愿阅读一段平均每个文件只有0.8条注释的代码。

    如果你写了聪明的代码,就在评论中吹嘘它。这将帮助一些不理解它的可怜的家伙,即使事实证明你并不聪明,他们也会更喜欢你,因为你解释了为什么它只是晦涩难懂的,所以他们知道简化它是否安全。五年后,这个“可怜的家伙”很可能就是你。

    关于评论的评论:

    *中世纪的大教堂实际上是使用集市系统建造的。结果他们中的许多人倒下了。这个术语真的很可怕。

    *我同意任何人的建议,如果一个调试工具告诉你你正在做一些愚蠢的事情,也许你是。

    *Bug跟踪器是做这类事情的好地方。当我在谷歌上搜索一个bug时,把我扔到了bug跟踪器的某个地方,我总是利用这个机会阅读一些类似的bug,因为我当时正在考虑这个问题,并根据需要添加评论和补丁。

    *如果(a)您确定这是您最后一次分叉的地方,或者(b)您在分叉()之后再做,那么将getpid()放入混合中可能会很有用。

  • 作记号 (2008年5月22日下午12:13)“另外,我们不要忘记这个补丁的原因:让一些valgrind警告沉默。

    我认为debian不应该仅仅因为一些code-checker工具显示了一些警告就对包(尤其是如此重要的中央包)进行修补。"

    这里的问题不是关于OpenSSL的警告,而是当您在使用OpenSSL的软件上运行valgrind时,就会收到警告。由于大量与OpenSSL相关的警告,因此很难看到软件的任何潜在问题。

    这大概也是为什么“如果它有助于调试”被解释为可以在生产部署中使用的原因——它是OpenSSL的生产部署,可能用于调试某些客户端代码。

  • 病毒的 (2008年5月23日上午10:00)在这里,你们(所有人)谈谈如何评论代码。。。我想加一些便士。。。有一次我得到了一个很好的教训:
    -代码是问题的解决方案。在注释中,如果您编写代码解决的问题是什么,这将有所帮助。如果评论只解释了解决方案,那也没什么帮助。

    对我来说,/*即使n!=i、 使用完整数组*/是冗余注释的另一个示例,而不是解释下一行解决的问题:使用缓冲区的未初始化部分增加熵,正如我们已经用RAND_add计算过的那样。
    (如果我理解正确的话)

  • (2008年5月24日上午1:14)2贾斯汀:

    并排评审不仅是“并排坐在桌边”,而且是“新旧版本并排看”。

    基本上,我认为在跨越多个大陆的开源团队中建立适当的软件审查流程没有任何问题。

    1.发送补丁以供审查。
    2.评审员对变更进行彻底检查,包括差异等,并对其进行评论。
    3.寄件人处理意见。
    重复第2和第3步,直到审核人明确批准。
    4.发送方将代码提交到中继。

    审核人的批准意味着他与发送者共同负责引入的任何错误。

    我想知道,为什么像Debian这样的大人物仍然没有复习的想法。

  • (2008年5月24日上午1:18)持续的。。

    这不是一个工具的问题,这个过程可以用一个简单的差异来设置。我真的不知道障碍是什么。

    顺便说一句,吉多·范·罗苏姆(Guido van Rossum)有一个蒙德里安克隆人,名叫里特维尔(Rietveld)。

  • 怪物 (2009年8月8日上午11:13)嘿,异常处理得很好。。从未想过这样。。很好。/。
    www.spyfree.info。你会得到更多的细节。。

  • 欧文 (2011年4月19日上午7:26)为什么没有人提到测试是这里的问题之一?

    如果有一个测试生成了10万个密钥,并进行了一些统计分析,那么这个问题将有95%以上的几率被发现。