Skip to content

Commit 928981d

Browse files
Ic3b3rgaduh95
authored andcommitted
zlib: validate flush kind for brotli streams
BrotliCompress/Decompress.flush(kind) forwarded any value to the native layer, causing a 100% CPU hang for kinds outside the brotli operation range (e.g. Z_FINISH). Validate against [0, 3] and throw ERR_OUT_OF_RANGE on invalid input. Fixes: #63701 Signed-off-by: Ic3b3rg <s.ceccarini94@gmail.com> PR-URL: #63746 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: James M Snell <jasnell@gmail.com>
1 parent 8b060a9 commit 928981d

2 files changed

Lines changed: 82 additions & 0 deletions

File tree

‎lib/zlib.js‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,7 @@ function ZlibBase(opts, mode, handle, { flush, finishFlush, fullFlush }) {
276276
this._defaultFlushFlag=flush;
277277
this._finishFlushFlag=finishFlush;
278278
this._defaultFullFlushFlag=fullFlush;
279+
this._flushBoundIdx=flushBoundIdx;
279280
this._info=opts?.info;
280281
this._maxOutputLength=maxOutputLength;
281282

@@ -357,6 +358,18 @@ ZlibBase.prototype.flush = function(kind, callback) {
357358
kind=this._defaultFullFlushFlag;
358359
}
359360

361+
// Reject kinds outside the brotli operation range. Otherwise the fake-chunk
362+
// path forwards an unsupported flush flag to the native encoder/decoder,
363+
// which spins at 100% CPU without making progress (see #63701).
364+
if(this._flushBoundIdx===FLUSH_BOUND_IDX_BROTLI){
365+
constmin=FLUSH_BOUND[FLUSH_BOUND_IDX_BROTLI][0];
366+
constmax=FLUSH_BOUND[FLUSH_BOUND_IDX_BROTLI][1];
367+
if(typeofkind!=='number'||NumberIsNaN(kind)||
368+
kind<min||kind>max){
369+
thrownewERR_OUT_OF_RANGE('kind',`>= ${min} and <= ${max}`,kind);
370+
}
371+
}
372+
360373
if(this.writableFinished){
361374
if(callback)
362375
process.nextTick(callback);
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
'use strict';
2+
// Regression test for https://github.com/nodejs/node/issues/63701
3+
// BrotliCompress/BrotliDecompress.flush(kind) used to spin at 100% CPU when
4+
// kind was outside the brotli operation range (0..3) — e.g. Z_FINISH (4) or
5+
// Z_BLOCK (5). It must now throw ERR_OUT_OF_RANGE before reaching the native
6+
// layer.
7+
8+
require('../common');
9+
constassert=require('assert');
10+
constzlib=require('zlib');
11+
12+
const{
13+
BROTLI_OPERATION_PROCESS,
14+
BROTLI_OPERATION_FLUSH,
15+
BROTLI_OPERATION_FINISH,
16+
BROTLI_OPERATION_EMIT_METADATA,
17+
Z_BLOCK,
18+
Z_FINISH,
19+
}=zlib.constants;
20+
21+
// Brotli operations 0..3 are valid and must not throw synchronously from
22+
// flush(). Some operations (e.g. FINISH on a decoder with no input) emit a
23+
// Z_BUF_ERROR asynchronously — that's expected and unrelated to the input
24+
// validation under test, so we attach a noop error handler.
25+
for(constvalidKindof[
26+
BROTLI_OPERATION_PROCESS,
27+
BROTLI_OPERATION_FLUSH,
28+
BROTLI_OPERATION_FINISH,
29+
BROTLI_OPERATION_EMIT_METADATA,
30+
]){
31+
constc=zlib.createBrotliCompress();
32+
c.on('error',()=>{});
33+
c.flush(validKind);
34+
35+
constd=zlib.createBrotliDecompress();
36+
d.on('error',()=>{});
37+
d.flush(validKind);
38+
}
39+
40+
// Values outside [0, 3] must throw ERR_OUT_OF_RANGE for both compress and
41+
// decompress streams. Z_FINISH (4) and Z_BLOCK (5) previously hung at 100% CPU.
42+
constoutOfRange=[-1,Z_FINISH,Z_BLOCK,6,7,100];
43+
44+
for(constfactoryof[zlib.createBrotliCompress,zlib.createBrotliDecompress]){
45+
for(constkindofoutOfRange){
46+
assert.throws(
47+
()=>factory().flush(kind),
48+
{code: 'ERR_OUT_OF_RANGE',name: 'RangeError'},
49+
);
50+
}
51+
}
52+
53+
// Non-number kinds must also throw, instead of silently triggering the
54+
// fake-chunk path with an undefined buffer.
55+
for(constfactoryof[zlib.createBrotliCompress,zlib.createBrotliDecompress]){
56+
for(constkindof['foobar',null,{},NaN]){
57+
assert.throws(
58+
()=>factory().flush(kind),
59+
{code: 'ERR_OUT_OF_RANGE'},
60+
);
61+
}
62+
}
63+
64+
// flush() with no arguments (or with only a callback) must still work —
65+
// it uses the stream's _defaultFullFlushFlag, which is a valid brotli op.
66+
zlib.createBrotliCompress().flush();
67+
zlib.createBrotliCompress().flush(()=>{});
68+
zlib.createBrotliDecompress().flush();
69+
zlib.createBrotliDecompress().flush(()=>{});

0 commit comments

Comments
 (0)