Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

zlib: add maxOutputLength option #33516

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions doc/api/zlib.md
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,9 @@ These advanced options are available for controlling decompression:
<!-- YAML
added: v0.11.1
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/33516
description: The `maxOutputLength` option is supported now.
- version: v9.4.0
pr-url: https://github.com/nodejs/node/pull/16042
description: The `dictionary` option can be an `ArrayBuffer`.
Expand Down Expand Up @@ -512,13 +515,19 @@ ignored by the decompression classes.
* `dictionary` {Buffer|TypedArray|DataView|ArrayBuffer} (deflate/inflate only,
empty dictionary by default)
* `info` {boolean} (If `true`, returns an object with `buffer` and `engine`.)
* `maxOutputLength` {integer} Limits output size when using
[convenience methods][]. **Default:** [`buffer.kMaxLength`][]

See the [`deflateInit2` and `inflateInit2`][] documentation for more
information.

## Class: `BrotliOptions`
<!-- YAML
added: v11.7.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/33516
description: The `maxOutputLength` option is supported now.
-->

<!--type=misc-->
Expand All @@ -529,6 +538,8 @@ Each Brotli-based class takes an `options` object. All options are optional.
* `finishFlush` {integer} **Default:** `zlib.constants.BROTLI_OPERATION_FINISH`
* `chunkSize` {integer} **Default:** `16 * 1024`
* `params` {Object} Key-value object containing indexed [Brotli parameters][].
* `maxOutputLength` {integer} Limits output size when using
[convenience methods][]. **Default:** [`buffer.kMaxLength`][]

For example:

Expand Down Expand Up @@ -1140,6 +1151,7 @@ Decompress a chunk of data with [`Unzip`][].
[`BrotliCompress`]: #zlib_class_zlib_brotlicompress
[`BrotliDecompress`]: #zlib_class_zlib_brotlidecompress
[`Buffer`]: buffer.html#buffer_class_buffer
[`buffer.kMaxLength`]: buffer.html#buffer_buffer_kmaxlength
[`Content-Encoding`]: https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11
[`DataView`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView
[`DeflateRaw`]: #zlib_class_zlib_deflateraw
Expand All @@ -1157,5 +1169,6 @@ Decompress a chunk of data with [`Unzip`][].
[Memory Usage Tuning]: #zlib_memory_usage_tuning
[RFC 7932]: https://www.rfc-editor.org/rfc/rfc7932.txt
[Streams API]: stream.md
[convenience methods]: #zlib_convenience_methods
[zlib documentation]: https://zlib.net/manual.html#Constants
[zlib.createGzip example]: #zlib_zlib
4 changes: 1 addition & 3 deletions lib/internal/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,6 @@ const kTypes = [
'symbol'
];

const { kMaxLength } = internalBinding('buffer');

const MainContextError = Error;
const ErrorToString = Error.prototype.toString;
const overrideStackTrace = new WeakMap();
Expand Down Expand Up @@ -747,7 +745,7 @@ E('ERR_BUFFER_OUT_OF_BOUNDS',
return 'Attempt to access memory outside buffer bounds';
}, RangeError);
E('ERR_BUFFER_TOO_LARGE',
`Cannot create a Buffer larger than 0x${kMaxLength.toString(16)} bytes`,
'Cannot create a Buffer larger than %s bytes',
RangeError);
E('ERR_CANNOT_WATCH_SIGINT', 'Cannot watch for SIGINT signals', Error);
E('ERR_CHILD_CLOSED_BEFORE_REPLY',
Expand Down
25 changes: 18 additions & 7 deletions lib/zlib.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,11 @@ function zlibBufferOnData(chunk) {
else
this.buffers.push(chunk);
this.nread += chunk.length;
if (this.nread > this._maxOutputLength) {
this.close();
this.removeAllListeners('end');
this.cb(new ERR_BUFFER_TOO_LARGE(this._maxOutputLength));
}
}

function zlibBufferOnError(err) {
Expand All @@ -134,9 +139,7 @@ function zlibBufferOnError(err) {
function zlibBufferOnEnd() {
let buf;
let err;
if (this.nread >= kMaxLength) {
err = new ERR_BUFFER_TOO_LARGE();
} else if (this.nread === 0) {
if (this.nread === 0) {
buf = Buffer.alloc(0);
} else {
const bufs = this.buffers;
Expand Down Expand Up @@ -231,6 +234,7 @@ const checkRangesOrGetDefault = hideStackFrames(
// The base class for all Zlib-style streams.
function ZlibBase(opts, mode, handle, { flush, finishFlush, fullFlush }) {
let chunkSize = Z_DEFAULT_CHUNK;
let maxOutputLength = kMaxLength;
// The ZlibBase class is not exported to user land, the mode should only be
// passed in by us.
assert(typeof mode === 'number');
Expand All @@ -253,6 +257,10 @@ function ZlibBase(opts, mode, handle, { flush, finishFlush, fullFlush }) {
opts.finishFlush, 'options.finishFlush',
Z_NO_FLUSH, Z_BLOCK, finishFlush);

maxOutputLength = checkRangesOrGetDefault(
opts.maxOutputLength, 'options.maxOutputLength',
1, kMaxLength, kMaxLength);

if (opts.encoding || opts.objectMode || opts.writableObjectMode) {
opts = { ...opts };
opts.encoding = null;
Expand All @@ -276,6 +284,7 @@ function ZlibBase(opts, mode, handle, { flush, finishFlush, fullFlush }) {
this._finishFlushFlag = finishFlush;
this._defaultFullFlushFlag = fullFlush;
this._info = opts && opts.info;
this._maxOutputLength = maxOutputLength;
}
ObjectSetPrototypeOf(ZlibBase.prototype, Transform.prototype);
ObjectSetPrototypeOf(ZlibBase, Transform);
Expand Down Expand Up @@ -450,6 +459,12 @@ function processChunkSync(self, chunk, flushFlag) {
else
buffers.push(out);
nread += out.byteLength;

if (nread > self._maxOutputLength) {
_close(self);
throw new ERR_BUFFER_TOO_LARGE(self._maxOutputLength);
}

} else {
assert(have === 0, 'have should not go down');
}
Expand All @@ -476,10 +491,6 @@ function processChunkSync(self, chunk, flushFlag) {
self.bytesWritten = inputRead;
_close(self);

if (nread >= kMaxLength) {
throw new ERR_BUFFER_TOO_LARGE();
}

if (nread === 0)
return Buffer.alloc(0);

Expand Down
2 changes: 1 addition & 1 deletion test/parallel/test-zlib-brotli-kmaxlength-rangeerror.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const assert = require('assert');
// large Buffers.
const buffer = require('buffer');
const oldkMaxLength = buffer.kMaxLength;
buffer.kMaxLength = 128;
buffer.kMaxLength = 64;
const zlib = require('zlib');
buffer.kMaxLength = oldkMaxLength;

Expand Down
2 changes: 1 addition & 1 deletion test/parallel/test-zlib-kmaxlength-rangeerror.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const assert = require('assert');
// large Buffers.
const buffer = require('buffer');
const oldkMaxLength = buffer.kMaxLength;
buffer.kMaxLength = 128;
buffer.kMaxLength = 64;
const zlib = require('zlib');
buffer.kMaxLength = oldkMaxLength;

Expand Down
25 changes: 25 additions & 0 deletions test/parallel/test-zlib-maxOutputLength.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const zlib = require('zlib');

const encoded = Buffer.from('G38A+CXCIrFAIAM=', 'base64');

// Async
zlib.brotliDecompress(encoded, { maxOutputLength: 64 }, common.expectsError({
code: 'ERR_BUFFER_TOO_LARGE',
message: 'Cannot create a Buffer larger than 64 bytes'
}));

// Sync
assert.throws(function() {
zlib.brotliDecompressSync(encoded, { maxOutputLength: 64 });
}, RangeError);

// Async
zlib.brotliDecompress(encoded, { maxOutputLength: 256 }, function(err) {
rosaxxny marked this conversation as resolved.
Show resolved Hide resolved
assert.strictEqual(err, null);
});
Comment on lines +20 to +22
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
zlib.brotliDecompress(encoded, { maxOutputLength: 256 }, function(err) {
assert.strictEqual(err, null);
});
zlib.brotliDecompress(encoded, { maxOutputLength: 256 }, assert.ifError);


// Sync
zlib.brotliDecompressSync(encoded, { maxOutputLength: 256 });