在今天的 CTC 会议中,我们讨论了恢复DeprecationWarning
调用Buffer
而没有new
中引入的v7
(PR 在这里),并且很明显我们需要制定一个长期计划,说明我们究竟想要实现什么目标,如何做到这一点,并在我们采取行动之前和之后改进我们的信息传递。 我将尝试总结我们正在谈论的内容; 显然,我有点偏见,因为我参与了之前的大量讨论。 (顺便说一句,这仍然很长,所以我希望很多人会发现这里的信息足够有用以保证文字墙。)
Buffer
构造函数存在可用性缺陷,即它接受具有不同类型签名的输入,因此new Buffer('abcdef')
和new Buffer(100)
都将返回有效缓冲区,在后一种情况下, Buffer
将包含 100 字节的未初始化内存。 这是一个安全问题,原因有二:
Buffer
构造函数时,需要一个字符串但实际传递的是一个数字,将返回未初始化的内存:// This is a dangerous example of converting a string to Base64!
new Buffer(someStringReceivedFromTheUser).toString('base64')
在此处传递值100
将返回可能包含垃圾的内存切片,但通常可以包含以前存储在内存中的任何值,包括凭据、源代码等。 @ChALkeR对此有很好的描述: https :
同样, @ChALkeR在https://github.com/ChALkeR/notes/blob/master/Lets-fix-Buffer-API.md上有一篇关于这些安全问题的非常好的文章Buffer.alloc()
/ Buffer.from()
情况,但它包含一个有用的常见问题解答,其中包含对诸如“为什么不让Buffer(number)
默认为零填充所有内容?”之类的问题的答案。
到目前为止,在 Node v6.0.0 中引入了更安全的Buffer.alloc()
/ Buffer.from()
API,然后向后移植到 v5.10.0 和 v4.5.0 版本。 此外,v6.0.0 附带了旧Buffer()
API 的纯文档弃用。
6 月, https://github.com/nodejs/node/pull/7152开放,它试图使用运行时弃用来弃用旧的Buffer()
API,即在Buffer()
时Buffer()
或new Buffer()
是第一次执行。 目前,该 PR 仍处于开放状态。 它的简化版本https://github.com/nodejs/node/pull/8169作为 v7.0.0 中的 semver-major 更改登陆,它发出并显示DeprecationWarning
s 以供使用Buffer()
仅限new Buffer()
。
在做出该决定之前,我已经在https://github.com/nodejs/node/pull/7152#issuecomment -241355246 ¹ 中总结了目标和可能采取的行动; 并且@jasnell在https://github.com/nodejs/node/pull/7152#issuecomment -240753218 中编写了一个当时的长期计划,其中包括 v8.0.0 中new Buffer()
运行时弃用以及稍后对Buffer
构造函数的实际重大更改。
这种区别的原因是试图在遥远的未来开放的某个时刻保持使Buffer
成为一个合适的 ES6 类的可能性,这意味着调用new Buffer()
可能始终有效。 (将Buffer
转换为类的效果将是适当的子类化,并Buffer()
没有new
情况下破坏Buffer()
new
。但是,完全有可能向 API 添加一个单独的类,该类将表现得像当前的Buffer
实现一样,只是有这些不同。)
由于在 v7.0.0 中弃用了Buffer()
而没有new
,来自社区知名成员的大量反对接踵而至,都在https://github.com/上的线程中https://github.com/nodejs/node/pull/8169。 一方面,很明显,我们在消息传递中未能明确表明这一变化的主要动机是帮助我们的用户避免严重的安全问题; 另一方面,就 Node 核心而言,添加的弃用警告似乎与模块作者和消费者对稳定性和向后兼容性的期望不一致。
因此,CTC 决定考虑恢复弃用警告,可能是暂时的,相应的 PR 位于https://github.com/nodejs/node/pull/9529。 对此的决定尚未做出,但已表示希望尽快做出决定,以限制可能出现不一致行为的 v7.x 版本的数量。
从讨论中可以看出,前进的道路显然是一个有争议的问题; 现在,意见范围从从不为任何版本的Buffer
构造函数引入运行时弃用,到在 v8.0.0 的下一个 semver-major 版本中应用它的所有用途。
对于完全运行时弃用Buffer
构造函数的最强烈和最常表达的论点很快仍然是用户可能不知道他们的应用程序的某些部分使用了不安全的 API,应该对此发出警告。
另一方面,警告本身被认为是对生态系统非常具有破坏性的变化,这表明绝对值得探索替代方法来减少Buffer()
和new Buffer()
。
/cc @nodejs/collaborators
¹ 从我在这里表达我的想法的方式来看,这可能很明显,也可能不明显——我试图坚持陈述事实——但事后看来,我很后悔这样写。
来自我自己的更多自以为是的东西:
我认为我们不应该将Buffer
变成 ES6 类; 我们可以为此创建一个单独的 API 并让Buffer
成为它的包装器,就像之前讨论过的那样,并且不需要真正破坏Buffer()
任何内容。 我知道我的评论上面链接中提到的可能断裂的动机贬低Buffer()
比早期的new Buffer()
,但我绝对不会再和我在这过去的自我认同。 然而,我 _do_ 看到它们的使用存在重大的安全风险,这可能保证在未来的某个时候完全弃用运行时。
相应地,我会要求其他人(尽管我知道我显然不能代表所有人)尽量避免将“Subclassability”和“ES6 class”作为此处弃用的原因; 我认为很明显我们的消息传递在这里中断了,正如https://github.com/nodejs/node/pull/7152和https://github.com/nodejs/node/pull/8169上的评论所反映的那样
@addaleax感谢您的详细撰写。 非常感激。
我会建议其他人已经提到的前进方式。
与 Buffer.alloc 类似,Buffer 构造函数返回的缓冲区开始填充零,而不是弃用任何东西。 将此更改反向移植到旧版本的节点(我希望看到它一直添加到 0.10,但我知道这不再受到官方支持)。
这种方法的一个好处是旧代码将像以前一样工作 - 无需更改。
它还鼓励模块作者升级到新 API,因为零填充会导致性能损失。
@mafintosh ...自动零填充仅解决了部分问题。 现有的Buffer()
使用的其他方面存在问题——但可以用几乎相同的方式修补。 不幸的是,这样做会使旧版本的 Node.js 用户做错事的情况变得不那么明显——并且在新版本的 Node.js 中透明地修复这个问题可能会导致开发人员完全不知道他们的旧版本的 Node.js。 js 可能有问题。 这并不是说默认情况下填充零并限制最大大小以避免 DOS 不是一个好方法,只是伴随着生态系统和可用性问题仍然存在。
@mafintosh有关@jasnell所说内容的更多背景信息,请参阅https://github.com/nodejs/node/issues/4660#issuecomment -171262864(在Buffer.alloc
存在之前)。 令人担忧的是,这实际上会 _create_ 安全问题,因为人们会停止在模块中使用new Buffer(num).fill(0)
,然后任何使用没有零填充行为的旧版 Node 的人都会受到攻击。
既然Buffer.alloc
存在,这可能更可行。 如果我们决定走那条路,我认为我们至少应该保留Buffer()
和new Buffer()
软弃用以避免这个问题并鼓励每个人使用Buffer.alloc
代替。
只是为了澄清:我完全赞成构造函数的软弃用。 我指的是做零填充而不是硬性弃用它。
我认为我们不应该将 Buffer 变成 ES6 类; 我们可以为此创建一个单独的 API 并使 Buffer 成为它的包装器
我将不同意这一点。 “只添加一个新的 API”的附加方法会导致设计杂乱无章。 node 的新手已经抱怨有太多东西要处理了,我们不要让它变得更糟。
不小心接受大数值会很快增加资源使用率
v6.x 和 v7.x 通过在调用Buffer(1234, 'encoding')
(但不是Buffer('1234', 'encoding')
)时抛出异常来解决这个问题,我们应该讨论将其向后移植到 v4.x。
我赞成,它提高了安全性,我只能看到它破坏了已经很容易被攻击者破坏的代码。 我们比一些黑帽子更好,对吗?
v6.x 和 v7.x 通过在调用
Buffer(1234, 'encoding')
时抛出异常来解决这个问题(但不适用于Buffer('1234', 'encoding')
)
当只有一个参数时,它们不会, Buffer(1234)
vs Buffer('1234')
。
我记录在案,支持弃用没有意义的 Node.js API(https://www.youtube.com/watch?v=jJaIwea8r2A,https://gist.github.com/sam-github/ 4c5c019b92cf95fb6571),我什至支持弃用这些 Buffer API(缓慢地、正确地传达),但我认为这些变化的痛苦并没有得到很好的理解。
一方面,控制台消息对下游有巨大的负面影响,因为对它们无能为力的人看到了它们,请参阅https://github.com/nodejs/node/pull/9483
但另一方面,随着模块依赖的增加,琐碎的更改变得越来越不琐碎。
例如,假设某个版本的 glob 因存在安全问题而被弃用。 一个新的专业出来了。 唯一的变化是在我不关心的一个不起眼的角落案例中,而且大多数人都不关心。 但是,它是一个功能变化,它_必须_是一个主要的。 没关系。
将直接依赖于 glob 的包更新到新的主要是微不足道的,只需在你的包中增加主要,API 实际上并没有改变,所以没有代码更改。 到现在为止还挺好。 所以依赖于 glob 的包会这样做,需要几分钟,重新发布,没问题。
但是作者只在他们的包的头部这样做。 回到历史并对曾经使用过 glob 的每个主要版本进行补丁发布? 这是很多工作,所以只有最新的。 也许他们会修改他们的包(编辑:主要)版本,因为,嘿,谁知道,也许某些下游 dep 依赖于那个特定的 glob 角落案例。
并且这些变化慢慢地上升,通过其他有重大更新的包上升 3 个级别,由 tap 使用......现在最新的 tap 取决于最新的 glob,这是 A-OK。
但是我有很多年没有接触过的单元测试包……现在我需要使用最新的 tap 来了解有关 glob 的消息……虽然 glob 的变化很小而且微不足道,但 tap 已经发生了变化一个 _lot_,我所有的测试现在都失败了,我花了几个小时来修复它们,即使 glob 的原始更改需要 _no js 代码更改来更新_!
最近,随着优雅的-fs 和 glob 更新,这一直发生在我身上。 我支持这些更新,我正在做这项工作,我不介意。
但值得记住的是,这两个更新的根源都是微不足道的,graceful-fs 和 glob 都有新版本,可以直接替换以前的专业。 所以,升级本来应该是微不足道的,它是针对直接依赖的,但是随着依赖树的变化,这些变化变得越来越不微不足道,因为它们开始与不那么微不足道的变化捆绑在一起。
这是 Buffer 更改会发生的事情。 使用 Buffer 的直接代码会发生微不足道的变化,但在树的更深处,它会受到更多的伤害。
当只有一个参数时,它们不会,Buffer(1234) 与 Buffer('1234')。
是的,这很遗憾,但短期内无法改变。 向后移植两个参数案例的更改至少会减轻@ChALkeR的 hoek 示例。
我一直在思考这个问题,实际上写了一点,但还没有分享我的想法,因为它们不完整。 但我想分享我的一个想法:也许根本问题与缓冲区或 API 根本无关,而是我们如何弃用这些东西。
我们都有两个共同的价值观,但现在它们相互冲突:安全和稳定。 Core 想要激励模块作者修复他们代码中可能存在的两个高度危险的缺陷:通过未初始化的缓冲区泄露秘密,以及通过使用未绑定检查的整数构造的缓冲区进行 DDOS 攻击。 创建了有缺陷的 API 后,他们承担起修复错误的责任似乎是合理的。 但是我们不能总是解决我们自己创造的问题,这是一个核心自己在一个坑里挖洞的情况,需要模块作者帮助把它们挖出来。 因为 Buffer API 是一种单向(有损)函数:您可以轻松地将 Buffer.from、Buffer.allocate 和 Buffer.allocUnsafe 转换为不安全的“Buffer()”形式,但给定 Buffer() 调用是不可能的自动选择基于参数的安全行为,因为知道它是否被安全使用需要分析 Buffer() 函数无法访问的调用函数中的源代码。
当你唯一的工具是锤子时,每个问题看起来都像钉子。 所以他们使用了他们仅有的工具:node 的文档网站,以及 node.js 中的控制台消息。 那是我们遇到稳定性冲突的时候。 因为显然 Node 的控制台输出被测试运行者视为其 API 的一部分,他们看到了弃用消息并吓坏了。 哪种方式在最初的目标上取得了成功:引起模块作者的注意,以便他们可以修复此缺陷。 然而,它面临另一个价值:稳定性。 模块作者吓坏了,认为核心在随意更改Buffer API,并大声抱怨。 这是正确的,因为损坏的测试用例,乘以每个包含某处作为依赖项的项目,会造成混乱。 (见左门)
问题的字面原因不是弃用 Buffer——而是它的通信方式,结果证明它破坏了模块,我认为这让核心感到惊讶。 我当然不会想到一条小的控制台日志消息会破坏构建。 但显然确实如此! 所以,我们能做些什么?
核心价值观的稳定性就像模块作者一样,我敢肯定。 因此,通过打印到控制台日志来弃用事物似乎很明显是错误的策略,因为这总是会干扰程序输出。 我认为弃用的策略......必须去。 应该从核心的工具箱中删除。
现在这是我的实际新颖性/贡献。 如果弃用的目标是引起模块作者的注意以便他们修复他们的代码,那么节点核心不是正确的场所。 Npm 是。 我认为,core 应该联系 npm,看看他们是否可以帮助宣传弃用。 在每个使用旧的不安全 API 的模块上放置警告横幅。 这将对代码的运行方式产生零影响,因此不会威胁模块生态系统的稳定性。 是的,开发人员可能会因为他们的模块使用不安全的 API 被公开呼吁而生气。 因此,请利用 npm 拥有所有作者的电子邮件地址这一事实,并提前让他们知道。 也许只是显示一个随着时间的推移变得更大的小警告。 关键是,弃用从根本上说是一种通过通信完成的社会过程,而不是可以通过修改节点引擎来完成的技术过程。
对不起,如果我有点啰嗦。
我当然不会想到一条小的控制台日志消息会破坏构建。 但显然确实如此!
到目前为止,我只听说过一个模块实际上在功能上被 Buffer-without-new 弃用破坏了。 我遇到的所有其他损坏情况都在查看 stderr 输出的测试中。
在每个使用旧的不安全 API 的模块上放置警告横幅。
静态分析在 JavaScript 中并不总是很好用。
我遇到的所有其他损坏情况都在查看 stderr 输出的测试中。
一个坏的测试是一个坏的测试,是一个坏的测试。 它具有破坏性,尤其是当您依赖持续部署并且中断测试会停止部署到生产时。 但这是我的假设。 我对细节有点好奇。 有人( @mafintosh @substack等)能谈谈由于测试套件中断而给他们带来的恐怖吗?
使用节点 CLI 程序时出现意外的控制台输出 _is_ 函数式
破损。
我(和@mafintosh 一起)是这个问题的最初记者。 在阅读了这个讨论之后(并处理了用户就这个问题提出的许多问题),我认为最好的方法是对用Buffer()
和new Buffer()
创建的缓冲区进行零填充。
提案:在 v7 中使用Buffer()
和new Buffer()
创建的零填充缓冲区(以及活动的 LTS 版本:v4 和 v6)
这解决了这里最重要的问题:通过无意的Buffer(num)
意外泄露数据的可能性。 Buffer(really_large_num)
的 DOS 问题仍然存在,但拒绝服务远比数据泄漏要好得多。
由于Buffer()
已经在文档中被软弃用,将来编写的模块不应该使用它。 使用Buffer()
现有代码只会从零填充中受益,这可能会修复现有的安全问题。
如果我们将零填充发送到所有活动的 LTS 版本,模块作者将开始假设Buffer(num)
将零填充的风险会降低,从而降低旧 Node 版本上的不安全模块。
总之,零填充解决了最重要的问题——数据泄漏——没有重大的破坏性变化,也没有硬性地弃用 Buffer API。
该解决方案让我们能够在可预见的未来支持遗留的Buffer()
API,确保现有代码继续工作,而不会对碰巧使用Buffer()
新代码造成安全风险。
似乎一致认为旧模块中的安全问题是一个真正的问题,但有些人认为它可以通过零填充new Buffer(num)
来更好地解决。 虽然这个替代提案有一些优点,但它也有其缺点,我认为这些缺点超过了优点。 我将尝试在下面总结它们。
解决方案 A:一次性运行时警告(又名硬弃用)
new
Buffer
情况下调用Buffer
成为一个可以被子类化的合适的 ES6 类。解决方案 B:用Buffer(num)
和new Buffer(num)
创建的零填充缓冲区
[new ]Buffer(num)
用法,因为他们不会有--trace-deprecation
的等价物到帮助他们。 向后移植也不会帮助出于某种原因将继续使用旧版本 Node.js 的用户。 (注意:我完全赞成强迫人们使用过时版本的节点进行升级,但默默地给他们提供安全漏洞是另一回事。)new Buffer
在旧代码中是可以的,那么在新代码中应该是可以的,那么有什么意义呢? Buffer.alloc*
?”)。 可能会给人留下糟糕的长期计划的印象。不会有一个让所有人都满意的解决方案。 我绝对是 👎 零填充Buffer
,同时我 👎 坚决反对任何东西。 我 👎 正在交付不安全的软件。 我也知道上述情况之一会发生。 这是一个糟糕的情况。
澄清一下,我在旧 API 中看到的主要问题是Buffer(string)
和new Buffer(string)
,而不是new Buffer('hello world')
和new Buffer(42)
。 对于旧模块,唯一有问题的情况是new Buffer(string)
,其中 string 是一个变量。
为Buffer(str)
发出警告 _just_ 而不是Buffer(str, 'utf8')
和Buffer(int)
也可能是一个可行的选择。 这应该将维护人员的表面积减少到有问题的情况。
这可能是一个愚蠢的想法。
我们可以开始在 linting/安全软件中添加规则来捕捉这个吗? 也许已经有了,但这可能是一种简单的入门方法。
为
Buffer(str)
发出警告 _just_ 而不是Buffer(str, 'utf8')
和Buffer(int)
也可能是一个可行的选择。 这应该将维护人员的表面积减少到有问题的情况。
我也想过这个,但不,那行不通。 Buffer(42, 'utf8')
在 4.x LTS 上做了坏事,所以Buffer(str, 'utf8')
并不是普遍安全的。 在 v4.x LTS 中添加一个 throw 是很难证明的。
new Buffer(42)
可能_可能_没问题,虽然我还不确定,需要多考虑一下。
更新:至少有一个缺点——如果我们保留它,我们将无法对new Buffer(42)
进行零填充,原因如下所述。
@feross Re:零填充,唯一可行的零填充时间是在同一版本中,该版本为 API 引入了运行时弃用警告。 例如,当/如果Buffer(42)
被运行时弃用并带有明确的信息,我们将确保没有人会在 _new_ 代码中添加它,认为它会在他们关心的所有版本上进行零填充。
零填充的问题在于,如果我们在 vN.0.0 中引入它,但没有在该版本中引入Buffer(42)
的运行时弃用,那么至少一些只关心 vN.0.0 的库作者会依赖Buffer(42)
被零填充,当有人在以前的版本上运行该代码时,事情会比现在更糟。
另外不要忘记,零填充并不能防止 DoS 问题。
回复:零填充,唯一可行的零填充时间是在为 API 引入运行时弃用警告的同一版本中。 例如,当/如果 Buffer(42) 在运行时被弃用并带有明确的消息,我们将确保没有人会在新代码中添加它,认为它会在他们关心的所有版本上进行零填充。
@ChALkeR确保安全和弃用 API 是两件事。 通过零填充可以保证安全性,这与将人员迁移到新 API 不同。 我觉得虽然两者可能有关联,但它们绝不是相互关联的。
@ChALkeR如果我理解正确,你会尝试指出如果人们不迁移到新的 API,npm 生态系统将比现在更糟。 鉴于在当前情况下没有人使用新 API,这是不可能的。 相反,我们_可以_做的是通过零填充使所有缓冲区代码立即安全,并推动生态系统向前迈进。
然后是将人们转移到新 API 的重点。 我觉得 NodeJS 通过发行说明、博客文章谈话和其他渠道有很好的推广。 足以让人们意识到更改的 API。 除此之外,还有一个激励开发人员以提高性能的形式选择新的 API。 如果我们做得好,所有关心性能的开发人员都将有办法和理由转向新的 API,而无需让维护人员感到难过。
我希望这听起来合理; 默认情况下,我支持零填充,因为这似乎是问题最少的前进方式。 Fwiw 我也很想看到零填充的性能影响,因为我不记得到目前为止看到任何数字并且拥有它们会很好地激励人们转向新的 API。
我希望这听起来合理; 默认情况下,我支持零填充,因为这似乎是问题最少的前进方式。 Fwiw 我也很想看到零填充的性能影响,因为我不记得到目前为止看到任何数字并且拥有它们会很好地激励人们转向新的 API。
console.time('new Buffer(1024)')
for (var i = 0; i < 10000000; i++) {
new Buffer(1024)
}
console.timeEnd('new Buffer(1024)')
这是大约 2 倍:
$ node --zero-fill-buffers test.js
new Buffer(1024): 4329.844ms
$ node test.js
new Buffer(1024): 2562.717ms
(节点 v6.9.1)
恕我直言,让旧的 API 变慢比发出警告更糟糕,因为你“强迫”人们移动,还有一个额外的问题是罪魁祸首不容易追踪。 从好的方面来说,测试不会导致发出警告。
作为 semver-major 更改和警告一起可能没问题,但不适用于 v4、v6 或 v7。
我仍然 👎 在零填充上,就像我在 👎 在弃用警告上一样。 但是,如果我们寻求弃用警告,则可以对任何new Buffer()
和Buffer()
调用进行零填充。
也许我们可以警告如下:
“出于安全原因,new Buffer() 和 Buffer 现在为零填充,请参阅 LINK”。 在 LINK 中,我们展示了新的 API 以及如何迁移到它们。 如果我们能写出像standard --fix
这样的东西来自动完成这项工作,那将会有很大的帮助。
上面的警告很烦人,但与完全弃用相比,它可能会引起更少的担忧。 这不是通用的“api 可能会消失”,但它是积极的:我们已弃用此 api 以确保您的安全,请阅读此链接以使此警告消失。
请记住,零填充目前以一种简单的方式完成。 预分配内存与将归零卸载到单独的线程相结合应该可以消除大部分开销。
@bnoordhuis请记住,零填充目前以一种简单的方式完成。
很高兴知道! 如果零填充的性能差异可以忽略不计,那对我来说似乎是最好的选择。
@seishun您列表中的大多数缺点都不正确。
如果不向后移植,则会产生新的安全漏洞,请参阅@ChALkeR的摘要
如果我们向支持的发布行添加零填充,那么影响将是最小的。 将涵盖 v4、v6、v7(以及 0.12,如果及时解决)上的用户。
至于使用 0.10 或其他不受支持版本的用户——他们已经通过在产品中运行不受支持的版本来玩火了。 即便如此,它们也不太可能受到使用Buffer()
不安全 IMO 的新代码的影响。 Buffer()
已在文档中弃用。 新代码不应该使用它。 (熟悉旧行为并且最近没有阅读文档的开发人员将继续假设他们负责清零/覆盖缓冲区,因此不会造成任何伤害。)
如果这是向后移植,它将悄悄地向某些用户引入非平凡的性能下降
这不是问题。 见@bnoordhuis的评论: https :
提出了一个问题,为什么我们不首先这样做而不是引入新的 API(“如果新的 Buffer 在旧代码中没问题,那么在新代码中应该没问题,那么 Buffer.alloc* 的意义何在?” )。 可能会给人留下糟糕的长期计划的印象。
新 API( Buffer.alloc()
等)的重点是将两种截然不同的行为拆分为显式函数。 Buffer.alloc()
分配num
字节的内存。 Buffer.from()
将对象转换为Buffer
。
@feross
将涵盖 v4、v6、v7(以及 0.12,如果及时解决)上的用户。
不,他们不会。 您假设立即更新,我预计会因此而遇到问题的用户数量(即,不会立即更新到分支中的最新版本,或者在安装依赖于的软件包之前更新速度不够快)零填充)作为非零和重要的。
此外,我估计这比强迫流行软件包的维护者进行微不足道的更新更能扰乱生态系统。
此外,每次有人提议补零时,他们都忽略了 DoS 问题。
熟悉旧行为并且最近没有阅读文档的开发人员将继续假设他们负责清零/覆盖缓冲区,因此不会造成任何伤害。
为什么您认为这些开发人员不会犯当初触发新 Buffer API 创建的相同错误?
这不是问题。 见@bnoordhuis的评论:#9531(评论)
我不会这么草率下结论。 请记住,人们在各种硬件上运行 Node.js。
新 API(Buffer.alloc() 等)的重点是将两种截然不同的行为拆分为显式函数。
不,将它们分开并不是最终目标。 最终目标是修复安全和 DoS 漏洞,我们同意应该通过引入新 API 而不是更改旧 API 来解决这个问题。 现在零填充意味着放弃该决定。
@ChALkeR迫使流行软件包的维护者进行微不足道的更新
影响比您或@seishun理解的要严重得多。
你不能只发几个 PR,做几个npm publish
可以了。 许多软件包依赖于不是最新版本的软件包。 因此,为了使警告在顶级包中消失,您需要升级一两个专业的 semver,并可能重写大量代码。 IMO 继续称其为“微不足道”是不准确的。
node.js 弃用Buffer()
那一天将是每个人在启动 node 时看到永久弃用错误的那一天。
也许弃用信息并迫使每个人做大量工作最终是正确的方法,但您不应该最小化这将对社区产生的巨大而非常实际的影响。
不,将它们分开并不是最终目标。 最终目标是修复安全和 DoS 漏洞
这根本不正确。 由于Buffer()
,Node.js 中从来没有任何安全或拒绝服务漏洞。 Buffer()
的行为是有据可查的,并且实现一直按文档工作。
我打开了最初的问题,因为Buffer()
是一个潜在的足枪。 根据传入的参数类型,它会做非常不同的事情。更改总是关于将安全和“不安全”方法分开,因此用户在需要未初始化的内存时可以非常明确。
现在零填充意味着放弃该决定。
我们可以通过零填充使原始的、有缺陷的Buffer()
API 更安全,同时还推荐更新的、显式的 API。
@seishun为什么你认为这些开发人员不会犯同样的错误,首先触发了新的 Buffer API 的创建?
他们可能会犯那个错误。 但在这种情况下,使用零填充缓冲区的 Node 用户将是安全的,而没有它的 Node 用户则不会。 在这种情况下,我们希望尽可能多的用户进行零填充。
@feross
影响比您或@seishun理解的要严重得多。
我确实理解这种影响。 此外,我从 npm 构建了一个包的依赖关系图,它将版本考虑在内,特别是用于跟踪更新传播。 (这里有一个旧版本,入口点是最新的包版本)。 我还没有建立一个模型来将该图与Buffer()
使用和下载计数相结合,但我计划很快做到这一点,在下一次弃用之前,收集和提交安全问题仍然需要很多时间.
根据当前数据和之前在依赖链中传播包更新的经验,我预计总使用量(每次下载)将在几个月内减少约 90%。 我希望尽快获得更好的估计。
node.js 弃用 Buffer() 的那一天将是每个人在启动 node.js 时看到永久弃用错误的那一天。
我们可以通过零填充使原始的、有缺陷的 Buffer() API 更安全,同时还推荐更新的、显式的 API。
你有没有读过我上面反对的论点? 安全性会因这种变化而_降低_的设置数量不会为零。 如果您不同意该声明 - 请解释。
具有零填充缓冲区的节点用户将是安全的
不,他们不会,因为他们的项目中仍然存在 DoS 漏洞。
不,将它们分开并不是最终目标。 最终目标是修复安全和 DoS 漏洞
这根本不正确。 由于
Buffer()
,Node.js 中从来没有任何安全或拒绝服务漏洞。
对于最终用户来说,漏洞在哪里_无关紧要,没有必要在这里责怪任何人或说“这不是我的问题”。 这里重要的是生态系统安全(或最终用户安全,无论你喜欢怎么称呼它),那里肯定存在问题。 我们仍然有这个问题。
@ChALkeR版本可在此处获得
你的链接失效了。
这不是错误,而是一次性(每次启动)警告。
我应该说“警告”而不是“错误”,但我的观点仍然成立。 几乎所有节点用户都会在启动时看到警告涌入控制台。
可以使用命令行标志抑制该警告。
没关系。 95% 的用户不会抑制警告,维护人员仍然会被之前运行良好的代码问题所淹没。
在很大比例的情况下,该警告会暗示依赖项中某处存在安全问题。 由于您提到的相同原因,现在已隐藏。
如果我们只是将所有缓冲区填零而不是显示警告,那么就不再存在数据泄漏安全问题。
你有没有读过我上面反对的论点? 安全性会因这种变化而降低的设置数量不会为零。 如果您不同意该声明 - 请解释。
我不同意这种说法。
根据我的理解,您的论点是,如果我们提供零填充的缓冲区,那么新代码可能由模块作者编写,他们假设所有 Node 版本都会发生零填充,从而使旧版本用户的安全性降低。
要做到这一点,以下所有事情都需要出错,我认为这不太可能:
1) 开发人员需要使用Buffer(num)
编写新代码,尽管文档中强烈建议不要这样做。
2) 开发人员需要以这样一种方式使用Buffer(num)
,使得缓冲区在暴露给客户端之前不会被新数据完全覆盖。
3) 开发人员需要安装这个新代码并将其发送到生产环境。 (这是非常不可能的,因为 v0.10 或 v0.12 上的用户现在无法真正升级他们的依赖项,而没有一些 ES6 或 ES7 潜入并破坏一切。)
4) 开发人员需要在生产中运行不安全的 Node.js 版本。 (这是一个非常糟糕的主意,任何关心安全性的用户/公司都将使用受支持的版本,以掌握关键的 OpenSSL 修复等。)
5) 攻击者需要识别易受攻击的服务并创建漏洞利用。
安全是一个连续体。 不同的用户愿意承受不同程度的风险。 具有最严格安全要求的用户将运行受支持的版本:0.12、v4 LTS、v6 LTS 或 v7。 这些版本的用户将获得零填充缓冲区,并且比以前更好。
不,他们不会,因为他们的项目中仍然存在 DoS 漏洞。
这两种解决方案都无法修复“DoS 漏洞”。 打印弃用警告并不能阻止 DoS。
此外,将其称为“DoS 漏洞”是不切实际的。 这是一个API 可用性问题,它使得编写有朝一日可能会被 DoS 攻击的代码变得容易。 正则表达式也因同样的问题而臭名昭著——您是否也愿意弃用它们?
对于最终用户来说,漏洞在哪里并不重要,在这里指责任何人或说“这不是我的问题”都没有意义。
实际上,问题的根源在哪里确实很重要。
编写具有极端 DoS 潜力的正则表达式非常容易,例如可以利用(a+)+
或(a|aa)+
使服务器脱机。 防止这种情况发生是 Node 的责任吗? 当然不是! 您不能弃用正则表达式,也不能弃用 npm 上的 1000 个软件包,因为Buffer(large_num)
可能会降低某些站点的速度。
这里重要的是生态系统安全(或最终用户安全,无论你喜欢怎么称呼它),那里肯定存在问题。 我们仍然有这个问题。
是的,我同意这个说法:) 我们都在努力让 (1) 用户更容易做正确的事情,同时 (2) 最大限度地减少对生态系统的破坏。
我们只是不同意在多大程度上破坏生态系统是可以接受的。 Node 不能再进行巨大的向后不兼容的更改。 该网站是WAY大于节点甚至没有重大更改了。
95% 的用户不会抑制警告,维护人员仍然会被之前运行良好的代码问题所淹没。
重要的一点是,如果用户不想被惹恼,他们有这样的选择。 我不确定你所说的“淹没”是什么意思。 据我所知,在没有new
情况下弃用 Buffer 不会导致任何项目收到一个以上关于警告的问题。 我见过的大多数模块也收到了带有修复程序的 PR。
这两种解决方案都无法修复“DoS 漏洞”。 打印弃用警告并不能阻止 DoS。
它确实是间接的。 通过鼓励人们更新他们的代码或他们的依赖项。
这是一个 API 可用性问题,它使得编写有朝一日可能会被 DoS 攻击的代码变得容易。
我们本可以对new Buffer(num)
应用完全相同的逻辑,但是我们没有这样做是有原因的。
正则表达式也因同样的问题而臭名昭著——您是否也愿意弃用它们?
只有当它们的滥用与Buffer
一样普遍,并且可能深入依赖树的内部。
在考虑更多之后,我认为由于构造函数继承,弃用整个构造函数是不可行的。
这是需要新的东西应该打开更多的东西,因为它为我们的用户打开了更多的用例,我认为我们不太可能退缩。
在考虑更多之后,我认为由于构造函数继承,弃用整个构造函数是不可行的。
实际上,如果它是子类,您可以添加检查以防止弃用警告。 见这里。
fwiw, @trevnorris的建议可以简化为... if (new.target && new.target === Buffer) { /** **/ }
@feross
你的链接失效了。
已解决,谢谢。 我忘记了http://
,这很明显 =)。
要做到这一点,以下所有事情都需要出错,我认为这不太可能:
«不太可能» 不会那样工作。 为每个人分配概率,乘以它们,再乘以生态系统规模。 正是庞大的生态系统使得这里的净效应非零。 如果生态系统中有 10 个用户,那么这当然不是问题。
开发人员需要使用 Buffer(num) 编写新代码,尽管文档中强烈建议不要这样做。
他们已经这样做了。 许多用户忽略了文档中的弃用警告。 _p > 0.1_。
开发人员需要以这样一种方式使用 Buffer(num) 缓冲区在暴露给客户端之前不会被新数据完全覆盖。
他们已经这样做了。
开发人员需要安装这个新代码并将其发送到生产环境。 (这是非常不可能的,因为 v0.10 或 v0.12 上的用户现在无法真正升级他们的依赖项,而没有一些 ES6 或 ES7 潜入并破坏一切。)
你为什么只提到 0.10 和 0.12? 想想 v6.9.1、v7.1.0、v4.6.2。 这就是所有当前的节点用户,其中很大一部分用户会在不更新 Node.js 的情况下安装/更新新包。 _p > 0.3(来自受影响的软件包用户)。_
开发人员需要在生产中运行不安全的 Node.js 版本。 (这是一个非常糟糕的主意,任何关心安全性的用户/公司都将使用受支持的版本,以掌握关键的 OpenSSL 修复等。)
不安全? 例如 v7.1.0? v7.1.0 将成为“不安全版本”的事实仅仅是因为您提出的这一更改。
攻击者需要识别易受攻击的服务并创建漏洞利用。
当一个人来到这里并且这成为他们最后的希望时,事情已经很糟糕了。 如果服务易受攻击,这本身就是一个问题,即使它实际上并未受到攻击。 问题之一是,在许多情况下,人们无法确定服务器是否受到威胁。 此外,我估计在这里仅使用 params fuzzler 攻击易受攻击的东西(它接受键入的输入,例如通过 JSON)的速度是很有可能的。
打印弃用警告并不能阻止 DoS。
它确实如此,尽管突出显示了潜在危险的代码并使包从中迁移到更安全的代码。
好吧,让我们弄清楚一些事实:
我觉得我们可以就这些事实的最佳解决方案进行富有成效的讨论。 但是试图一次又一次地反对他们是徒劳的。 它使具有可能有价值的观点的人变得疲倦并离开话题,而且我很确定将话题变成回音室并不符合项目的最佳利益。
那就是:我现在要离开这个线程。 我累了。
我会说,将观点作为事实提出也不是很有成效。 我不确定回应是否有任何意义,但无论如何我都会尽量简洁。
在任何情况下都不会比零填充缓冲区更安全地抛出弃用警告
如前所述,零填充对 DoS 没有帮助,并且某些开发人员依赖它的可能性非零,从而为旧版本节点的用户带来安全问题。
发出弃用警告将对生态系统和维护者都造成破坏
事实上,这会给用户带来烦恼并为维护者工作,但我会保留术语“中断”用于事情中断和停止工作的情况,即完全不同的糟糕程度。 (例如:realpath 惨败、移除 leftpad 等)
旁白: “抛出”弃用警告是用词不当。 如果这是真的,你的程序就会有未处理的异常。 除非您使用标志,否则不会。
如果您跳过了我的第一条评论,请返回阅读。
@feross @mafintosh @substack @sam-github 我没有编写任何流行的模块,所以我没有被 Github 问题“淹没”。 您将编写数百个包,并且似乎认为弃用警告消息会导致世界末日。 为了我们其他人(即@seishun @ChALkeR @Fishrock123)的利益,您能否链接到此弃用所造成的大量问题? 因为我真的很好奇。 像@Fishrock123 这样的评论似乎表明他们不认为弃用警告是“真正的”错误。
我的 2 美分:
DoS 仅意味着在最坏的情况下,您的服务器出现故障。 也许你亏钱了。 我处于这个位置 - 显然,生产中的五个不同的包引入了 RegExp DoS 漏洞,即使我安装了我自己的所有依赖项的最新版本。 任何。
从服务器内存泄露机密? 更糟的是,你有一个主要的
来自所有拥有密码和性取向的用户的诉讼
刚刚公开。
第一个仅仅是可用性问题。 二是隐私问题。
但每个人都只见树木不见森林:
对于最终用户来说,漏洞在哪里并不重要,在这里指责任何人或说“这不是我的问题”都没有意义。
实际上,问题的根源在哪里确实很重要。
^这是Node TSC需要讨论、阐述和解决的讨论。 每个人都想要安全的代码。 但坦率地说,我认为core
在试图保护这里的用户方面做得太过分了。 TSC 需要有一份合同,指定在这种情况下该怎么做。 坦率地说,这个漏洞已经存在很长时间了,所以通过不作为,你基本上选择了“向后兼容”而不是“向前安全”。 但这需要讨论、商定并在某处写成石头。 您需要明确决定安全性方面的压力在哪里。 现在,core 正试图将这一责任推给模块作者(“我们将破坏您现有的代码以激励您修复它”),而模块作者是
试图将其提升到核心上(“只需将缓冲区填充为零”),双方甚至都没有建议安全是用户的责任。 这很好,直到有诉讼,然后我保证用户将因运行过时的、不安全的代码而被追究责任。
@wmhilton探究 github 问题非常耗时,我们的很多问题都来自私人支持渠道,但这里有一些相关的例子:
https://github.com/strongloop/strongloop/issues/296
滚动到最后,您可以看到用户说“它有效”! 您还可以看到我们的 yeoman 生成器输出:
$ slc loopback
(node:36574) fs: re-evaluating native module sources is not supported. If you are using the graceful-fs module, please update it to a more recent version.
loopback deprecated loopback.cookieParser is deprecated. Use `require('cookie-parser');` instead. ../../../../usr/local/lib/node_modules/strongloop/node_modules/loopback-workspace/server/server.js:45:17
_-----_
| | .--------------------------.
|--(o)--| | Let's create a LoopBack |
`---------´ | application! |
( _´U`_ ) '--------------------------'
/___A___\
| ~ |
__'.___.'__
´ ` |° ´ Y `
? What's the name of your application?
哦....我们解决了这个问题。 请注意命令功能完美,它不需要这些功能,但这些弃用没有任何“软”,它们非常面向最终用户,即使它们只能由开发人员操作。
另外, https: //github.com/strongloop/strongloop/issues/200#issuecomment -76652267,这又不是特别具体,但是您可以看到用户转储了一堆可怕的消息,假设这是一个问题,并且我们必须走过去说“好吧,但真的,这一切真的有效吗?”,这很耗时。
我试图找到一个在安装各种工具期间出现的 glob“安全弃用”示例(即使对于不接受 glob 模式的 CLI),但没有在 github 上找到。
^--- 接下来,我们将所有 npm 可怕的输出视为严重错误(而不是“软”消息),因为当出现问题时,客户往往不会像 npm 温和地询问那样提供完整的 npm-debug.log他们,但只是他们看到的第一个可怕输出的屏幕截图,即使更有经验的节点用户会将警告视为建议,而不是与最终的安装失败相关。 与他们来回获取相关信息对每个人来说都是痛苦的,而且不值得付出支持成本。
@wmhilton这个问题的大多数回复的语气太顽固/对抗/咄咄逼人,我无法参与。 我分享了@feross 之前的观点,并支持我原来的回复。 我认为我编写的大约 1/4 模块在某种程度上使用了 Buffer 构造函数,所以这里涉及的任何弃用都会对我产生很大影响,这就是我关心这个问题的原因。 如果您有任何问题,请随时在 IRC / Twitter 上联系我 :)
抱歉@mafintosh ,我会继续参与! :) 所以我说模块开发人员的共识是,将警告打印到控制台不是node
的工作,我说得对吗? 标准输出和标准错误是神圣的,不应该被node
核心使用? (我倾向于同意这一点。)
@sam-github 我认为您可能将node
运行时 api 弃用警告与npm
安装弃用包警告混为一谈。 尽管从用户的体验来看,它们可能在感知上是相似的,但负责 node 和 npm 的开发团队是完全独立的 AFAIK。
现在是凌晨 5 点之后,我已经彻夜未眠,目前正在看一些动漫,同时解决问题。 所以如果我在这个大线程中遗漏了一些东西,请原谅我。
我想首先巩固关于Buffer
构造函数的任何未来更改的两件事。
Buffer
构造函数不能完全删除。 我们可以强制new
,打印警告或保持原样,但不能完全删除它。Buffer
必须接受的最小参数是Uint8Array
也接受的参数。这些是我们可以去的地方的更多事实,而不是请求。 所以我想在这里凝固的主要论点是,如果Buffer
无new
将被弃用,如果这样的话我们将如何做呢?
@trevnorris似乎普遍认为“ES6 类”不足以作为硬弃用的理由。 有一些共识认为出于安全原因的硬弃用是合理的,但这意味着完全弃用Buffer
构造函数(不要删除,请注意),这也有一个很好的副作用,即强弃用Buffer
没有new
(以后可能会被移除)。
@wmhilton您问“弃用如何影响节点模块作者”,还是我误解了? 这是一个节点 semver-major 更改破坏了优雅-fs,并且是节点引入了有关您正在使用的优雅-fs 版本需要如何更新的警告,并且在进行重大更改之前这样做了,以便为开发人员提供提前通知(但最终用户和开发人员看到了该消息)。 如果/当 Buffer 构造函数被弃用时,它将导致一波 npm 模块弃用旧版本,因此虽然弃用消息将来自 npm,但根本原因将在这里,在 node.js 中。
除此之外,还有一个允许 Buffer 扩展的解决方案,我相信它涉及安全外围设备之外。 基本上它会归结为:
function Buffer(arg, encodingOrOffset, length) {
if (typeof new.target === 'function' && new.target !== Buffer) {
// continue w/ the old API
}
// ...
}
我相信允许使用旧的 API,没有警告,足够具体,不会引起安全问题。 因此,在这种情况下唯一的问题是,使用简单的new Buffer()
弃用现有的 API 而不是在扩展时不推荐使用现有的 API 是否会产生足够奇怪的 API 差异。
@sam-github 啊,这是有道理的。 我不知道 Graceful-fs 被弃用的根本原因是 node.js 的更改。 它具有涟漪效应。
@feross现在这是一个有趣的想法。 我想知道有多少构建会因为依赖 linter 传递而中断? 但我认为这是正确的想法。 我们还能让谁参与推广新的 API? 显然是 npmjs。 但甚至可能是流行的博主。 我们可以在 Medium 和 Twitter 上看到“我使用新的 Buffer API 重写了我的模块,你也应该这样做”的趋势。 或者节点安全项目。 这是一个社会工程问题的核心。
@trevnorris几个问题:
Buffer
进行零填充?Buffer(string)
API?@ChALkeR我想我可以回答这些:
@seishun这是 PoC:
'use strict';
const addon = require('./build/Release/addon');
class FastFoo extends Uint8Array { }
FastFoo.prototype.constructor = Foo;
Foo.prototype = FastFoo.prototype;
function Foo(arg) {
if (typeof new.target === 'function' && new.target !== Foo)
return addon.fromString(arg, new.target.prototype);
return new FastFoo(...arguments);
}
Object.setPrototypeOf(Foo, Uint8Array.prototype);
Object.setPrototypeOf(Foo.prototype, Uint8Array.prototype);
Foo.prototype.toString = function toString() {
return Buffer.from(this.buffer).toString();
};
// User's extending class.
class Bar extends Foo {
constructor() {
super(...arguments);
}
toStringHex() {
return Buffer.from(this.buffer).toString('hex');
}
}
const b = new Bar('a');
console.log(b);
console.log(b.toString());
console.log(b.toStringHex());
这是作为本机模块编写的,其中fromString()
是:
void FromString(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
Local<Context> context = isolate->GetCurrentContext();
assert(args[0]->IsString() && "first argument must be a string");
assert(args[1]->IsObject() && "second argument must be a object");
Local<Object> buf = node::Buffer::New(isolate, args[0].As<String>()).ToLocalChecked();
char* data = node::Buffer::Data(buf);
size_t len = node::Buffer::Length(buf);
char* dup = new char[len];
strncpy(dup, data, len);
Local<ArrayBuffer> ab = ArrayBuffer::New(
isolate, dup, len, ArrayBufferCreationMode::kInternalized);
if (dup == nullptr)
ab->Neuter();
Local<Uint8Array> ui = Uint8Array::New(ab, 0, len);
ui->SetPrototype(context, args[1].As<Object>()).IsJust();
args.GetReturnValue().Set(ui);
}
我认为这将允许从Buffer
正确扩展。
@查尔克
Buffer
具有这种功能会有所帮助。 由于这不可能是 ATM b/c 的安全问题,因此不可能,而且 b/c 的情况更专业,我认为允许这样做是合理的。 虽然允许扩展Buffer
的完整参数集可能会让人感到奇怪,但不允许普通构造函数使用。 这是我唯一关心的。@seishun 将此添加到binding.gyp
与这两个文件夹中的相同文件夹中:
{
"targets": [{
"target_name": "addon",
"sources": [ "main.cc" ]
}]
}
还用这个包装本机代码:
#include <v8.h>
#include <node.h>
#include <node_buffer.h>
using namespace v8;
// place c++ code from above here
NODE_MODULE(addon, init)
然后构建:
$ node-gyp rebuild
如果您使用的是节点的自定义构建,请执行以下操作:
$ /path/to/node/node $(which node-gyp) rebuild --nodedir=/path/to/node
让我知道这是否适合您。
@seishun介意你的代码吗?
@seishun我是个白痴。 首先将FromJust
重命名FromString
然后.cc
文件的结尾应该是:
void init(Handle<Object> exports) {
NODE_SET_METHOD(exports, "fromString", FromString);
}
NODE_MODULE(addon, init)
对于那个很抱歉。
@trevnorris它似乎可以工作(尽管您必须将所有 Uint8Array 参数组合的情况添加到 C++ 代码中),但是:
@trevnorris @seishun等等……等等……等等……这实际上是我们正确子类化所需的全部内容:
class ExtensibleBuffer extends Buffer {
constructor(...args) {
super(...args);
Object.setPrototypeOf(this, new.target.prototype);
}
}
?
@seishun
- 您是否 100% 确定它不会有任何边缘情况?
不。 没有明显的问题,但我相信社区会找到一些东西。
- 您知道与常规 ES6 类相比这会对性能产生什么影响吗?
在节点 v6 中,将new FastFoo(...arguments)
切换new FastFoo(args0, args1, args2)
(我确定它在较新的 V8 中更快)表明任何开销都在误差范围内。
- 我记得你提到过你希望简化 Buffer 的内部实现(我完全同意这种观点)。 添加此类 hack 似乎与此相反。
我有两个目标。 一是简化内部实现。 另一种是使 Buffer 可扩展。 由于简化内部结构不是短期内,我认为继续进行并使其可扩展是有益的,这样用户就可以编写他们的代码,期望这些简化发生。 这种变化将是未来的证明。
@addaleax
等等……等等……等等……这实际上是我们正确子类化所需的全部内容:
或多或少。 老实说,直到编写上述示例时我才意识到这一点。
我的建议是:不要管 Buffer。 轻轻地推动人们完全停止使用它。 为 fs.readFile() 和流等核心方法添加编码:'uint8'、'int16'、'arraybuffer' 等,它们提供未修改的 Uint8Array 等对象。
节点有Buffer
的事实是一个历史意外,因为人们需要处理二进制数据,而那时 v8 还没有数组缓冲区对象。 从长远来看,我们应该让所有核心都支持原生数组缓冲区,而将Buffer
作为我们可以逐渐摆脱的遗留 API。 当我们已经有了 Uint8Array 时,我看不到扩展 Uint8Array 的意义。 核心方法可以直接返回普通对象。
因此,为了避免混淆:所有Buffer
对象都已经是Uint8Array
,唯一的区别是扩展原型。
从长远来看,我们应该让所有核心都支持原生数组缓冲区
:+1: Fwiw 我已经开始寻找可以接受通用Uint8Array
作为一个核心模块输入的方法,并计划对其他模块做同样的事情。 本机绑定层甚至看不到Buffer
和Uint8Array
之间的任何区别,因此这在任何地方都运行良好。
关于核心方法的输出,我不确定向核心方法添加更多(伪?)编码是否值得,因为在类型化数组之间进行转换已经很容易了。 另外,我不确定这种变化会对流产生什么影响,所以我至少会放过我自己的手。
@substack AFAIK 我们目前还没有决定
弃用任何东西。 @nodejs/ctc 我可以得到确认吗
2016 年 12 月 26 日星期一下午 4:39 James Halliday通知@ github.com
写道:
升级到 7.2.1。 这已在节点核心中恢复:#9529
https://github.com/nodejs/node/pull/9529
添加新的不会让我们任何地方,因为这将在 8.0 中被弃用—
您收到此消息是因为您所在的团队已被提及。
直接回复本邮件,在GitHub上查看
https://github.com/nodejs/node/issues/9531#issuecomment-269243457或静音
线程
https://github.com/notifications/unsubscribe-auth/AAecVwJeMiUYKxqFp7v_SjtBixD0sYzOks5rMDQtgaJpZM4Kt-70
.
AFAIK 我们目前没有决定弃用任何东西
我不认为我们现在就任何事情达成共识,所以,是的。 基本上。
从更大的角度来看 API。 我希望看到我们支持所有类型化数组和ArrayBuffer
。 一个专门针对后者的地方是当我想一次将一个大文件读入内存时。 如果我可以将ArrayBuffer
传递给fs.read()
这将是可能的。
至于Buffer
会发生什么。 它有很多经过高度优化的方法,以及其他使在 node.js 中处理数据变得更加容易的优点。 所以Buffer
本身不会去任何地方。 我想看到它最终需要使用new
,但如果这是不可能的,那么嗯。 我们可以使用上面的示例代码段来获得class
适当支持。
从安全编程的角度来看,必须决定在 8.0.0 中更改new Buffer()
内容(如果有的话)。
鉴于……呃……缺乏共识,这个决定很可能必须由 CTC 做出。 (还有一个额外的问题,即向后移植到 LTS 线路的任何更改的哪些元素,但这可能不必由 CTC 决定。一旦为 8.0.0 做出决定,LTS 人员可能能够解决这个问题。)
这个问题很长,也有很多外部资源提供信息。 我想至少列出合理的选项(并省略没有人想要的选项)以至少了解需要决定什么可能是件好事。
考虑到这一点,我认为这些基本上是选择:
new Buffer()
new Buffer()
new Buffer()
我错过了任何其他选择吗?
我似乎记得@jasnell有一个想法,即如何在没有性能影响的情况下进行零填充,而这会影响我们以前的做法。
我不知道,但我知道@bnoordhuis曾提到过进行某种形式的按需初始化,其中分配的内存块将根据需要增量或读取为零填充。 然而,我不确定是否有任何努力来调查这个想法的可行性。
在这一点上,我对 7.x 分支中的任何重大更改持 -1 态度。
在 8.x 中,我想为Buffer(n)
和new Buffer(n)
切换到默认零填充。 相关的性能下降可能有助于鼓励开发人员更快地转向新的构造函数。
在 9.x 中,我希望看到new Buffer()
和Buffer()
-without-new 的运行时弃用。 也就是说, Buffer()
-without-new 应该继续工作。
@jasnell你能解释一下为什么你反对 7.0.0 的变化但想要 8.0.0 的变化吗? 不是试图以其他方式说服你或任何事情。 只是好奇你的理由是什么。 7.0.0 来得太早是普遍的感觉吗? 或者有更具体的事情发生吗?
我们距离 8.0.0 仅剩 3 个多月,7.x 不会成为 LTS,我想给生态系统更多的时间来准备。 简而言之,我认为没有理由急于求成。
哇...漫长的一天什么的...我在想 7.x 即将到来,但是呃,是的,显然情况并非如此。 更新我之前对 8.0.0 的选项的评论。
我支持在 8.x 中默认发货零填充。 理想情况下,我们会将默认零填充向后移植到受支持的 LTS 行,以便假设Buffer(n)
零填充编写的新代码将在旧版本的 Node.js 上安全运行。
此外,如果我们默认零填充, Buffer()
构造函数的主要安全问题(信息泄漏)是固定的,所以我不认为 9.x 中的运行时弃用是必要的。
在我的轶事经验中,仅弃用文档已足以阻止新代码的新使用Buffer(n)
。 文档非常非常清楚,应该避免使用Buffer()
。 即使它确实被使用了,我们也有零填充,所以不可能有信息泄漏。
@jasnell如果您支持运行时弃用 Buffer 构造函数,那么您为什么建议在引入零填充后在下一个主要版本中执行此操作? 由于之前讨论的安全问题,不应在运行时弃用之前引入零填充。
此外,引入性能冲击并不是鼓励开发人员转向新构造函数的最佳方式。 对于给定的开发人员,它可能会导致以下两种结果之一:
new Buffer
用法。 我敢打赌,他们更喜欢在升级后立即通知他们性能下降的弃用警告,再加上--trace-deprecation
标志,这将帮助他们找到他们需要更新的确切行。我不想添加到ctc-review
堆中,但我们需要决定 Node.js 8.0.0 中的 Buffer 构造函数会发生什么。 选项似乎是:
与 ES 模块一样,没有一个选项很好,所以我想我们应该提供很多时间。 如果模块讨论是任何指标,那么多个平庸选项的讨论可能需要大量时间来仔细审查所有内容。
添加 ctc-review 标签...@nodejs/ctc
@Trott我再次考虑了这一点,我认为我们不应该受到这三个选项的限制。
SlowBuffer
(#8182),但这引发了另一个问题 - 这些软件包和工具的毫无戒心的用户会收到这些警告,并认为某些东西已损坏,这很可能不是。 在大多数(> 50%)情况下,缓冲区使用情况很好,原始问题来自这样一个事实,即在_some_百分比的情况下它不好,并且整体使用量很大,结合到许多潜在的不安全的地方。 但是每个不同的情况都有可能好起来,用户不希望收到这些警告的垃圾邮件。Buffer(10).fill(0)
这将使 _some_ 用户将其重写为Buffer(10)
。那么,这个怎么样:在 9.0(或更高版本)中针对运行时弃用(尽可能多地),但在 8.0 版本中宣布,在 8.0 中引入一个新标志,例如--buffer-deprecation
将尽早启用该弃用以便_想要_看到这些警告的用户会收到这些警告并将问题报告给受影响的软件包,让他们有 6 个月的时间可以毫不犹豫地进行迁移。
想法?
我喜欢选择加入标志的想法。 这看起来像是来自@jasnell的--deprectate-soon
提议,目前已关闭。
我不认为旗帜会有任何好处。
很少有人会知道它的存在,很少有人会关心启用它。
这里的关键部分是 8.0.0 发行说明中的一个单独部分,该部分指出旧方法将很快在运行时弃用并敦促停止使用这些方法,解释其背景,并提供标志作为帮助工具来查找在实际运行时弃用出现之前的代码开始影响所有用户。
我希望一些构建在受影响库上的用户使用该标志运行他们的代码并向受影响的库报告问题。 这通常有效。
很明显,一些模块作者并不关心仅弃用文档并拒绝修复此类使用的 PR。
我们不需要在运行时弃用之前涵盖所有情况,我们只需要显着减少此类运行时警告对毫无戒心的用户的影响,并给模块作者时间进行迁移。
这里的关键部分是 8.0.0 发行说明中的一个单独部分,该部分指出旧方法将很快在运行时弃用并敦促停止使用这些方法,并解释了其背景
6.0.0 发行说明已经提到不推荐使用构造函数,文档详细解释了原因。 如果发行说明警告说将在 6 个月内发出警告,并且提供了一个工具来查找将导致警告的代码,这会产生巨大的不同吗? 也许吧,但对我来说似乎不太可能。 人们通常不关心他们的代码在未来很久会发生什么,特别是如果它只是一个警告。
即使大多数用户不关心它,我认为这样的标志也会有好处。 我个人会在我的应用程序中使用它来发现使用不推荐使用的 API 的库并打开有关它的问题。
我同意这将是有益的,尽管相关的 PR 没有得到太多的赞扬。
我不同意运行时弃用应该推迟,希望标志会有所作为。
我认为我提议的 PR 的问题主要是围绕标志的命名。 总体概念似乎得到了支持,我可以通过解决这些缺点的新 PR 轻松地重新审视它。 也就是说,在Buffer
的情况下,我的想法是我们应该更积极地对待它。 对于那些真的不想被这些事情打扰的人,我们现在有必要时静音警告的机制(环境变量, --redirect-warnings
和--no-warnings
标志)。 这就是为什么我们有一个弃用过程开始。
这个问题继续绕圈子。
出于前面讨论的所有原因,不推荐使用运行时并不是一个好主意。 我们甚至在 v7 行的中间回滚了运行时弃用,因为它太具有破坏性了。 在再次尝试完全相同的事情之前,我们可以讨论一下为什么我们认为这次会有所不同吗?
@ChALkeR您继续重复零填充缓冲区会导致新的安全问题的想法,但您没有在此评论中回应我的建议:
我支持在 8.x 中默认发货零填充。 理想情况下,我们会将默认零填充向后移植到受支持的 LTS 行,以便假设
Buffer(n)
零填充编写的新代码将在旧版本的 Node.js 上安全运行。
此外,如果我们默认零填充,
Buffer()
构造函数的主要安全问题(信息泄漏)是固定的,所以我不认为 9.x 中的运行时弃用是必要的。
添加零填充并将其向后移植到所有 LTS 版本不会导致生态系统中断,也不会引入新的安全问题。
我们甚至在 v7 行的中间回滚了运行时弃用,因为它太具有破坏性了。 在再次尝试完全相同的事情之前,我们可以讨论一下为什么我们认为这次会有所不同吗?
不,它没有恢复,因为它太具有破坏性。 它被退回,因为它没有足够的理由。 这一次,会的。
@ChALkeR您继续重复零填充缓冲区会导致新的安全问题的想法,但您没有在此评论中回应我的建议:
不过,他有。 https://github.com/nodejs/node/issues/9531#issuecomment -262003359
@seishun , @jasnell我将支持 8.0 的运行时弃用,因为我们不会再次将其恢复到任何地方。 但我不认为它会被每个人接受,我认为它可能会对最终用户 atm 造成太大的干扰(我们之所以要恢复甚至不使用缓冲区的新运行时弃用,这只是只是我们实际上应该弃用的一部分)。
@seishun不,它没有恢复,因为它太具有破坏性。 它被退回,因为它没有足够的理由。 这一次,会的。
“证明”一个巨大的重大变化不会使所说的重大变化对生态系统的破坏性降低。 Web 比 Node.js 大得多,它以某种方式设法避免进行重大更改。 Node.js 可以也应该这样做。
这个线程很长,我有点迷茫,所以我有几个问题。
@mikeal ...正如已经指出的那样,如果模块开始依赖于假设零填充是默认设置,那么默认情况下的零填充
就其价值而言, standard
的下一个版本会将已弃用的Buffer()
构造函数的使用视为错误。 我确信 Airbnb 和 Google 等其他流行风格指南的维护者会被说服添加相同的规则。 这样,模块开发人员就会收到有关该问题的警告,而不会惹恼那些甚至无法修复它的普通用户。
如果模块开始依赖于假设零填充是默认设置,默认情况下的零填充会给旧版本的用户带来风险
是的,但您是否不考虑为这些用户完全破坏此 API? 如果替代方案无论如何都会破坏他们,那么他们在这里可能依赖的任何行为都是无关紧要的。
是的,但您是否不考虑为这些用户完全破坏此 API?
不,运行时弃用不会破坏任何内容。 首次使用时会显示警告。
谁甚至不能做任何事情来解决它。
他们可以提交问题或 PR 或设置NODE_NO_WARNINGS=1
。
不,运行时弃用不会破坏任何内容。 首次使用时会显示警告。
将意外的文本注入 Node CLI 程序的 stderr 肯定会破坏事情。 假装是不负责任的。
他们可以提交问题或 PR 或设置 NODE_NO_WARNINGS=1。
如果弃用的用法在深度嵌套的依赖项中,那么 PR 可能还需要提交到依赖项链中的每个包,一直到顶级包。
包依赖于落后一两个主要版本的包是很常见的。 在这种情况下,可能需要进行大的重构才能更新到最新的依赖项并使警告消失。
而且, NODE_NO_WARNINGS=1
对于最终用户来说几乎不是一个现实的解决方案。
将意外的文本注入 Node CLI 程序的 stderr 肯定会破坏事情。 假装是不负责任的。
是的,它在某些情况下可能会导致损坏(就像任何其他弃用警告一样),但我认为这不是@mikeal所说的“完全破坏此 API”。
而且,NODE_NO_WARNINGS=1 对于最终用户来说几乎不是一个现实的解决方案。
为什么不? 你能澄清一下你所说的“最终用户”吗?
我认为这是问题的一部分:
“弃用没什么大不了的”:
“弃用将极具破坏性”:
弃用拥护者根本不了解实际成本。
我们都以不同的方式为 Node 做出贡献。 我并不是暗示一种贡献比另一种更有价值。 但是,对于这种变化可能给社区的其他成员造成的真正痛苦,我会很感激,而不是不断地忽视我们的担忧。
绝对确定:我从未说过弃用“没什么大不了”,所以请不要以这种方式描述我的立场。 对我来说,这不是要提出一个好的解决方案,而是要围绕最不坏的方法达成协议,该方法将有效地将生态系统朝着我们需要的方向发展,而不会产生额外的风险。
绝对确定:我从未说过弃用“没什么大不了”,所以请不要以这种方式描述我的立场。
当然。 这些类别可能更好地命名为“弃用倡导者”和“弃用批评者”。
为了就权衡进行生产对话,我们需要了解一些事情。
我认为我们在这里遇到的问题是有些人认为这种中断是一个灰色区域,它实际上不像“真正的”中断变化那样极端。 那不是真的,我们不能假装这是真的。
这场谈话中的许多人都明白这一点,并且可能仍然认为这次休息是值得的。 从那时起,我们可以就权衡进行富有成效的对话。
我们不能做的是继续争论这种弃用的严重性。 这是一个突破性的变化,它破坏了生态系统中的一堆东西,它会阻止人们升级,如果这仍然是人们认为值得的事情,我们需要承认并适当计划。
这是关于就最不坏的方法达成一致,这种方法将有效地将生态系统朝着我们需要的方向发展,而不会产生额外的风险。
同意。
我认为@feross和其他一些人所说的是,由于运行时弃用而导致的中断实际上大于我们将要弃用的 API 填充为零时所看到的中断。 两者都是突破性的变化,但其中一个更具前瞻性,而且破坏性可能更小。
当然。 这些类别可能更好地命名为“弃用倡导者”和“弃用批评者”。
公平地说,每个人都提倡对 API 进行重大更改。
如果我们默认采用零填充的方法,那么我们需要看到生态系统方面的共同努力,以帮助保护用户免受相关风险的影响。 例如,默认情况下假设为零填充的模块可能希望主动检查它们是否在默认情况下进行零填充的 Node.js 版本上运行,并在不满足该要求时相应地警告用户。 版本嗅探很糟糕,但这将是确定用户没有面临风险的唯一方法。
积极的 linting 也会有帮助。 我曾建议 ESlint 添加一条关于使用新构造方法的规则,但他们拒绝了,因为该规则非常特定于 Node.js。 在这方面获得一些牵引力会很好,我很高兴看到standard
正在朝着这个方向发展。
最终,我希望在使用Buffer()
或new Buffer()
时看到运行时弃用,但只要我们在这里看到正确方向的积极进展,就不需要很快。
如果我们默认采用零填充的方法,那么我们需要看到生态系统方面的共同努力,以帮助保护用户免受相关风险的影响
所以,我们让@feross承诺向standardjs 添加规则。 我们还可以联系 AirBnB,将其添加到他们广泛采用的样式指南和工具中。
我们可以联系 Visual Studio Code 和 Atom,看看他们是否可以向其编辑器的默认 Node.js 支持添加类似的警告。
我们还可以联系 Snyk,看看他们是否可以在代码甚至依赖项做同样的事情时添加警告。
在这一点上,对于 stderr 的意外打印是一个突破性的变化,这是一个陈旧的论点。
没有人对此争论不休,但 IMO 可能由额外的 strerr 输出引起的实际损坏是运行时弃用的一个相对较小的痛点,而不是人们抱怨的那个。 人们最关心的是删除警告所需的工作量(在大多数情况下会引起烦恼,而不是破坏),我承认这一点。
如果这是唯一的解决方案,那么该更改就是破坏性更改。 它应该被视为破坏了所有依赖于行为的模块以及依赖于这些模块的所有模块等等。
在大多数情况下,简单地忽略警告也是一种解决方案。 在大多数情况下,代码将继续按预期工作。
@seishun ...不要忘记有很多用户使用--throw-deprecation
。 对于那些,新的弃用警告是一个新的运行时错误。 我们目前无法确定存在多少此类用户。
例如,默认情况下假设为零填充的模块可能希望主动检查它们是否在默认情况下进行零填充的 Node.js 版本上运行,
我认为这行不通,我们也没有办法在运行时检查它。
一个问题:如果我们默认启用零填充,您如何估计_将_假设零填充但_不会_检查 Node.js 版本的库作者的百分比?
另请注意,与仅使用可用的新 API 相比,这种组合并不更好,并且不会涉及更少的代码。
@mikeal , @jasnell re:
每个人,还请注意一个事实,即零填充并不能解决非常流行的Buffer(arg).toString('base64')
问题(它仍然是一个 DoS),而弃用则可以。
@ChALkeR ...是的。 我倾向于认为它可以更快地撕掉创可贴。 但是,我们不能低估在生态系统中进行大量投资的模块开发人员的数量,他们告诉我们运行时弃用会很糟糕,因此我试图找出折衷方案。 如果我们走生态开发者要求我们走的路,那么这些生态开发者就必须承担一定的责任来解决问题。 如果我们采取了这一行动,但我们没有看到可衡量的改进,那么使用运行时弃用绝对是我们可以采取的一个步骤来帮助解决问题。 您说得对,我们无法合理估计默认情况下切换到零填充所带来的风险。
同样,我在这里没有看到好的解决方案。 我在每个选项中都看到了糟糕的程度。 根据二元的“Will Work”或“Won't Work”来做出决定不会非常有效。
@mikeal , @jasnell re:
我认为在工具中使用这些类型的弃用可能比运行时弃用更有效。 有趣的是,不会使用任何工具进行 linting 或安全的人很可能只是在没有 dep 警告的情况下运行或忽略它们。 对于使用此工具的人来说,这实际上是一个更大的错误,他们必须更改代码才能通过,而不是运行时警告。
@mikeal这样的工具是否有助于在依赖项中找到已弃用的 API 的用法? 因为据我所知,在您自己的代码中修复它是微不足道的。 它正在确定需要更新哪些依赖项并适应其中需要最多工作的任何重大更改。
@jasnell这不是二进制的,我只是将事情简化了一点,使其更适合评论。
对单个随机模块开发人员仅考虑新版本并依赖零填充的概率进行估计,乘以该开发人员不关心版本检测的可能性,然后乘以生态系统规模(好吧,使用 Buffer API 的部分,但这仍然_非常_巨大)。 如果我们选择零填充路径,我希望看到许多模块在未启用零填充的版本上开始做坏事。
此外,未解决的DoS。
这基本上就是我说它“行不通”的原因。
像推动更多 linter 警告这样的方法很好,但问题不在于作为模块发布的新代码依赖于一个两年内没有更新的小模块,因此没有人查看代码。
我能想到的唯一真正处理它的方法是在 npm、grep(或解析 AST,如果有人愿意的话)中获取每个模块的转储,以便在所有模块和毯子中使用 Buffer 构造函数的任何匹配用途联系所有模块作者。
我刚刚发布了standard
10.0.0-beta.0,其中包括对已弃用的 Node.js API 的检查,包括Buffer()
构造函数的使用。 详情请见: https :
最终的 10.0.0 版本将从今天起一个月后发布,也就是在我们通常的一个月测试期之后的 2017 年 4 月 1 日。 你可以在这里关注发布进度: https :
注意:我比平时更早地推出了这个版本。 我渴望尝试这种使用社区工具的方法来阻止弃用的 API,远远早于 Node 实际硬弃用这些 API。 让我们看看这是怎么回事。
@ChALkeR您如何监控已弃用 API 的使用情况? 您能否让我们了解在接下来的几个月中,野外使用情况的变化情况?
大家好! @seishun邀请我加入一个真实世界的用例,用于扩展我最近遇到的 Buffer。 EthereumJS 项目 (https://github.com/ethereumjs) 使用 Buffer 对通过网络发送和接收的值进行编码。 然而,以太坊 RPC 协议需要一种特定类型的 HEX 编码,不同于 Buffer (https://github.com/ethereum/wiki/wiki/JSON-RPC#hex-value-encoding) 使用的编码。 解决这种不一致的一种方法是扩展 Buffer 类并覆盖toString
方法以返回预期的 HEX 值。 这样一来,严重依赖 Buffer 的项目就不需要使用替代的 Buffer 实现来重写。
示例代码:
module.exports = class EthBuffer extends Buffer {
constructor(value, encoding = 'utf8', type = 'unformatted') {
if (arguments.length === 2 && typeof value === 'string') {
super(value, encoding)
} else {
super(value)
}
if (arguments.length === 2 && typeof value !== 'string') {
type = encoding
}
this._type = type
}
toString() {
const args = Array.prototype.slice.call(arguments)
const val = super.toString.apply(this, args)
if (args[0] === 'hex' && this._type === 'quantity' && val[0] === '0') {
return val.substring(1)
} else {
return val
}
}
}
我希望这对你们决定如何进行 Buffer 重构有所帮助! 祝你好运!
@ChALkeR @seishun @nodejs/ctc 你如何看待零填充的变体,它没有人们依赖它的危险? 例如,我们可以在进程启动时选择一个伪随机 uint8,用于填充所有缓冲区。 (或者可能是uv_now(loop)
的较低位,如果这样更好的话。你明白了。)
我知道这听起来很奇怪,但它:
这不会解决的唯一问题是 DoS 问题。
“轻微”的性能损失? 你测量过这个@addaleax吗?
@addaleax仍然存在我在https://github.com/nodejs/node/issues/9531#issuecomment -273560353 中描述的相同问题。 基本上,性能损失对某些人来说可能很重要,他们将不得不花时间调查它的来源。
“轻微”的性能损失? 你测量过这个@addaleax吗?
不明确。 如果它的基准测试与Buffer.alloc
与Buffer.allocUnsafe
非常不同,那将会很奇怪。 所以:是的,如果你只看Buffer()
调用本身,它肯定会引人注目。
基本上,性能损失对某些人来说可能很重要,他们将不得不花时间调查它的来源。
我们会将我们所做的任何更改放入我们的发行说明中,所以我不会对此感到太糟糕。
我提出这个问题是因为似乎零填充实际上是很多 CTC 成员都会参与的事情,除非我遗漏了一些东西,否则这种填充将是比零填充更好的选择.
我们会将我们所做的任何更改放入我们的发行说明中,所以我不会对此感到太糟糕。
不过,并不是每个人都阅读发行说明。
除非我遗漏了一些东西,否则这种填充将是比零填充更好的选择。
我怀疑在许多平台上,零填充比垃圾填充更快。
我喜欢@addaleax的方法,因为它是迄今为止提出的破坏性最小的方法(除了“什么都不做”),同时仍然实现我们都试图解决的主要目标。
就像@jasnell说的:
我在这里没有看到好的解决方案。 我在每个选项中都看到了糟糕的程度。 根据二元的“Will Work”或“Won't Work”来做出决定不会非常有效。
我们应该衡量真正的性能影响以获得完整的画面,但是如果这种方法的最坏的缺点是对已弃用的 API 的性能影响(可以通过将Buffer()
更改Buffer.from()
来修复)大多数情况下)那么这对我来说肯定是“最不坏”的选择。
因为这是迄今为止提出的破坏性最小的方案
我不确定在这种情况下使用的“破坏性”的定义,但假设它意味着“事情没有按预期方式工作导致生产力降低”,那么引入性能损失也是破坏性的,我不相信依赖new Buffer()
高性能的人数明显低于依赖stderr输出的人数。
在其他条件相同的情况下,我认为明显的中断比隐藏在发行说明中的中断更可取。
注意:我不会忽略与弃用警告无关的非标准相关问题。 我只是不同意使用“中断”一词来描述这些。
我同意@seishun。
受到new Buffer()
10%(或任何百分比)性能变化影响的用户类型可能与跳过阅读发行说明的用户类型不同。 事实上,如果这两组之间有任何重叠,我会感到惊讶。
任何运行性能关键型应用程序的人不仅会阅读发行说明,还会在生产中部署新版本的 Node.js 之前测试他们的应用程序及其性能。
但是,是的,我承认即使这种方法也不是完美的。 尽管如此,我相信这是迄今为止提出的破坏性最小的方案。
仅供参考: https :
无论我们在这里做什么,都会有中断。 这是无法避免的。 问题是哪种中断是最可接受的(即“最不坏”)。 @addaleax建议填充随机选择的值是一个非常好的选择。 当然,可能有些人不阅读发行说明,有些人不阅读文档,有些人不阅读推文等等……我们无能为力对于那些人以外的人来说,不会让情况变得更糟。 我们肯定需要衡量性能影响,但Buffer.allocUnsafe()
是解决方案。
当然,它并不能解决所有问题,因此还有更多工作要做。
@addaleax抱歉,这行不通,它与零填充没有太大区别。 当我说“作者将开始依赖零填充”时,我的意思不是他们会开始依赖缓冲区来填充零,而是他们会开始依赖Buffer(num)
是安全的(当num
不是太大)。 请参阅下面的详细信息。
那里有(粗略)三种类型的有问题的包:
Buffer(num)
和Buffer(string|array)
用法_and_在那里接受用户输入。示例: ws
。
有关详细信息,请参阅https://github.com/websockets/ws/releases/tag/1.0.1 (尽管ws
不是那里唯一的包)。
两种类型的问题:
与下面描述的其他问题相比,这些类型的包相对较少。
Buffer(num)
,但在某些情况下未填写的。有关易受攻击的代码示例,请参阅https://github.com/ChALkeR/notes/blob/master/Buffer-knows-everything.md#how -to-avoid-leaks。
示例: ip
。
在https://github.com/indutny/node-ip/commit/b2b4469255a624619bda71e52fd1f05dc0dd621f之前, ip.mask('::1', '0.0.0.0')
返回未初始化的缓冲内存。
我不能确切地说出这有多常见,但这绝对没有下面的示例 3 常见。
没有大数字可以进入该包中的Buffer(number)
,因此 DoS 在那里是不可能的。
zerofill/randomfill 会修复它,_这就是这里的问题_。
问题是在 zerofill/randomfill 下,该代码是安全的,并且在所有当前的 Node.js 版本上,包括 6.10.0 和 7.7.2,它都不安全。 一旦人们开始依赖泄漏Buffer(small_number)
结果作为安全问题(有些人会立即这样做),对于没有更新到最新补丁版本的用户来说,情况会变得更糟。
此外,在 zerofill 和 randomfill 中的性能下降可能很明显,这是这里的另一个问题:您不能依赖所有用户仔细阅读更改日志。 一旦我们在不弃用的情况下启用零/随机填充,我希望发生的事情是一些用户会更新到最新的补丁版本,看到性能下降,不要费心去挖掘,而是假设这是一个临时错误并回滚到未打补丁的版本。 这会变得更糟。
Buffer(num)
和Buffer(string|array)
的那些,但不直接处理用户输入。示例: hoek
。
参见https://github.com/hapijs/hoek/issues/177 ,在[email protected]
修复。
模块作者通常说这不是他们的问题,并且不支持将number
传递给他们的 API(尽管他们不进行类型检查)。
其中有很多。
那些增加了这个问题的范围并使其更难追踪——如果任何用户输入最终被传递到某个其他包或一些未发布的服务器代码中的此类 API,它将变得与情况 1 相同——至少是一个 DoS并且可能是一个单元化的内存泄漏(如果有远程用户,他们通常有一些观察结果的方法)。
zero/randomfill 不会解决这里的 DoS(与情况 1 几乎相同),但它会使模块作者更不愿意修复他们的包,一旦他们听说问题在 Node.js 中以某种方式“修复” (虽然它实际上不会)。 如果您认为他们不会——请注意,即使是这里的讨论也经常完全忽略 DoS。
他们唯一能让事情变得安全、修复用户代码中未初始化的缓冲区内存泄漏和 DoS 问题的方法是将生态系统中的所有内容迁移到Buffer.alloc/Buffer.from
,我认为没有任何方法可以解决这个问题。 -学期。
@jasnell
无论我们在这里做什么,都会有中断。
为什么颠覆不可避免? 在我看来,如果只弃用文档,并且通过更好地记录各种 Buffer API 被弃用的原因,就不会有中断。
目前,用户必须跳到 Buffer 文档的顶部才能了解为什么这些 API 被弃用,并且每个弃用函数的文档都没有链接到该部分以及进一步的解释。
添加这些 API 被弃用的原因_在记录它们的同一部分_可以帮助传达这些 API 存在安全问题以及其他问题的信息。
@ChALkeR DoS,攻击者可以简单地用几个包杀死服务器。 这不会通过零/随机填充来解决。
我们需要停止提出 DoS。 它超出了范围并且与未初始化的内存泄露问题完全无关。
我们无法为决心伤害自己的用户解决安全问题。 根据您的逻辑, Buffer.alloc(num)
也容易受到 DoS 攻击,因为如果用户不检查num
的大小,远程攻击者可能会触发 DoS。 这是超出范围的,而不是 Node 需要担心的工作。
如果我们想在这里取得进展,我们需要同意专注于如何防止内存泄露问题。 那个问题,而不是 DoS 问题,是最初促使新的Buffer
API 的原因,它是将用户从Buffer()
迁移到这些新 API 的_唯一_正当理由。
@ChALkeR一旦人们开始依赖泄漏的 Buffer(small_number) 结果不是安全问题
没有理性的用户会将返回的随机数据视为“不是问题”。 与返回归零缓冲区不同,返回随机缓冲区_看起来非常非常奇怪_。 在这种情况下,唯一的解决方案是让用户手动将其清零或使用fill(0)
。
我认为你所描述的场景变得越来越荒谬和不切实际,以至于我现在实际上更有信心,这毕竟是正确的解决方案。
当我说“作者将开始依赖零填充”时,我的意思不是他们会开始依赖缓冲区来填充零,而是他们会开始依赖
Buffer(num)
是安全的(当num
不是太大)。
@ChALkeR我明白你的意思,但我认为事实并非如此。 对于代码作者来说,依赖Buffer(num)
是安全的意味着他们知道 Buffer 构造函数的问题,所以我们不需要担心这些人。
他们唯一能让事情变得安全、修复用户代码中未初始化的缓冲区内存泄漏和 DoS 问题的方法是将生态系统中的所有内容迁移到
Buffer.alloc/Buffer.from
,我认为没有任何方法可以解决这个问题-学期。
是的,恐怕这是我们必须接受的事情不会发生。
我们无法为决心伤害自己的用户解决安全问题。 根据您的逻辑,Buffer.alloc(num) 也容易受到 DoS 攻击,因为如果用户不检查 num 的大小,远程攻击者可能会触发 DoS。 这是超出范围的,而不是 Node 需要担心的工作。
您似乎误解了 DoS 问题。 当用户和new Buffer()
之间有一系列调用时,就会发生这种情况,没有人验证输入,因为他们假设其他人会验证输入。 因此,可以调用new Buffer(num)
而不是new Buffer(string)
。 Buffer.alloc()
不会发生这种情况。
我认为你所描述的场景变得越来越荒谬和不切实际,以至于我现在实际上更有信心,这毕竟是正确的解决方案。
我们理解您不同意@ChALkeR ,但此类言论具有贬低和
让我们一定要尽量保持对话的文明和建设性。 在这一点上,似乎没有太多令人信服的事情发生,只是彼此之间有很多交谈。 我们需要在走到一起什么是朝着解决方案前进的路径。 我们不会在一夜之间解决问题,也不会一步解决。
这个问题的三个组成部分需要协调一致才能找到解决方案:核心、用户和工具生态系统。 有成千上万的模块使用Buffer()
和new Buffer()
。 @addaleax是绝对正确的,无论我们在这里选择做什么,我们都不能简单地让这些模块更改它们的代码。 即使我们简单地将Buffer
完全去掉,这并不意味着这些模块作者会更改他们的代码。 @feross也是正确的,因为我们无法为这些开发人员“解决”安全问题。 他们必须对自己承担一定的责任。
也就是说, @seishun和@ChALkeR也是正确的,因为这些问题不会简单地消失,也不能简单地被忽略。 工具生态系统可以而且将会在这方面提供显着帮助。 对standard
、linting 规则、安全漏洞审计和教育的改变将会有很长的路要走,但肯定会很慢。 我们会到达那里。 @ChALkeR指出,当我们在其中收到弃用警告时, Buffer()
-without-new 的使用量显着下降。 这是积极的。 嘈杂的工作,但它也可能适得其反。
我现在想要做的是找到一个折衷的解决方案,我知道它不会让每个人都高兴或立即解决所有问题,但有望使事情朝着正确的方向发展。
我们修改Buffer(num)
和new Buffer(num)
以填充 v8.0.0 启动时选择的随机字节值。
我们引入了一个可选的 * 弃用警告,它在 v8.0.0 中Buffer()
或new Buffer()
构造函数时会发出此警告。
我们在 v9.0.0 中将弃用警告切换为默认开启
我们继续与工具创建者和教育提供者合作,宣传使用新的缓冲区构建方法。
我们继续与生态系统合作,主动提交拉取请求以替换生态系统中Buffer()
和new Buffer()
的已发现用途。
同样,我知道这个计划不会立即解决问题,而且我知道它引入了可能会破坏的烦人的 strerr 输出。 我不是要解决所有问题,也不是要让每个人都开心。 我正在努力寻找可行的解决方案。
@seishun您似乎误解了 DoS 问题。
感谢您向我解释我发现并报告的问题。 🙄
当用户和 new Buffer() 之间存在一系列调用时,就会发生这种情况,其中没有人验证输入,因为他们假设其他人会验证输入。 因此,可以调用 new Buffer(num) 而不是 new Buffer(string)。 这不会发生在 Buffer.alloc() 中。
是的, Buffer(num)
和Buffer(string)
之间的混淆实际上是我们创建新 API 的原因,但_它与 DoS_ 无关。 你在事后编造理由。
如果您返回并阅读原始问题,您会发现新 API 的基本原理是_未初始化的内存泄露_,这个问题至少比 DoS 严重 100 倍。
如果我们可以防止未初始化的内存泄露,正如@addaleax的提议所做的那样,那么我们首先解决了促使创建这些新的Buffer
API 的原始问题。 这将使我们将Buffer()
保留更长时间,让生态系统有更多时间进行迁移。
@jasnell看起来我们同时发表了评论。 我可以支持你的提议,尽管我认为它仍然过于激进地弃用Buffer
。 v9 是大约 8 个月之后。
随着随机字节填充解决了内存泄露问题,弃用的紧迫性是什么? 我认为我们可以等待更长时间,让生态系统有更多时间进行迁移。
那么也许这样:在 10 月份发布 9.0.0 之前,CTC 可以审查将生态系统从Buffer()
和new Buffer()
迁移的进度。 CTC 可以决定是否启用弃用消息。 然而,到明年 10.0.0,弃用警告肯定会默认开启。
@jasnell让 Node 核心 API 成为移动目标是疏远为平台提供价值的模块作者的绝佳方式。 不要弃用原语。 曾经。
@jasnell ,这与我通过用随机数https://github.com/nodejs/node/issues/9531#issuecomment -285625233 中所述的原因,我仍然不确定。 此外,如果它登陆 8.0,LTS 会发生什么? 它不会得到随机填充吗?
请注意, @addaleax表达了一个观点(在表格中,如果我理解正确的话),如果在不久的将来(即一年不够)的任何时候弃用,我们将承受太大的压力,将不得不恢复。
不过,我个人并不同意这一点。
@feross不,您个人忽略 DoS 问题的事实并没有让它消失——我预计大量设置容易受到 DoS 的攻击,因为依赖链深处的某个包中的Buffer(num)/Buffer(arg)
混淆. 是的,这种混淆是您报告的问题,但您没有提到 DoS 的事实并不意味着它超出了范围。
顺便说一句,我不确定你如何反对https://github.com/nodejs/node/issues/9531#issuecomment -283295518 / https://github.com/nodejs/node/issues/9531#issuecomment -283246696但赞成https://github.com/nodejs/node/issues/9531#issuecomment -285772835 大体相同(除了 8.0 中的 randomfill)。 或者我误解了什么?
至于“越来越荒谬,如此不切实际”——我确实在这里承认你的表达语言(这里不是开玩笑,它也是一个输入源),但如果你引用你不同意的确切陈述(最好是确切的部分)会更有帮助和。
是的,Buffer(num) 和 Buffer(string) 之间的混淆实际上是我们创建新 API 的原因,但它与 DoS 无关。 你在事后编造理由。
除了上面@ChALkeR的评论之外,我还想指出https://github.com/ChALkeR/notes/blob/master/Lets-fix-Buffer-API.md 中提到的 DoS _was_,这是新 API 的主要基础。
看来你忽略了我的和@jasnell对更文明态度的要求。
我认为我们可以等待更长时间,让生态系统有更多时间进行迁移。
如果有证据表明生态系统中new Buffer()
的使用量会自行减少而不会弃用运行时,我会同意。 到目前为止,我没有看到它,linting 不会让人们更新他们的依赖项,而且我真的怀疑很多人会愿意启用可选警告。 不过,我很乐意被证明是错误的,我认为这是值得讨论的事情。
我的建议:不要弃用new Buffer(n)
或Buffer(n)
。 不要将其更改为 ES 类。 默认情况下启用零填充。
弃用是一项重大成本。
如果目标是让社区更安全,零填充就可以满足这一点。
如果目标是推动社区向更快的 API 方向发展,零填充满足了关心的人的需求,不关心的人不应该被迫关心它。
如果目标是使Buffer
更具可扩展性,那么它已经具有足够的可扩展性,而且我看不出“可以对缓冲区进行子类化”如何使 Node.js 成为更好的平台。 事实上,这可能是个坏主意。 API 应该温和地传达 Buffer _可能不应该被子类化_,并将其作为工厂函数来完成。
但是,任何_想要_对其进行子类化的人今天都可以使用 Node 7,而且它已经足够老套了,他们很可能会避免使用它。
如果目标是警告人们不要创建一个非常大的缓冲区,它可以在Buffer(n)
被调用时打印一个警告,并使用适当的大数字,以便他们可以在出现问题时对其进行调试。 这比内存泄露要好得多。
如果目标是让生态系统中的人们停止使用new Buffer()
,那么......为什么?
@jasnell ,再想想, https: //github.com/nodejs/node/issues/9531#issuecomment -285772835 对我来说看起来不错。 如果我们有一个具体的弃用路径,随机填充是可以接受的。 不过,我仍然不确定它是否应该向后移植——你的计划不包括向后移植。 也许这是最好的——这样,“用户开始依赖随机填充”问题的影响被减少到可能的最小值,8.0 将不会出现未初始化的内存泄漏。
尽管如此,它仍然不可忽视——即使是现在,模块作者通常也不会将https://github.com/nodejs/node/issues/9531#issuecomment -285625233 中的案例 3 视为模块中的问题。
很抱歉上个月左右没有参加这个讨论。 有人可以澄清我在“Node.js Buffer options”电子表格中显示的所有“runtime deprecation”选项是否意味着完全弃用Buffer
构造函数?
我已经表达了在过去的这几次,但我的观点是,如果任何改变都发生(仅零填充之外),那么Buffer
参数应该成为相同或超集Uint8Array
。 new
的要求也很重要。 这是否反映在任何这些选项中?
重申一下, new Buffer(number)
和类似的东西永远不应该在运行时被弃用。
如果目标是使
Buffer
更具可扩展性,那么它已经具有足够的可扩展性,而且我看不出“缓冲区可以被子类化”如何使 Node.js 成为一个更好的平台。 事实上,这可能是个坏主意。 API 应该温和地传达 Buffer _可能不应该被子类化_,并将其作为工厂函数来完成。
这是@isaacs的强烈观点,但我没有看到任何理由或支持。 过去,我扩展了Buffer
的附加功能,使 API 更易于使用。 关键是能够在实例上调用函数,同时还能够通过数组索引访问其数据。 这很难复制,如果我可以简单地使用class Foo extends Buffer
事情会容易得多。
有人可以澄清我在“Node.js Buffer options”电子表格中显示的所有“runtime deprecation”选项是否意味着完全弃用
Buffer
构造函数?
是的,确实如此。
@trevnorris ,请注意该表有一个“技术上可能的范围”句子,这意味着它不应该破坏未明确使用Buffer(arg)
。 #7152 和 #11808 对此有一个解决方法,据我所知,一切正常。 我相信这是由@seishun完成的。 您能否提供一个会被任何这些 PR 破坏的测试用例? 不过,最好将讨论转移到 PR 上。
将这个从ctc-review
升级到ctc-agenda
。 我们需要决定在 8.0.0 版本中会发生什么和不会发生什么。
总结现在的情况,至少在我看来,并在@ChALkeR设置的电子表格中阅读了大量内容,并邀请所有 CTC 成员填写:
虽然没有达成一致,但似乎有一个共识,即我们应该做点什么——忽视这个问题不是一个明智的选择。
在 8.0.0 版本中弃用有相当多的反对意见和支持。
补零此时的支持多于反对。 也就是说,对它持中立态度的人几乎与支持它的人一样多。 因此,很大程度上取决于这些人最终如何投票。
安排弃用有很大的支持,但反对的声音略少。 同样,在那个问题上有很多中立的人,所以这将取决于未决定的人最终如何投票。
Opt-in deprecate 预计将轻松登陆 8.0.0 版。 它有两个人被记录在案。 其中之一反对更新文档以外的任何内容。 如果我没记错的话,另一个人很难记住他们为什么表示反对,这可能是一个错误。
对于某些人来说,对零填充的支持或反对取决于是否承诺弃用运行时(例如,宣布将在 N.0.0 版本中进行运行时弃用)以及是否将其向后移植到 LTS 行. 因此,在尝试决定是否进行零填充之前,尝试决定是否安排弃用可能是有意义的。
随机填充似乎比零填充支持更少,但仍然是一个可行的竞争者。 大多数关于零填充的事情说
我认为这已经解决了,至少对于 Node.js 8.0.0。 我们希望在 Node.js 9.0.0 发布之前的 6 个月内(如果不是更早的话!)重新审视这个问题。
目前,反恐委员会决定:
没有任何决定可以取悦所有人。 这就是现在的情况。 如上所述,我们肯定会在不久的将来重新审视这个问题,以评估事情的运作方式或不运作方式。
我还应该提到,有一项努力将规则引入 ESLint,该规则将标记 Buffer 构造函数的使用。 我认为该提案的状态很可能被 ESLint 采纳,但目前还不确定。
我将关闭此问题,但如果您认为此时这不是正确的做法,请随时重新打开或发表评论。 谢谢!
仅供参考,我刚刚发布了standard
10.0.0,它将已弃用的 Node.js API 的使用视为 lint 错误。 因此,我们现在有成千上万的用户(一旦更新),他们将看到有关Buffer()
在他们的测试和 CI 管道中被弃用的警告。
- 禁止使用已弃用的 Node.js API
- 确保代码始终在最新版本的 Node.js 上运行而不会发出警告
- 确保使用安全的 Buffer 方法(
Buffer.from()
、Buffer.alloc()
)而不是Buffer()
很难确切知道有多少人使用standard
,但我们可共享的 eslint 配置每月下载 670K 次,所以我希望这个变化对使用数量有一些明显的影响。 走着瞧。
我认为,如果我们能够依靠社区工具(如standard
和其他工具)来帮助减少此类弃用在未来的痛苦,那就太好了。 @ChALkeR ,如果您能密切关注Buffer()
使用情况,看看它在接下来的 1-3 个月内发生了多少变化,那就太好了。
同样在工具方面:ESLint 将在可预见的未来随附无缓冲区构造函数(这可能不是规则的名称,我现在只是将其用作速记)规则。 请参阅https://github.com/eslint/eslint/issues/5614#issuecomment -291742518 并感谢 @not-an-aardvark 和@jasnell以及其他让我们
随着 Node.js 9.0.0 发布的临近,我认为是时候重新审视这个问题了。 @ChALkeR您能否评估Buffer()
的使用情况在过去 3 个月内发生了多少变化?
@seishun谢谢提醒! 我希望在几天内做到这一点。 =)
@ChALkeR再次响起...
最有用的评论
我的建议:不要弃用
new Buffer(n)
或Buffer(n)
。 不要将其更改为 ES 类。 默认情况下启用零填充。弃用是一项重大成本。
如果目标是让社区更安全,零填充就可以满足这一点。
如果目标是推动社区向更快的 API 方向发展,零填充满足了关心的人的需求,不关心的人不应该被迫关心它。
如果目标是使
Buffer
更具可扩展性,那么它已经具有足够的可扩展性,而且我看不出“可以对缓冲区进行子类化”如何使 Node.js 成为更好的平台。 事实上,这可能是个坏主意。 API 应该温和地传达 Buffer _可能不应该被子类化_,并将其作为工厂函数来完成。但是,任何_想要_对其进行子类化的人今天都可以使用 Node 7,而且它已经足够老套了,他们很可能会避免使用它。
如果目标是警告人们不要创建一个非常大的缓冲区,它可以在
Buffer(n)
被调用时打印一个警告,并使用适当的大数字,以便他们可以在出现问题时对其进行调试。 这比内存泄露要好得多。如果目标是让生态系统中的人们停止使用
new Buffer()
,那么......为什么?