From e5d71cd17be68480e847f4eb47d4160894908402 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D0=BA=D0=BE=D0=B2=D0=BE=D1=80=D0=BE=D0=B4=D0=B0=20?= =?UTF-8?q?=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0=20=D0=90=D0=BD=D0=B4=D1=80?= =?UTF-8?q?=D0=B5=D0=B5=D0=B2=D0=B8=D1=87?= Date: Wed, 26 Jun 2019 23:18:02 +0300 Subject: [PATCH 1/9] buffer: add Buffer.harden() method --- lib/buffer.js | 39 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/lib/buffer.js b/lib/buffer.js index 7b656496e53efb..cff03ebcdbee2a 100644 --- a/lib/buffer.js +++ b/lib/buffer.js @@ -63,6 +63,7 @@ const { const { codes: { + ERR_ASSERTION, ERR_BUFFER_OUT_OF_BOUNDS, ERR_INVALID_ARG_TYPE, ERR_INVALID_ARG_VALUE, @@ -109,8 +110,13 @@ let poolSize, poolOffset, allocPool; // do not own the ArrayBuffer allocator. Zero fill is always on in that case. const zeroFill = bindingZeroFill || [0]; +// Hardening Buffer enables Buffer constructor runtime deprecation, +// disables pooling, and enables mandratory zero-fill. This is more secure, but +// has potential performance impact, depending on the usecase. +let hardened = false; + function createUnsafeBuffer(size) { - zeroFill[0] = 0; + zeroFill[0] = hardened ? 1 : 0; try { return new FastBuffer(size); } finally { @@ -140,6 +146,15 @@ const bufferWarning = 'Buffer() is deprecated due to security and usability ' + 'Buffer.allocUnsafe(), or Buffer.from() methods instead.'; function showFlaggedDeprecation() { + if (hardened) { + if (bufferWarningAlreadyEmitted) return; + process.emitWarning( + bufferWarning + ' Buffer() will soon throw in hardened mode.', + 'DeprecationWarning', 'DEP0XXX'); + bufferWarningAlreadyEmitted = true; + return; + } + if (bufferWarningAlreadyEmitted || ++nodeModulesCheckCounter > 10000 || (!require('internal/options').getOptionValue('--pending-deprecation') && @@ -157,6 +172,26 @@ function showFlaggedDeprecation() { bufferWarningAlreadyEmitted = true; } +// Calling this method does not affect existing buffers, only new ones. +Buffer.harden = function() { + if (hardened) return; + if (isInsideNodeModules()) { + throw new ERR_ASSERTION( + 'Buffer.harden() should be called only from the top-level module. ' + + 'Calling Buffer.harden() from dependencies is not supported.' + ); + } + hardened = true; + Object.defineProperty(Buffer, 'poolSize', { + enumerable: true, + get: () => 0, + set: () => {}, + }); + Object.freeze(Buffer); + Object.freeze(Buffer.prototype); + Object.freeze(module.exports); +}; + function toInteger(n, defaultVal) { n = +n; if (!Number.isNaN(n) && @@ -365,7 +400,7 @@ function allocate(size) { if (size <= 0) { return new FastBuffer(); } - if (size < (Buffer.poolSize >>> 1)) { + if (size < (Buffer.poolSize >>> 1) && !hardened) { if (size > (poolSize - poolOffset)) createPool(); const b = new FastBuffer(allocPool, poolOffset, size); From cebb1d5ff8002e482d89b51a73a4c024ad09ba46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D0=BA=D0=BE=D0=B2=D0=BE=D1=80=D0=BE=D0=B4=D0=B0=20?= =?UTF-8?q?=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0=20=D0=90=D0=BD=D0=B4=D1=80?= =?UTF-8?q?=D0=B5=D0=B5=D0=B2=D0=B8=D1=87?= Date: Mon, 29 Jul 2019 14:56:44 +0300 Subject: [PATCH 2/9] [squash] buffer: undo freezing Buffer.prototype --- lib/buffer.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/buffer.js b/lib/buffer.js index cff03ebcdbee2a..55bbc2d9173ab5 100644 --- a/lib/buffer.js +++ b/lib/buffer.js @@ -188,7 +188,6 @@ Buffer.harden = function() { set: () => {}, }); Object.freeze(Buffer); - Object.freeze(Buffer.prototype); Object.freeze(module.exports); }; From 5a29ee6f46a3b6f3a76ec48f97ccdfcf61a54348 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D0=BA=D0=BE=D0=B2=D0=BE=D1=80=D0=BE=D0=B4=D0=B0=20?= =?UTF-8?q?=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0=20=D0=90=D0=BD=D0=B4=D1=80?= =?UTF-8?q?=D0=B5=D0=B5=D0=B2=D0=B8=D1=87?= Date: Mon, 29 Jul 2019 14:57:20 +0300 Subject: [PATCH 3/9] [squash] buffer: add "strict" harden mode (default) --- lib/buffer.js | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/lib/buffer.js b/lib/buffer.js index 55bbc2d9173ab5..a4174921412416 100644 --- a/lib/buffer.js +++ b/lib/buffer.js @@ -113,7 +113,12 @@ const zeroFill = bindingZeroFill || [0]; // Hardening Buffer enables Buffer constructor runtime deprecation, // disables pooling, and enables mandratory zero-fill. This is more secure, but // has potential performance impact, depending on the usecase. -let hardened = false; +const harden = { + NONE: 0, // falsy + STRICT: 1, + LAX: 2, +}; +let hardened = harden.NONE; function createUnsafeBuffer(size) { zeroFill[0] = hardened ? 1 : 0; @@ -147,10 +152,13 @@ const bufferWarning = 'Buffer() is deprecated due to security and usability ' + function showFlaggedDeprecation() { if (hardened) { + if (hardened !== harden.LAX) { + throw new ERR_ASSERTION( + 'Unsafe Buffer() API is forbidden by Buffer strict hardening opt-in.' + ); + } if (bufferWarningAlreadyEmitted) return; - process.emitWarning( - bufferWarning + ' Buffer() will soon throw in hardened mode.', - 'DeprecationWarning', 'DEP0XXX'); + process.emitWarning(bufferWarning, 'DeprecationWarning', 'DEP0005'); bufferWarningAlreadyEmitted = true; return; } @@ -173,15 +181,20 @@ function showFlaggedDeprecation() { } // Calling this method does not affect existing buffers, only new ones. -Buffer.harden = function() { - if (hardened) return; +Buffer.harden = function({ strict = true } = {}) { + if (hardened) { + // So that params are not changed afterwards + throw new ERR_ASSERTION( + 'Buffer.harden can be called only once' + ); + } if (isInsideNodeModules()) { throw new ERR_ASSERTION( 'Buffer.harden() should be called only from the top-level module. ' + 'Calling Buffer.harden() from dependencies is not supported.' ); } - hardened = true; + hardened = strict ? harden.STRICT : harden.LAX; Object.defineProperty(Buffer, 'poolSize', { enumerable: true, get: () => 0, From 028b6ed5d32874c5d2815dd612b5cd6bec04b139 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D0=BA=D0=BE=D0=B2=D0=BE=D1=80=D0=BE=D0=B4=D0=B0=20?= =?UTF-8?q?=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0=20=D0=90=D0=BD=D0=B4=D1=80?= =?UTF-8?q?=D0=B5=D0=B5=D0=B2=D0=B8=D1=87?= Date: Wed, 31 Jul 2019 18:02:22 +0300 Subject: [PATCH 4/9] [squash] buffer: add more options (all enabled by default) --- lib/buffer.js | 50 ++++++++++++++++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 18 deletions(-) diff --git a/lib/buffer.js b/lib/buffer.js index a4174921412416..ceb6cfe896e7e6 100644 --- a/lib/buffer.js +++ b/lib/buffer.js @@ -113,15 +113,15 @@ const zeroFill = bindingZeroFill || [0]; // Hardening Buffer enables Buffer constructor runtime deprecation, // disables pooling, and enables mandratory zero-fill. This is more secure, but // has potential performance impact, depending on the usecase. -const harden = { - NONE: 0, // falsy - STRICT: 1, - LAX: 2, +const hardened = { + applied: false, + zeroFill: false, + disablePool: false, + throwOnUnsafe: false, }; -let hardened = harden.NONE; function createUnsafeBuffer(size) { - zeroFill[0] = hardened ? 1 : 0; + zeroFill[0] = hardened.zeroFill ? 1 : 0; try { return new FastBuffer(size); } finally { @@ -151,8 +151,8 @@ const bufferWarning = 'Buffer() is deprecated due to security and usability ' + 'Buffer.allocUnsafe(), or Buffer.from() methods instead.'; function showFlaggedDeprecation() { - if (hardened) { - if (hardened !== harden.LAX) { + if (hardened.applied) { + if (hardened.throwOnUnsafe) { throw new ERR_ASSERTION( 'Unsafe Buffer() API is forbidden by Buffer strict hardening opt-in.' ); @@ -181,8 +181,13 @@ function showFlaggedDeprecation() { } // Calling this method does not affect existing buffers, only new ones. -Buffer.harden = function({ strict = true } = {}) { - if (hardened) { +Buffer.harden = function({ + zeroFill = true, + disablePool = true, + throwOnUnsafe = true, + freeze = true, +} = {}) { + if (hardened.applied) { // So that params are not changed afterwards throw new ERR_ASSERTION( 'Buffer.harden can be called only once' @@ -194,14 +199,23 @@ Buffer.harden = function({ strict = true } = {}) { 'Calling Buffer.harden() from dependencies is not supported.' ); } - hardened = strict ? harden.STRICT : harden.LAX; - Object.defineProperty(Buffer, 'poolSize', { - enumerable: true, - get: () => 0, - set: () => {}, + Object.assign(hardened, { + applied: true, + zeroFill: Boolean(zeroFill), + disablePool: Boolean(disablePool), + throwOnUnsafe: Boolean(throwOnUnsafe), }); - Object.freeze(Buffer); - Object.freeze(module.exports); + if (disablePool) { + Object.defineProperty(Buffer, 'poolSize', { + enumerable: true, + get: () => 0, + set: () => {}, + }); + } + if (freeze) { + Object.freeze(Buffer); + Object.freeze(module.exports); + } }; function toInteger(n, defaultVal) { @@ -412,7 +426,7 @@ function allocate(size) { if (size <= 0) { return new FastBuffer(); } - if (size < (Buffer.poolSize >>> 1) && !hardened) { + if (size < (Buffer.poolSize >>> 1) && !hardened.disablePool) { if (size > (poolSize - poolOffset)) createPool(); const b = new FastBuffer(allocPool, poolOffset, size); From 331a982f61b9833a2eb2fba6c8b2a9b80564cd8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D0=BA=D0=BE=D0=B2=D0=BE=D1=80=D0=BE=D0=B4=D0=B0=20?= =?UTF-8?q?=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0=20=D0=90=D0=BD=D0=B4=D1=80?= =?UTF-8?q?=D0=B5=D0=B5=D0=B2=D0=B8=D1=87?= Date: Wed, 31 Jul 2019 18:48:03 +0300 Subject: [PATCH 5/9] [squash] doc: add docs for Buffer.harden() --- doc/api/buffer.md | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/doc/api/buffer.md b/doc/api/buffer.md index ab7fcac31a7765..7453c8f3480b3d 100644 --- a/doc/api/buffer.md +++ b/doc/api/buffer.md @@ -919,6 +919,51 @@ const buf = Buffer.from(new Foo(), 'utf8'); A `TypeError` will be thrown if `object` has not mentioned methods or is not of other type appropriate for `Buffer.from()` variants. +### Class Method: Buffer.harden([options]) + + +* `options` {Object} + * `zeroFill` {boolean} + If `true`, all future `Buffer` instances, even the ones created with + `Buffer.allocUnsafe`, will be zero-filled by default. + **Default:** `true`. + * `disablePool` {boolean} + If `true`, all future `Buffer` allocations will not be pooled and their + underlying `ArrayBuffer` will have the same size as `Buffer` instance. + **Default:** `true`. + * `throwOnUnsafe` {boolean} + If `true`, unsafe `Buffer(arg)` API will throw instead of showing a + deprecation warning. + Otherwise, if `false`, first call to unsafe `Buffer(arg)` will trigger a + deprecation warning, regardless of whether it was called from inside of + `node_modules` directory or not (unlike the default warning). + **Default:** `true`. + * `freeze` {boolean} + If `true`, `Buffer` and `require('buffer')` objects are frozen and can + not be monkey-patched. + `Buffer.prototype` is not frozen for compatibility reasons. + **Default:** `true`. + +Calling `Buffer.harden()` enables additional security measures for Buffer API, +disabling some trade-offs between security and performance/compatibility that +are present by default. + +This method has effect only of subsequent Buffer API usage, Buffer instances +created before `Buffer.harden()` is called are not affected. + +Warning: for ecosystem compatibility and security reasons `Buffer.harden()` can +be called only once and only from the top-level application code. Attempting to +call it from a library in `node_modules` will throw. + +By default, it enables mandratory zero fill, disables Buffer pooling, disables +deprecated unsafe Buffer API, freezes `Buffer` and `require('buffer')` objects. + +The exact behaviour could be fine-tuned: for example, to still allow unsafe +Buffer API for compatibility reasons (e.g. if that us required by application +dependencies), `Buffer.haden({ throwOnUnsafe: false })` could be used. + ### Class Method: Buffer.from(string[, encoding]) +> Stability: 1 - Experimental + * `options` {Object} * `zeroFill` {boolean} If `true`, all future `Buffer` instances, even the ones created with From 587c8d03278daeeb8ee4f3a291e41b4576294c0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D0=BA=D0=BE=D0=B2=D0=BE=D1=80=D0=BE=D0=B4=D0=B0=20?= =?UTF-8?q?=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0=20=D0=90=D0=BD=D0=B4=D1=80?= =?UTF-8?q?=D0=B5=D0=B5=D0=B2=D0=B8=D1=87?= Date: Thu, 22 Aug 2019 04:25:24 +0300 Subject: [PATCH 9/9] [squash] Update doc/api/buffer.md Co-Authored-By: mscdex --- doc/api/buffer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api/buffer.md b/doc/api/buffer.md index 2b2f70756fd478..76cfd8bf155481 100644 --- a/doc/api/buffer.md +++ b/doc/api/buffer.md @@ -965,7 +965,7 @@ deprecated unsafe Buffer API, freezes `Buffer` and `require('buffer')` objects. The exact behaviour could be fine-tuned: for example, to still allow unsafe Buffer API for compatibility reasons (e.g. if that us required by application -dependencies), `Buffer.haden({ throwOnUnsafe: false })` could be used. +dependencies), `Buffer.harden({ throwOnUnsafe: false })` could be used. ### Class Method: Buffer.from(string[, encoding])