Buffer.from()、Buffer.alloc() 与 Buffer.allocUnsafe()

在 Node.js 6.0.0 之前的版本中,Buffer 实例是使用 Buffer 构造函数创建的,它根据提供的参数以不同的方式分配返回的 Buffer

  • 将数字作为第一个参数传给 Buffer()(例如 new Buffer(10))会分配指定大小的新 Buffer 对象。 在 Node.js 8.0.0 之前,为此类 Buffer 实例分配的内存未初始化并且可能包含敏感数据。 此类 Buffer 实例必须随后通过使用 buf.fill(0) 或在从 Buffer 读取数据之前写入整个 Buffer 来初始化。 虽然这种行为是为了提高性能,但开发经验表明,在创建快速但未初始化的 Buffer 与创建较慢但更安全的 Buffer 之间需要更明确的区别。 从 Node.js 8.0.0 开始,Buffer(num)new Buffer(num) 返回带有初始化内存的 Buffer
  • 传入字符串、数组或 Buffer 作为第一个参数会将传入的对象的数据复制到 Buffer
  • 传入 ArrayBufferSharedArrayBuffer 返回 Buffer,它与给定的数组缓冲区共享分配的内存。

由于 new Buffer() 的行为因第一个参数的类型而异,因此当未执行参数验证或 Buffer 初始化时,可能会无意中将安全性和可靠性问题引入到应用程序中。

例如,如果攻击者可以使应用程序接收到预期为字符串的数字,则应用程序可能会调用 new Buffer(100) 而不是 new Buffer("100"),从而导致它分配 100 字节的缓冲区,而不是分配内容为 "100" 的 3 字节缓冲区。 这通常可以使用 JSON API 调用实现。 由于 JSON 区分数字和字符串类型,因此它允许在未充分验证其输入的天真编写的应用程序可能期望始终接收字符串的情况下注入数字。 在 Node.js 8.0.0 之前,100 字节的缓冲区可能包含任意预先存在的内存数据,因此可用于向远程攻击者公开内存机密。 从 Node.js 8.0.0 开始,不会发生内存暴露,因为数据是零填充的。 但是,其他攻击仍然是可能的,例如导致服务器分配非常大的缓冲区,导致性能下降或因内存耗尽而崩溃。

为了使 Buffer 实例的创建更可靠且不易出错,new Buffer() 构造函数的各种形式已被弃用,并由单独的 Buffer.from()Buffer.alloc()Buffer.allocUnsafe() 方法取代。

开发人员应将 new Buffer() 构造函数的所有现有用途迁移到这些新 API 之一。

如果 size 小于或等于 Buffer.poolSize 的一半,则 Buffer.allocUnsafe()Buffer.from(array) 返回的 Buffer 实例可以从共享内部内存池中分配。 Buffer.allocUnsafeSlow() 返回的实例从不使用共享内部内存池。

In versions of Node.js prior to 6.0.0, Buffer instances were created using the Buffer constructor function, which allocates the returned Buffer differently based on what arguments are provided:

  • Passing a number as the first argument to Buffer() (e.g. new Buffer(10)) allocates a new Buffer object of the specified size. Prior to Node.js 8.0.0, the memory allocated for such Buffer instances is not initialized and can contain sensitive data. Such Buffer instances must be subsequently initialized by using either buf.fill(0) or by writing to the entire Buffer before reading data from the Buffer. While this behavior is intentional to improve performance, development experience has demonstrated that a more explicit distinction is required between creating a fast-but-uninitialized Buffer versus creating a slower-but-safer Buffer. Since Node.js 8.0.0, Buffer(num) and new Buffer(num) return a Buffer with initialized memory.
  • Passing a string, array, or Buffer as the first argument copies the passed object's data into the Buffer.
  • Passing an ArrayBuffer or a SharedArrayBuffer returns a Buffer that shares allocated memory with the given array buffer.

Because the behavior of new Buffer() is different depending on the type of the first argument, security and reliability issues can be inadvertently introduced into applications when argument validation or Buffer initialization is not performed.

For example, if an attacker can cause an application to receive a number where a string is expected, the application may call new Buffer(100) instead of new Buffer("100"), leading it to allocate a 100 byte buffer instead of allocating a 3 byte buffer with content "100". This is commonly possible using JSON API calls. Since JSON distinguishes between numeric and string types, it allows injection of numbers where a naively written application that does not validate its input sufficiently might expect to always receive a string. Before Node.js 8.0.0, the 100 byte buffer might contain arbitrary pre-existing in-memory data, so may be used to expose in-memory secrets to a remote attacker. Since Node.js 8.0.0, exposure of memory cannot occur because the data is zero-filled. However, other attacks are still possible, such as causing very large buffers to be allocated by the server, leading to performance degradation or crashing on memory exhaustion.

To make the creation of Buffer instances more reliable and less error-prone, the various forms of the new Buffer() constructor have been deprecated and replaced by separate Buffer.from(), Buffer.alloc(), and Buffer.allocUnsafe() methods.

Developers should migrate all existing uses of the new Buffer() constructors to one of these new APIs.

Buffer instances returned by Buffer.allocUnsafe() and Buffer.from(array) may be allocated off a shared internal memory pool if size is less than or equal to half Buffer.poolSize. Instances returned by Buffer.allocUnsafeSlow() never use the shared internal memory pool.