Projekt

Obecné

Profil

Stáhnout (19.3 KB) Statistiky
| Větev: | Revize:
1 9bb1e829 cagy
# safe-buffer [![travis][travis-image]][travis-url] [![npm][npm-image]][npm-url] [![downloads][downloads-image]][downloads-url] [![javascript style guide][standard-image]][standard-url]
2
3
[travis-image]: https://img.shields.io/travis/feross/safe-buffer/master.svg
4
[travis-url]: https://travis-ci.org/feross/safe-buffer
5
[npm-image]: https://img.shields.io/npm/v/safe-buffer.svg
6
[npm-url]: https://npmjs.org/package/safe-buffer
7
[downloads-image]: https://img.shields.io/npm/dm/safe-buffer.svg
8
[downloads-url]: https://npmjs.org/package/safe-buffer
9
[standard-image]: https://img.shields.io/badge/code_style-standard-brightgreen.svg
10
[standard-url]: https://standardjs.com
11
12
#### Safer Node.js Buffer API
13
14
**Use the new Node.js Buffer APIs (`Buffer.from`, `Buffer.alloc`,
15
`Buffer.allocUnsafe`, `Buffer.allocUnsafeSlow`) in all versions of Node.js.**
16
17
**Uses the built-in implementation when available.**
18
19
## install
20
21
```
22
npm install safe-buffer
23
```
24
25
[Get supported safe-buffer with the Tidelift Subscription](https://tidelift.com/subscription/pkg/npm-safe-buffer?utm_source=npm-safe-buffer&utm_medium=referral&utm_campaign=readme)
26
27
## usage
28
29
The goal of this package is to provide a safe replacement for the node.js `Buffer`.
30
31
It's a drop-in replacement for `Buffer`. You can use it by adding one `require` line to
32
the top of your node.js modules:
33
34
```js
35
var Buffer = require('safe-buffer').Buffer
36
37
// Existing buffer code will continue to work without issues:
38
39
new Buffer('hey', 'utf8')
40
new Buffer([1, 2, 3], 'utf8')
41
new Buffer(obj)
42
new Buffer(16) // create an uninitialized buffer (potentially unsafe)
43
44
// But you can use these new explicit APIs to make clear what you want:
45
46
Buffer.from('hey', 'utf8') // convert from many types to a Buffer
47
Buffer.alloc(16) // create a zero-filled buffer (safe)
48
Buffer.allocUnsafe(16) // create an uninitialized buffer (potentially unsafe)
49
```
50
51
## api
52
53
### Class Method: Buffer.from(array)
54
<!-- YAML
55
added: v3.0.0
56
-->
57
58
* `array` {Array}
59
60
Allocates a new `Buffer` using an `array` of octets.
61
62
```js
63
const buf = Buffer.from([0x62,0x75,0x66,0x66,0x65,0x72]);
64
  // creates a new Buffer containing ASCII bytes
65
  // ['b','u','f','f','e','r']
66
```
67
68
A `TypeError` will be thrown if `array` is not an `Array`.
69
70
### Class Method: Buffer.from(arrayBuffer[, byteOffset[, length]])
71
<!-- YAML
72
added: v5.10.0
73
-->
74
75
* `arrayBuffer` {ArrayBuffer} The `.buffer` property of a `TypedArray` or
76
  a `new ArrayBuffer()`
77
* `byteOffset` {Number} Default: `0`
78
* `length` {Number} Default: `arrayBuffer.length - byteOffset`
79
80
When passed a reference to the `.buffer` property of a `TypedArray` instance,
81
the newly created `Buffer` will share the same allocated memory as the
82
TypedArray.
83
84
```js
85
const arr = new Uint16Array(2);
86
arr[0] = 5000;
87
arr[1] = 4000;
88
89
const buf = Buffer.from(arr.buffer); // shares the memory with arr;
90
91
console.log(buf);
92
  // Prints: <Buffer 88 13 a0 0f>
93
94
// changing the TypedArray changes the Buffer also
95
arr[1] = 6000;
96
97
console.log(buf);
98
  // Prints: <Buffer 88 13 70 17>
99
```
100
101
The optional `byteOffset` and `length` arguments specify a memory range within
102
the `arrayBuffer` that will be shared by the `Buffer`.
103
104
```js
105
const ab = new ArrayBuffer(10);
106
const buf = Buffer.from(ab, 0, 2);
107
console.log(buf.length);
108
  // Prints: 2
109
```
110
111
A `TypeError` will be thrown if `arrayBuffer` is not an `ArrayBuffer`.
112
113
### Class Method: Buffer.from(buffer)
114
<!-- YAML
115
added: v3.0.0
116
-->
117
118
* `buffer` {Buffer}
119
120
Copies the passed `buffer` data onto a new `Buffer` instance.
121
122
```js
123
const buf1 = Buffer.from('buffer');
124
const buf2 = Buffer.from(buf1);
125
126
buf1[0] = 0x61;
127
console.log(buf1.toString());
128
  // 'auffer'
129
console.log(buf2.toString());
130
  // 'buffer' (copy is not changed)
131
```
132
133
A `TypeError` will be thrown if `buffer` is not a `Buffer`.
134
135
### Class Method: Buffer.from(str[, encoding])
136
<!-- YAML
137
added: v5.10.0
138
-->
139
140
* `str` {String} String to encode.
141
* `encoding` {String} Encoding to use, Default: `'utf8'`
142
143
Creates a new `Buffer` containing the given JavaScript string `str`. If
144
provided, the `encoding` parameter identifies the character encoding.
145
If not provided, `encoding` defaults to `'utf8'`.
146
147
```js
148
const buf1 = Buffer.from('this is a tést');
149
console.log(buf1.toString());
150
  // prints: this is a tést
151
console.log(buf1.toString('ascii'));
152
  // prints: this is a tC)st
153
154
const buf2 = Buffer.from('7468697320697320612074c3a97374', 'hex');
155
console.log(buf2.toString());
156
  // prints: this is a tést
157
```
158
159
A `TypeError` will be thrown if `str` is not a string.
160
161
### Class Method: Buffer.alloc(size[, fill[, encoding]])
162
<!-- YAML
163
added: v5.10.0
164
-->
165
166
* `size` {Number}
167
* `fill` {Value} Default: `undefined`
168
* `encoding` {String} Default: `utf8`
169
170
Allocates a new `Buffer` of `size` bytes. If `fill` is `undefined`, the
171
`Buffer` will be *zero-filled*.
172
173
```js
174
const buf = Buffer.alloc(5);
175
console.log(buf);
176
  // <Buffer 00 00 00 00 00>
177
```
178
179
The `size` must be less than or equal to the value of
180
`require('buffer').kMaxLength` (on 64-bit architectures, `kMaxLength` is
181
`(2^31)-1`). Otherwise, a [`RangeError`][] is thrown. A zero-length Buffer will
182
be created if a `size` less than or equal to 0 is specified.
183
184
If `fill` is specified, the allocated `Buffer` will be initialized by calling
185
`buf.fill(fill)`. See [`buf.fill()`][] for more information.
186
187
```js
188
const buf = Buffer.alloc(5, 'a');
189
console.log(buf);
190
  // <Buffer 61 61 61 61 61>
191
```
192
193
If both `fill` and `encoding` are specified, the allocated `Buffer` will be
194
initialized by calling `buf.fill(fill, encoding)`. For example:
195
196
```js
197
const buf = Buffer.alloc(11, 'aGVsbG8gd29ybGQ=', 'base64');
198
console.log(buf);
199
  // <Buffer 68 65 6c 6c 6f 20 77 6f 72 6c 64>
200
```
201
202
Calling `Buffer.alloc(size)` can be significantly slower than the alternative
203
`Buffer.allocUnsafe(size)` but ensures that the newly created `Buffer` instance
204
contents will *never contain sensitive data*.
205
206
A `TypeError` will be thrown if `size` is not a number.
207
208
### Class Method: Buffer.allocUnsafe(size)
209
<!-- YAML
210
added: v5.10.0
211
-->
212
213
* `size` {Number}
214
215
Allocates a new *non-zero-filled* `Buffer` of `size` bytes.  The `size` must
216
be less than or equal to the value of `require('buffer').kMaxLength` (on 64-bit
217
architectures, `kMaxLength` is `(2^31)-1`). Otherwise, a [`RangeError`][] is
218
thrown. A zero-length Buffer will be created if a `size` less than or equal to
219
0 is specified.
220
221
The underlying memory for `Buffer` instances created in this way is *not
222
initialized*. The contents of the newly created `Buffer` are unknown and
223
*may contain sensitive data*. Use [`buf.fill(0)`][] to initialize such
224
`Buffer` instances to zeroes.
225
226
```js
227
const buf = Buffer.allocUnsafe(5);
228
console.log(buf);
229
  // <Buffer 78 e0 82 02 01>
230
  // (octets will be different, every time)
231
buf.fill(0);
232
console.log(buf);
233
  // <Buffer 00 00 00 00 00>
234
```
235
236
A `TypeError` will be thrown if `size` is not a number.
237
238
Note that the `Buffer` module pre-allocates an internal `Buffer` instance of
239
size `Buffer.poolSize` that is used as a pool for the fast allocation of new
240
`Buffer` instances created using `Buffer.allocUnsafe(size)` (and the deprecated
241
`new Buffer(size)` constructor) only when `size` is less than or equal to
242
`Buffer.poolSize >> 1` (floor of `Buffer.poolSize` divided by two). The default
243
value of `Buffer.poolSize` is `8192` but can be modified.
244
245
Use of this pre-allocated internal memory pool is a key difference between
246
calling `Buffer.alloc(size, fill)` vs. `Buffer.allocUnsafe(size).fill(fill)`.
247
Specifically, `Buffer.alloc(size, fill)` will *never* use the internal Buffer
248
pool, while `Buffer.allocUnsafe(size).fill(fill)` *will* use the internal
249
Buffer pool if `size` is less than or equal to half `Buffer.poolSize`. The
250
difference is subtle but can be important when an application requires the
251
additional performance that `Buffer.allocUnsafe(size)` provides.
252
253
### Class Method: Buffer.allocUnsafeSlow(size)
254
<!-- YAML
255
added: v5.10.0
256
-->
257
258
* `size` {Number}
259
260
Allocates a new *non-zero-filled* and non-pooled `Buffer` of `size` bytes.  The
261
`size` must be less than or equal to the value of
262
`require('buffer').kMaxLength` (on 64-bit architectures, `kMaxLength` is
263
`(2^31)-1`). Otherwise, a [`RangeError`][] is thrown. A zero-length Buffer will
264
be created if a `size` less than or equal to 0 is specified.
265
266
The underlying memory for `Buffer` instances created in this way is *not
267
initialized*. The contents of the newly created `Buffer` are unknown and
268
*may contain sensitive data*. Use [`buf.fill(0)`][] to initialize such
269
`Buffer` instances to zeroes.
270
271
When using `Buffer.allocUnsafe()` to allocate new `Buffer` instances,
272
allocations under 4KB are, by default, sliced from a single pre-allocated
273
`Buffer`. This allows applications to avoid the garbage collection overhead of
274
creating many individually allocated Buffers. This approach improves both
275
performance and memory usage by eliminating the need to track and cleanup as
276
many `Persistent` objects.
277
278
However, in the case where a developer may need to retain a small chunk of
279
memory from a pool for an indeterminate amount of time, it may be appropriate
280
to create an un-pooled Buffer instance using `Buffer.allocUnsafeSlow()` then
281
copy out the relevant bits.
282
283
```js
284
// need to keep around a few small chunks of memory
285
const store = [];
286
287
socket.on('readable', () => {
288
  const data = socket.read();
289
  // allocate for retained data
290
  const sb = Buffer.allocUnsafeSlow(10);
291
  // copy the data into the new allocation
292
  data.copy(sb, 0, 0, 10);
293
  store.push(sb);
294
});
295
```
296
297
Use of `Buffer.allocUnsafeSlow()` should be used only as a last resort *after*
298
a developer has observed undue memory retention in their applications.
299
300
A `TypeError` will be thrown if `size` is not a number.
301
302
### All the Rest
303
304
The rest of the `Buffer` API is exactly the same as in node.js.
305
[See the docs](https://nodejs.org/api/buffer.html).
306
307
308
## Related links
309
310
- [Node.js issue: Buffer(number) is unsafe](https://github.com/nodejs/node/issues/4660)
311
- [Node.js Enhancement Proposal: Buffer.from/Buffer.alloc/Buffer.zalloc/Buffer() soft-deprecate](https://github.com/nodejs/node-eps/pull/4)
312
313
## Why is `Buffer` unsafe?
314
315
Today, the node.js `Buffer` constructor is overloaded to handle many different argument
316
types like `String`, `Array`, `Object`, `TypedArrayView` (`Uint8Array`, etc.),
317
`ArrayBuffer`, and also `Number`.
318
319
The API is optimized for convenience: you can throw any type at it, and it will try to do
320
what you want.
321
322
Because the Buffer constructor is so powerful, you often see code like this:
323
324
```js
325
// Convert UTF-8 strings to hex
326
function toHex (str) {
327
  return new Buffer(str).toString('hex')
328
}
329
```
330
331
***But what happens if `toHex` is called with a `Number` argument?***
332
333
### Remote Memory Disclosure
334
335
If an attacker can make your program call the `Buffer` constructor with a `Number`
336
argument, then they can make it allocate uninitialized memory from the node.js process.
337
This could potentially disclose TLS private keys, user data, or database passwords.
338
339
When the `Buffer` constructor is passed a `Number` argument, it returns an
340
**UNINITIALIZED** block of memory of the specified `size`. When you create a `Buffer` like
341
this, you **MUST** overwrite the contents before returning it to the user.
342
343
From the [node.js docs](https://nodejs.org/api/buffer.html#buffer_new_buffer_size):
344
345
> `new Buffer(size)`
346
>
347
> - `size` Number
348
>
349
> The underlying memory for `Buffer` instances created in this way is not initialized.
350
> **The contents of a newly created `Buffer` are unknown and could contain sensitive
351
> data.** Use `buf.fill(0)` to initialize a Buffer to zeroes.
352
353
(Emphasis our own.)
354
355
Whenever the programmer intended to create an uninitialized `Buffer` you often see code
356
like this:
357
358
```js
359
var buf = new Buffer(16)
360
361
// Immediately overwrite the uninitialized buffer with data from another buffer
362
for (var i = 0; i < buf.length; i++) {
363
  buf[i] = otherBuf[i]
364
}
365
```
366
367
368
### Would this ever be a problem in real code?
369
370
Yes. It's surprisingly common to forget to check the type of your variables in a
371
dynamically-typed language like JavaScript.
372
373
Usually the consequences of assuming the wrong type is that your program crashes with an
374
uncaught exception. But the failure mode for forgetting to check the type of arguments to
375
the `Buffer` constructor is more catastrophic.
376
377
Here's an example of a vulnerable service that takes a JSON payload and converts it to
378
hex:
379
380
```js
381
// Take a JSON payload {str: "some string"} and convert it to hex
382
var server = http.createServer(function (req, res) {
383
  var data = ''
384
  req.setEncoding('utf8')
385
  req.on('data', function (chunk) {
386
    data += chunk
387
  })
388
  req.on('end', function () {
389
    var body = JSON.parse(data)
390
    res.end(new Buffer(body.str).toString('hex'))
391
  })
392
})
393
394
server.listen(8080)
395
```
396
397
In this example, an http client just has to send:
398
399
```json
400
{
401
  "str": 1000
402
}
403
```
404
405
and it will get back 1,000 bytes of uninitialized memory from the server.
406
407
This is a very serious bug. It's similar in severity to the
408
[the Heartbleed bug](http://heartbleed.com/) that allowed disclosure of OpenSSL process
409
memory by remote attackers.
410
411
412
### Which real-world packages were vulnerable?
413
414
#### [`bittorrent-dht`](https://www.npmjs.com/package/bittorrent-dht)
415
416
[Mathias Buus](https://github.com/mafintosh) and I
417
([Feross Aboukhadijeh](http://feross.org/)) found this issue in one of our own packages,
418
[`bittorrent-dht`](https://www.npmjs.com/package/bittorrent-dht). The bug would allow
419
anyone on the internet to send a series of messages to a user of `bittorrent-dht` and get
420
them to reveal 20 bytes at a time of uninitialized memory from the node.js process.
421
422
Here's
423
[the commit](https://github.com/feross/bittorrent-dht/commit/6c7da04025d5633699800a99ec3fbadf70ad35b8)
424
that fixed it. We released a new fixed version, created a
425
[Node Security Project disclosure](https://nodesecurity.io/advisories/68), and deprecated all
426
vulnerable versions on npm so users will get a warning to upgrade to a newer version.
427
428
#### [`ws`](https://www.npmjs.com/package/ws)
429
430
That got us wondering if there were other vulnerable packages. Sure enough, within a short
431
period of time, we found the same issue in [`ws`](https://www.npmjs.com/package/ws), the
432
most popular WebSocket implementation in node.js.
433
434
If certain APIs were called with `Number` parameters instead of `String` or `Buffer` as
435
expected, then uninitialized server memory would be disclosed to the remote peer.
436
437
These were the vulnerable methods:
438
439
```js
440
socket.send(number)
441
socket.ping(number)
442
socket.pong(number)
443
```
444
445
Here's a vulnerable socket server with some echo functionality:
446
447
```js
448
server.on('connection', function (socket) {
449
  socket.on('message', function (message) {
450
    message = JSON.parse(message)
451
    if (message.type === 'echo') {
452
      socket.send(message.data) // send back the user's message
453
    }
454
  })
455
})
456
```
457
458
`socket.send(number)` called on the server, will disclose server memory.
459
460
Here's [the release](https://github.com/websockets/ws/releases/tag/1.0.1) where the issue
461
was fixed, with a more detailed explanation. Props to
462
[Arnout Kazemier](https://github.com/3rd-Eden) for the quick fix. Here's the
463
[Node Security Project disclosure](https://nodesecurity.io/advisories/67).
464
465
466
### What's the solution?
467
468
It's important that node.js offers a fast way to get memory otherwise performance-critical
469
applications would needlessly get a lot slower.
470
471
But we need a better way to *signal our intent* as programmers. **When we want
472
uninitialized memory, we should request it explicitly.**
473
474
Sensitive functionality should not be packed into a developer-friendly API that loosely
475
accepts many different types. This type of API encourages the lazy practice of passing
476
variables in without checking the type very carefully.
477
478
#### A new API: `Buffer.allocUnsafe(number)`
479
480
The functionality of creating buffers with uninitialized memory should be part of another
481
API. We propose `Buffer.allocUnsafe(number)`. This way, it's not part of an API that
482
frequently gets user input of all sorts of different types passed into it.
483
484
```js
485
var buf = Buffer.allocUnsafe(16) // careful, uninitialized memory!
486
487
// Immediately overwrite the uninitialized buffer with data from another buffer
488
for (var i = 0; i < buf.length; i++) {
489
  buf[i] = otherBuf[i]
490
}
491
```
492
493
494
### How do we fix node.js core?
495
496
We sent [a PR to node.js core](https://github.com/nodejs/node/pull/4514) (merged as
497
`semver-major`) which defends against one case:
498
499
```js
500
var str = 16
501
new Buffer(str, 'utf8')
502
```
503
504
In this situation, it's implied that the programmer intended the first argument to be a
505
string, since they passed an encoding as a second argument. Today, node.js will allocate
506
uninitialized memory in the case of `new Buffer(number, encoding)`, which is probably not
507
what the programmer intended.
508
509
But this is only a partial solution, since if the programmer does `new Buffer(variable)`
510
(without an `encoding` parameter) there's no way to know what they intended. If `variable`
511
is sometimes a number, then uninitialized memory will sometimes be returned.
512
513
### What's the real long-term fix?
514
515
We could deprecate and remove `new Buffer(number)` and use `Buffer.allocUnsafe(number)` when
516
we need uninitialized memory. But that would break 1000s of packages.
517
518
~~We believe the best solution is to:~~
519
520
~~1. Change `new Buffer(number)` to return safe, zeroed-out memory~~
521
522
~~2. Create a new API for creating uninitialized Buffers. We propose: `Buffer.allocUnsafe(number)`~~
523
524
#### Update
525
526
We now support adding three new APIs:
527
528
- `Buffer.from(value)` - convert from any type to a buffer
529
- `Buffer.alloc(size)` - create a zero-filled buffer
530
- `Buffer.allocUnsafe(size)` - create an uninitialized buffer with given size
531
532
This solves the core problem that affected `ws` and `bittorrent-dht` which is
533
`Buffer(variable)` getting tricked into taking a number argument.
534
535
This way, existing code continues working and the impact on the npm ecosystem will be
536
minimal. Over time, npm maintainers can migrate performance-critical code to use
537
`Buffer.allocUnsafe(number)` instead of `new Buffer(number)`.
538
539
540
### Conclusion
541
542
We think there's a serious design issue with the `Buffer` API as it exists today. It
543
promotes insecure software by putting high-risk functionality into a convenient API
544
with friendly "developer ergonomics".
545
546
This wasn't merely a theoretical exercise because we found the issue in some of the
547
most popular npm packages.
548
549
Fortunately, there's an easy fix that can be applied today. Use `safe-buffer` in place of
550
`buffer`.
551
552
```js
553
var Buffer = require('safe-buffer').Buffer
554
```
555
556
Eventually, we hope that node.js core can switch to this new, safer behavior. We believe
557
the impact on the ecosystem would be minimal since it's not a breaking change.
558
Well-maintained, popular packages would be updated to use `Buffer.alloc` quickly, while
559
older, insecure packages would magically become safe from this attack vector.
560
561
562
## links
563
564
- [Node.js PR: buffer: throw if both length and enc are passed](https://github.com/nodejs/node/pull/4514)
565
- [Node Security Project disclosure for `ws`](https://nodesecurity.io/advisories/67)
566
- [Node Security Project disclosure for`bittorrent-dht`](https://nodesecurity.io/advisories/68)
567
568
569
## credit
570
571
The original issues in `bittorrent-dht`
572
([disclosure](https://nodesecurity.io/advisories/68)) and
573
`ws` ([disclosure](https://nodesecurity.io/advisories/67)) were discovered by
574
[Mathias Buus](https://github.com/mafintosh) and
575
[Feross Aboukhadijeh](http://feross.org/).
576
577
Thanks to [Adam Baldwin](https://github.com/evilpacket) for helping disclose these issues
578
and for his work running the [Node Security Project](https://nodesecurity.io/).
579
580
Thanks to [John Hiesey](https://github.com/jhiesey) for proofreading this README and
581
auditing the code.
582
583
584
## license
585
586
MIT. Copyright (C) [Feross Aboukhadijeh](http://feross.org)