safe-buffer

中文文档
5.2.1发布于 2020/05/11MITTypeScriptCommonJS零依赖近 30 天下载 13.2亿

更安全的Node.js缓冲API

中文使用文档
文档目录23 节
  1. 安装
  2. 用法
  3. api
  4. 类方法:Buffer.from(array)
  5. 类方法:Buffer.from(arrayBuffer[, byteOffset[, length]])
  6. 类方法:buffer .from(buffer)
  7. 类方法:Buffer.from(str[, encoding])
  8. 类方法:Buffer。Alloc (size[, fill[, encoding]])
  9. 类方法:Buffer.allocUnsafe(size)
  10. 类方法:Buffer.allocUnsafeSlow(size)
  11. 其他的
  12. 相关链接
  13. 为什么 Buffer 不安全吗?
  14. 远程内存公开
  15. 这在实际代码中会成为问题吗?
  16. 哪些真实世界的软件包是易受攻击的?
  17. 解决办法是什么?
  18. 我们如何修复node.js核心?
  19. 真正的长期解决方案是什么?
  20. 结论
  21. 链接
  22. 信贷
  23. 许可证

safe-buffer travis npm downloads javascript style guide

更安全的Node.js缓冲API

使用新的Node.js Buffer api (Buffer.from, Buffer.alloc, Buffer.allocUnsafe, Buffer.allocUnsafeSlow)在所有版本的Node.js中。

在可用时使用内置实现。

安装

npm install safe-buffer

用法

这个包的目标是为node.js提供一个安全的替代品 Buffer。

这是一个临时替代品 Buffer。加1就可以了 require 行 node.js模块的顶部:

var Buffer = require('safe-buffer').Buffer

// Existing buffer code will continue to work without issues:

new Buffer('hey', 'utf8')
new Buffer([1, 2, 3], 'utf8')
new Buffer(obj)
new Buffer(16) // create an uninitialized buffer (potentially unsafe)

// But you can use these new explicit APIs to make clear what you want:

Buffer.from('hey', 'utf8') // convert from many types to a Buffer
Buffer.alloc(16) // create a zero-filled buffer (safe)
Buffer.allocUnsafe(16) // create an uninitialized buffer (potentially unsafe)

api

类方法:Buffer.from(array)

  • array {数组}

分配一个新的 Buffer 使用一个 array 八位字节。

const buf = Buffer.from([0x62,0x75,0x66,0x66,0x65,0x72]);
  // creates a new Buffer containing ASCII bytes
  // ['b','u','f','f','e','r']

一个 TypeError 会被抛出 array 不是。 Array。

类方法:Buffer.from(arrayBuffer[, byteOffset[, length]])

  • arrayBuffer {ArrayBuffer} .buffer a的性质 TypedArray 或 一个 new ArrayBuffer()
  • byteOffset {}数量默认值: 0
  • length {}数量默认值: arrayBuffer.length - byteOffset

的引用 .buffer a的性质 TypedArray 实例, 新创建的 Buffer 将共享与 TypedArray。

const arr = new Uint16Array(2);
arr[0] = 5000;
arr[1] = 4000;

const buf = Buffer.from(arr.buffer); // shares the memory with arr;

console.log(buf);
  // Prints: <Buffer 88 13 a0 0f>

// changing the TypedArray changes the Buffer also
arr[1] = 6000;

console.log(buf);
  // Prints: <Buffer 88 13 70 17>

可选 byteOffset 和 length 参数指定的内存范围 这个 arrayBuffer 这将由 Buffer。

const ab = new ArrayBuffer(10);
const buf = Buffer.from(ab, 0, 2);
console.log(buf.length);
  // Prints: 2

一个 TypeError 会被抛出 arrayBuffer 不是。 ArrayBuffer。

类方法:buffer .from(buffer)

  • buffer {缓冲}

复制传递的 buffer 数据放到新的 Buffer 实例。

const buf1 = Buffer.from('buffer');
const buf2 = Buffer.from(buf1);

buf1[0] = 0x61;
console.log(buf1.toString());
  // 'auffer'
console.log(buf2.toString());
  // 'buffer' (copy is not changed)

一个 TypeError 会被抛出 buffer 不是。 Buffer。

类方法:Buffer.from(str[, encoding])

  • str {String}要编码的字符串。
  • encoding {String}使用的编码,默认值: 'utf8'

创建一个新的 Buffer 包含给定的JavaScript字符串 str。如果 提供, encoding 参数标识字符编码。 如未提供, encoding 默认为 'utf8'。

const buf1 = Buffer.from('this is a tést');
console.log(buf1.toString());
  // prints: this is a tést
console.log(buf1.toString('ascii'));
  // prints: this is a tC)st

const buf2 = Buffer.from('7468697320697320612074c3a97374', 'hex');
console.log(buf2.toString());
  // prints: this is a tést

一个 TypeError 会被抛出 str 不是字符串。

类方法:Buffer。Alloc (size[, fill[, encoding]])

  • size {}数量
  • fill {}的值默认值: undefined
  • encoding {}字符串默认值: utf8

分配一个新的 Buffer 的 size 字节。如果 fill 是 undefined, Buffer 将 zero-filled。

const buf = Buffer.alloc(5);
console.log(buf);
  // <Buffer 00 00 00 00 00>

这个 size 必须小于或等于 require('buffer').kMaxLength (在64位架构上, kMaxLength 是 (2^31)-1). 否则,a [RangeError[]被抛出。一个零长度的缓冲区可以 创建,如果 size 指定小于或等于0。

如果 fill ,则分配的 Buffer 将通过调用 buf.fill(fill)。参见[buf.fill()[]获取更多信息。

const buf = Buffer.alloc(5, 'a');
console.log(buf);
  // <Buffer 61 61 61 61 61>

如果两个 fill 和 encoding 指定,分配 Buffer 将 通过调用初始化 buf.fill(fill, encoding)。例如:

const buf = Buffer.alloc(11, 'aGVsbG8gd29ybGQ=', 'base64');
console.log(buf);
  // <Buffer 68 65 6c 6c 6f 20 77 6f 72 6c 64>

调用 Buffer.alloc(size) 会比另一种选择慢得多吗 Buffer.allocUnsafe(size) 但确保了新创建的 Buffer 实例 内容将 不要包含敏感数据。

一个 TypeError 会被抛出 size 不是一个数字。

类方法:Buffer.allocUnsafe(size)

  • size {}数量

分配一个新的 non-zero-filled Buffer 的 size 字节。这个 size 必须 的值小于或等于 require('buffer').kMaxLength (在64位 架构, kMaxLength 是 (2^31)-1). 否则,a [RangeError] [] 抛出。将创建一个零长度缓冲区 size 小于或等于 指定0。

的底层内存 Buffer 以这种方式创建的实例是 不 初始化。新创建的内容 Buffer 是未知的 可能包含敏感数据。使用[buf.fill(0)[]初始化这样的 Buffer 实例归零。

const buf = Buffer.allocUnsafe(5);
console.log(buf);
  // <Buffer 78 e0 82 02 01>
  // (octets will be different, every time)
buf.fill(0);
console.log(buf);
  // <Buffer 00 00 00 00 00>

一个 TypeError 会被抛出 size 不是一个数字。

请注意 Buffer 模块预分配一个内部的 Buffer 的实例 大小 Buffer.poolSize 它被用作快速分配new的池 Buffer 使用以下命令创建的实例 Buffer.allocUnsafe(size) (以及被弃用的 new Buffer(size) 构造函数)仅当 size 是小于还是等于 Buffer.poolSize >> 1 (楼 Buffer.poolSize 除以2)。默认的 的价值 Buffer.poolSize 是 8192 但是可以修改。

使用这种预分配的内部内存池是两者之间的关键区别 调用 Buffer.alloc(size, fill) vs。 Buffer.allocUnsafe(size).fill(fill)。 具体地说, Buffer.alloc(size, fill) 将 从来没有 使用内部缓冲区 池, Buffer.allocUnsafe(size).fill(fill) 将 使用内部 缓冲池 size 是小于还是等于1 / 2 Buffer.poolSize。这个 差异是微妙的,但当应用程序需要 额外的性能 Buffer.allocUnsafe(size) 提供了。

类方法:Buffer.allocUnsafeSlow(size)

  • size {}数量

分配一个新的 non-zero-filled 要,没有缓冲的、生命期 Buffer 的 size 字节。这个 size 必须小于或等于 require('buffer').kMaxLength (在64位架构上, kMaxLength 是 (2^31)-1). 否则,a [RangeError[]被抛出。一个零长度的缓冲区可以 创建,如果 size 指定小于或等于0。

的底层内存 Buffer 以这种方式创建的实例是 不 初始化。新创建的内容 Buffer 是未知的 可能包含敏感数据。使用[buf.fill(0)[]初始化这样的 Buffer 实例归零。

当使用 Buffer.allocUnsafe() 分配新的 Buffer 情况下, 默认情况下,4KB以下的分配是从单个预分配的分区中切片的 Buffer。这允许应用程序避免垃圾收集开销 创建许多单独分配的缓冲区。这种方法改进了 通过消除跟踪和清理的需要来提高性能和内存使用 许多 Persistent 对象。

然而,在开发人员可能需要保留一小块的情况下 从内存池中取出内存的时间不确定,这可能是合适的 来创建一个未池化的Buffer实例 Buffer.allocUnsafeSlow() 然后 把相关的部分抄下来。

// need to keep around a few small chunks of memory
const store = [];

socket.on('readable', () => {
  const data = socket.read();
  // allocate for retained data
  const sb = Buffer.allocUnsafeSlow(10);
  // copy the data into the new allocation
  data.copy(sb, 0, 0, 10);
  store.push(sb);
});

使用 Buffer.allocUnsafeSlow() 应该作为最后的手段吗 后 一个开发人员在他们的应用程序中发现了不适当的内存保留。

一个 TypeError 会被抛出 size 不是一个数字。

其他的

剩下的 Buffer API与node.js中的完全相同。 请参阅文档。

为什么 Buffer 不安全吗?

今天,node.js Buffer 构造函数被重载以处理许多不同的参数 类型等 String, Array, Object, TypedArrayView (Uint8Array等), ArrayBuffer,而且 Number。

该API是为了方便而优化的:您可以向它抛出任何类型,它将尝试这样做 你想要什么。

因为Buffer构造函数非常强大,你经常会看到这样的代码:

// Convert UTF-8 strings to hex
function toHex (str) {
  return new Buffer(str).toString('hex')
}

但是如果 toHex 是用 Number 论点吗?

远程内存公开

如果攻击者可以使您的程序调用 Buffer 的构造函数 Number 参数,然后他们可以让它从node.js进程中分配未初始化的内存。 这可能会泄露TLS私钥、用户数据或数据库密码。

当 Buffer 构造函数传递给 Number 参数时,它返回一个 未初始化 指定的内存块 size。当你创建 Buffer 就像 这个,你 必须 在将内容返回给用户之前覆盖它。

从 node . js文件:

new Buffer(size)

  • size 数量

的底层内存 Buffer 以这种方式创建的实例不会初始化。 新创建的内容 Buffer 是否未知且可能含有敏感物质 数据。 使用 buf.fill(0) 将Buffer初始化为0。

(强调我们自己的观点。)

每当程序员打算创建一个未初始化的 Buffer 你经常会看到代码 是这样的:

var buf = new Buffer(16)

// Immediately overwrite the uninitialized buffer with data from another buffer
for (var i = 0; i < buf.length; i++) {
  buf[i] = otherBuf[i]
}

这在实际代码中会成为问题吗?

是。在a中忘记检查变量的类型是非常常见的 动态类型语言,如JavaScript。

通常,假设错误的类型的后果是您的程序崩溃 未捕获异常。但是忘记检查参数类型的失败模式 这个 Buffer 构造函数更具灾难性。

下面是一个易受攻击的服务示例,该服务接受JSON有效负载并将其转换为 十六进制:

// Take a JSON payload {str: "some string"} and convert it to hex
var server = http.createServer(function (req, res) {
  var data = ''
  req.setEncoding('utf8')
  req.on('data', function (chunk) {
    data += chunk
  })
  req.on('end', function () {
    var body = JSON.parse(data)
    res.end(new Buffer(body.str).toString('hex'))
  })
})

server.listen(8080)

在这个例子中,http客户端只需要发送:

{
  "str": 1000
}

它将从服务器获得1000字节未初始化的内存。

这是一个非常严重的错误。在严重程度上和 心脏出血bug 允许公开OpenSSL进程 内存被远程攻击者攻击。

哪些真实世界的软件包是易受攻击的?

bittorrent-dht

马赛厄斯我校 和我 (Feross Aboukhadijeh)在我们自己的一个软件包中发现了这个问题, bittorrent-dht。这个漏洞将允许 任何人在互联网上发送一系列的消息给用户 bittorrent-dht 并获得 它们每次显示来自node.js进程的未初始化内存的20个字节。

这是 提交 这就解决了问题。我们发布了一个新的固定版本,创建了一个 节点安全项目披露,弃用一切 NPM上的易受攻击版本,因此用户将收到升级到新版本的警告。

ws

这让我们怀疑是否还有其他易受攻击的软件包。果然,在短时间内 一段时间以来,我们发现了同样的问题 ws, 在node.js中最流行的WebSocket实现。

如果调用了某些api Number 参数而不是 String 或 Buffer 作为 预期,那么未初始化的服务器内存将被公开给远程对等端。

这些是易受攻击的方法:

socket.send(number)
socket.ping(number)
socket.pong(number)

下面是一个带有回显功能的易受攻击的套接字服务器:

server.on('connection', function (socket) {
  socket.on('message', function (message) {
    message = JSON.parse(message)
    if (message.type === 'echo') {
      socket.send(message.data) // send back the user's message
    }
  })
})

socket.send(number) 在服务器上调用,将公开服务器内存。

这是 释放 问题在哪里? 是固定的,并有更详细的解释。道具 Arnout Kazemier 为了快速解决问题。这是 节点安全项目披露。

解决办法是什么?

重要的是,node.js提供了一种快速获取内存的方法,否则对性能至关重要 应用程序将不必要地变得慢很多。

但我们需要更好的方法 表明我们的意图 作为程序员。 当我们需要的时候 未初始化的内存,我们应该显式请求它。

敏感的功能不应该被打包到开发人员友好的API中 接受许多不同的类型。这种类型的API鼓励懒惰的传递实践 在没有仔细检查类型的情况下导入变量。

一个新的API: Buffer.allocUnsafe(number)

使用未初始化内存创建缓冲区的功能应该是另一个功能的一部分 API。我们建议 Buffer.allocUnsafe(number)。这样,它就不是API的一部分 经常获取传递给它的各种不同类型的用户输入。

var buf = Buffer.allocUnsafe(16) // careful, uninitialized memory!

// Immediately overwrite the uninitialized buffer with data from another buffer
for (var i = 0; i < buf.length; i++) {
  buf[i] = otherBuf[i]
}

我们如何修复node.js核心?

我们发送 一个PR到node.js核心 (合并 semver-major),就一个案件进行辩护;

var str = 16
new Buffer(str, 'utf8')

在这种情况下,这意味着程序员希望第一个参数是a 字符串,因为它们传递了一个编码作为第二个参数。今天,node.js将分配 的情况下未初始化的内存 new Buffer(number, encoding),这可能不是 程序员的意图。

但这只是部分解决方案,因为如果程序员这样做 new Buffer(variable) (没有一个 encoding 参数),没有办法知道他们的意图。如果 variable 有时是一个数字,那么有时会返回未初始化的内存。

真正的长期解决方案是什么?

我们可以弃用和删除 new Buffer(number) 和使用 Buffer.allocUnsafe(number) 当 我们需要未初始化的内存。但这将破坏数千个包。

我们认为最好的解决方案是:

1. 改变 new Buffer(number) 返回安全的、归零的内存

2. 创建一个用于创建未初始化缓冲区的新API。我们建议: Buffer.allocUnsafe(number)

更新

我们现在支持添加三个新的api:

  • Buffer.from(value) -从任何类型转换为缓冲区
  • Buffer.alloc(size) -创建一个零填充缓冲区
  • Buffer.allocUnsafe(size) -创建一个给定大小的未初始化缓冲区

这就解决了影响的核心问题 ws 和 bittorrent-dht 这是 Buffer(variable) 被骗接受一个数字参数。

通过这种方式,现有的代码可以继续工作,而对npm生态系统的影响将会减小 最小的。随着时间的推移,npm维护者可以将性能关键型代码迁移使用 Buffer.allocUnsafe(number) 而不是 new Buffer(number)。

结论

我们认为这是一个严重的设计问题 Buffer 今天的API。它 通过将高风险功能放入方便的API中来促进不安全的软件 友好的“开发者人体工程学”。

这不仅仅是一个理论练习,因为我们在一些 最流行的NPM包。

幸运的是,今天有一个简单的解决办法。使用 safe-buffer 代替 buffer。

var Buffer = require('safe-buffer').Buffer

最终,我们希望node.js核心可以切换到这种新的、更安全的行为。我们相信 对生态系统的影响将是最小的,因为这不是一个突破性的变化。 维护良好的、流行的软件包将被更新以使用 Buffer.alloc 很快,而 旧的、不安全的包将神奇地从这种攻击向量中变得安全。

信贷

最初的问题 bittorrent-dht (信息披露), ws (信息披露)被 马赛厄斯我校 和 Feross Aboukhadijeh。

多亏了 亚当·鲍德温 帮助揭露这些问题 以及他的工作 节点安全项目。

多亏了 约翰Hiesey 校对本自述文件和 审核代码。

许可证

麻省理工学院。版权(C) Feross Aboukhadijeh

版本与发布记录

safe-buffer 共发布 11 个版本,首次发布于 2016/01/13;过去 12 个月发布了 0 个版本。

版本发布日期距今
5.2.1latest2020/05/116 年前
5.2.02019/07/067 年前
5.1.22018/04/268 年前
5.1.12017/06/229 年前
5.1.02017/06/039 年前
5.0.12016/05/3010 年前

关于 safe-buffer 的常见问题

safe-buffer 是什么?

更安全的Node.js缓冲API

如何安装 safe-buffer?

在项目目录运行 npm install safe-buffer,或使用 pnpm add / yarn add / bun add。

safe-buffer 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

safe-buffer 支持 ESM 还是 CommonJS?

以 CommonJS 形式发布,可以直接 require();在 ES Module 项目中也可以用 import 引入(Node.js 会把 module.exports 作为默认导出)。

safe-buffer 可以商用吗?

safe-buffer 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

safe-buffer 的下载量有多少?

根据 npm 官方下载统计,safe-buffer 在 2026/10/05 – 2026/11/03 这 30 天内被下载 1,319,706,407 次,日均约 43,990,214 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

safe-buffer 还在维护吗?

最新版本 5.2.1 发布于 2020/05/11(6 年前);至今共发布 11 个版本,其中过去 12 个月发布了 0 个;首次发布于 2016/01/13。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。