Skip to content

Commit 33b22d7

Browse files
rosaxxnyaddaleax
authored andcommitted
zlib: add maxOutputLength option
Fixes: #27253 PR-URL: #33516 Reviewed-By: Anna Henningsen <[email protected]>
1 parent 2302dff commit 33b22d7

6 files changed

+59
-10
lines changed

doc/api/zlib.md

+13
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,9 @@ These advanced options are available for controlling decompression:
486486
<!-- YAML
487487
added: v0.11.1
488488
changes:
489+
- version: REPLACEME
490+
pr-url: https://github.com/nodejs/node/pull/33516
491+
description: The `maxOutputLength` option is supported now.
489492
- version: v9.4.0
490493
pr-url: https://github.com/nodejs/node/pull/16042
491494
description: The `dictionary` option can be an `ArrayBuffer`.
@@ -514,13 +517,19 @@ ignored by the decompression classes.
514517
* `dictionary` {Buffer|TypedArray|DataView|ArrayBuffer} (deflate/inflate only,
515518
empty dictionary by default)
516519
* `info` {boolean} (If `true`, returns an object with `buffer` and `engine`.)
520+
* `maxOutputLength` {integer} Limits output size when using
521+
[convenience methods][]. **Default:** [`buffer.kMaxLength`][]
517522

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

521526
## Class: `BrotliOptions`
522527
<!-- YAML
523528
added: v11.7.0
529+
changes:
530+
- version: REPLACEME
531+
pr-url: https://github.com/nodejs/node/pull/33516
532+
description: The `maxOutputLength` option is supported now.
524533
-->
525534

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

535546
For example:
536547

@@ -1142,6 +1153,7 @@ Decompress a chunk of data with [`Unzip`][].
11421153
[`BrotliCompress`]: #zlib_class_zlib_brotlicompress
11431154
[`BrotliDecompress`]: #zlib_class_zlib_brotlidecompress
11441155
[`Buffer`]: buffer.html#buffer_class_buffer
1156+
[`buffer.kMaxLength`]: buffer.html#buffer_buffer_kmaxlength
11451157
[`Content-Encoding`]: https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11
11461158
[`DataView`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView
11471159
[`DeflateRaw`]: #zlib_class_zlib_deflateraw
@@ -1159,5 +1171,6 @@ Decompress a chunk of data with [`Unzip`][].
11591171
[Memory usage tuning]: #zlib_memory_usage_tuning
11601172
[RFC 7932]: https://www.rfc-editor.org/rfc/rfc7932.txt
11611173
[Streams API]: stream.md
1174+
[convenience methods]: #zlib_convenience_methods
11621175
[zlib documentation]: https://zlib.net/manual.html#Constants
11631176
[zlib.createGzip example]: #zlib_zlib

lib/internal/errors.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -763,7 +763,7 @@ E('ERR_BUFFER_OUT_OF_BOUNDS',
763763
return 'Attempt to access memory outside buffer bounds';
764764
}, RangeError);
765765
E('ERR_BUFFER_TOO_LARGE',
766-
`Cannot create a Buffer larger than 0x${kMaxLength.toString(16)} bytes`,
766+
'Cannot create a Buffer larger than %s bytes',
767767
RangeError);
768768
E('ERR_CANNOT_WATCH_SIGINT', 'Cannot watch for SIGINT signals', Error);
769769
E('ERR_CHILD_CLOSED_BEFORE_REPLY',

lib/zlib.js

+18-7
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,11 @@ function zlibBufferOnData(chunk) {
122122
else
123123
this.buffers.push(chunk);
124124
this.nread += chunk.length;
125+
if (this.nread > this._maxOutputLength) {
126+
this.close();
127+
this.removeAllListeners('end');
128+
this.cb(new ERR_BUFFER_TOO_LARGE(this._maxOutputLength));
129+
}
125130
}
126131

127132
function zlibBufferOnError(err) {
@@ -132,9 +137,7 @@ function zlibBufferOnError(err) {
132137
function zlibBufferOnEnd() {
133138
let buf;
134139
let err;
135-
if (this.nread >= kMaxLength) {
136-
err = new ERR_BUFFER_TOO_LARGE();
137-
} else if (this.nread === 0) {
140+
if (this.nread === 0) {
138141
buf = Buffer.alloc(0);
139142
} else {
140143
const bufs = this.buffers;
@@ -230,6 +233,7 @@ const checkRangesOrGetDefault = hideStackFrames(
230233
// The base class for all Zlib-style streams.
231234
function ZlibBase(opts, mode, handle, { flush, finishFlush, fullFlush }) {
232235
let chunkSize = Z_DEFAULT_CHUNK;
236+
let maxOutputLength = kMaxLength;
233237
// The ZlibBase class is not exported to user land, the mode should only be
234238
// passed in by us.
235239
assert(typeof mode === 'number');
@@ -252,6 +256,10 @@ function ZlibBase(opts, mode, handle, { flush, finishFlush, fullFlush }) {
252256
opts.finishFlush, 'options.finishFlush',
253257
Z_NO_FLUSH, Z_BLOCK, finishFlush);
254258

259+
maxOutputLength = checkRangesOrGetDefault(
260+
opts.maxOutputLength, 'options.maxOutputLength',
261+
1, kMaxLength, kMaxLength);
262+
255263
if (opts.encoding || opts.objectMode || opts.writableObjectMode) {
256264
opts = { ...opts };
257265
opts.encoding = null;
@@ -276,6 +284,7 @@ function ZlibBase(opts, mode, handle, { flush, finishFlush, fullFlush }) {
276284
this._defaultFullFlushFlag = fullFlush;
277285
this.once('end', _close.bind(null, this));
278286
this._info = opts && opts.info;
287+
this._maxOutputLength = maxOutputLength;
279288
}
280289
ObjectSetPrototypeOf(ZlibBase.prototype, Transform.prototype);
281290
ObjectSetPrototypeOf(ZlibBase, Transform);
@@ -445,6 +454,12 @@ function processChunkSync(self, chunk, flushFlag) {
445454
else
446455
buffers.push(out);
447456
nread += out.byteLength;
457+
458+
if (nread > self._maxOutputLength) {
459+
_close(self);
460+
throw new ERR_BUFFER_TOO_LARGE(self._maxOutputLength);
461+
}
462+
448463
} else {
449464
assert(have === 0, 'have should not go down');
450465
}
@@ -471,10 +486,6 @@ function processChunkSync(self, chunk, flushFlag) {
471486
self.bytesWritten = inputRead;
472487
_close(self);
473488

474-
if (nread >= kMaxLength) {
475-
throw new ERR_BUFFER_TOO_LARGE();
476-
}
477-
478489
if (nread === 0)
479490
return Buffer.alloc(0);
480491

test/parallel/test-zlib-brotli-kmaxlength-rangeerror.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const assert = require('assert');
1111
// large Buffers.
1212
const buffer = require('buffer');
1313
const oldkMaxLength = buffer.kMaxLength;
14-
buffer.kMaxLength = 128;
14+
buffer.kMaxLength = 64;
1515
const zlib = require('zlib');
1616
buffer.kMaxLength = oldkMaxLength;
1717

test/parallel/test-zlib-kmaxlength-rangeerror.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const assert = require('assert');
1111
// large Buffers.
1212
const buffer = require('buffer');
1313
const oldkMaxLength = buffer.kMaxLength;
14-
buffer.kMaxLength = 128;
14+
buffer.kMaxLength = 64;
1515
const zlib = require('zlib');
1616
buffer.kMaxLength = oldkMaxLength;
1717

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
'use strict';
2+
const common = require('../common');
3+
const assert = require('assert');
4+
const zlib = require('zlib');
5+
6+
const encoded = Buffer.from('G38A+CXCIrFAIAM=', 'base64');
7+
8+
// Async
9+
zlib.brotliDecompress(encoded, { maxOutputLength: 64 }, common.expectsError({
10+
code: 'ERR_BUFFER_TOO_LARGE',
11+
message: 'Cannot create a Buffer larger than 64 bytes'
12+
}));
13+
14+
// Sync
15+
assert.throws(function() {
16+
zlib.brotliDecompressSync(encoded, { maxOutputLength: 64 });
17+
}, RangeError);
18+
19+
// Async
20+
zlib.brotliDecompress(encoded, { maxOutputLength: 256 }, function(err) {
21+
assert.strictEqual(err, null);
22+
});
23+
24+
// Sync
25+
zlib.brotliDecompressSync(encoded, { maxOutputLength: 256 });

0 commit comments

Comments
 (0)