Copilot commented on code in PR #3852:
URL: https://github.com/apache/avro/pull/3852#discussion_r3564408475


##########
lang/js/lib/files.js:
##########
@@ -187,12 +208,40 @@ BlockDecoder.prototype._decodeHeader = function () {
   }
 
   var codec = (header.meta['avro.codec'] || 'null').toString();
-  this._decompress = (this._codecs || BlockDecoder.getDefaultCodecs())[codec];
-  if (!this._decompress) {
+  var decompress = (this._codecs || BlockDecoder.getDefaultCodecs())[codec];
+  if (!decompress) {
     this.emit('error', new Error(f('unknown codec: %s', codec)));
     return;
   }
 
+  // Bound the decompressed size of each block to guard against a block with a
+  // very high compression ratio (or a malformed block) expanding to far more
+  // memory than its compressed size. For the built-in deflate codec the limit
+  // is passed to zlib so the allocation itself is capped; every codec's output
+  // is additionally checked as a safeguard.
+  var maxLength = this._maxDecompressLength;
+  if (!this._codecs && codec === 'deflate') {
+    decompress = function (buf, cb) {
+      zlib.inflateRaw(buf, {maxOutputLength: maxLength}, cb);
+    };
+  }

Review Comment:
   The zlib `maxOutputLength` cap is only applied when `opts.codecs` is not 
provided. If a caller supplies a custom codecs map that includes `deflate: 
zlib.inflateRaw` (common when extending the default codecs), the allocation cap 
is silently disabled and only the post-decompress length check remains (too 
late to prevent decompression-bomb allocations). Consider applying the zlib cap 
whenever the selected decompressor is `zlib.inflateRaw`, regardless of whether 
it came from the default map or user-supplied codecs.



##########
lang/js/lib/files.js:
##########
@@ -187,12 +208,40 @@ BlockDecoder.prototype._decodeHeader = function () {
   }
 
   var codec = (header.meta['avro.codec'] || 'null').toString();
-  this._decompress = (this._codecs || BlockDecoder.getDefaultCodecs())[codec];
-  if (!this._decompress) {
+  var decompress = (this._codecs || BlockDecoder.getDefaultCodecs())[codec];
+  if (!decompress) {
     this.emit('error', new Error(f('unknown codec: %s', codec)));
     return;
   }
 
+  // Bound the decompressed size of each block to guard against a block with a
+  // very high compression ratio (or a malformed block) expanding to far more
+  // memory than its compressed size. For the built-in deflate codec the limit
+  // is passed to zlib so the allocation itself is capped; every codec's output
+  // is additionally checked as a safeguard.
+  var maxLength = this._maxDecompressLength;
+  if (!this._codecs && codec === 'deflate') {
+    decompress = function (buf, cb) {
+      zlib.inflateRaw(buf, {maxOutputLength: maxLength}, cb);
+    };
+  }
+  this._decompress = function (buf, cb) {
+    decompress(buf, function (err, data) {
+      if (err) {
+        cb(err);
+        return;
+      }
+      if (data && data.length > maxLength) {
+        cb(new Error(f(
+          'decompressed block size exceeds the maximum allowed of %d bytes',
+          maxLength
+        )));
+        return;
+      }
+      cb(null, data);
+    });

Review Comment:
   When the new max-length safeguard triggers (or any decompressor returns an 
error), the error is surfaced via `_createBlockCallback` which currently only 
`emit('error', err)` and returns. That leaves `_pending` decremented only on 
the success path and does not stop further decoding, which can cause the stream 
to hang in a pending state and potentially emit multiple errors for subsequent 
blocks. Consider updating the error path to decrement `_pending` and 
`destroy(err)` (or otherwise stop processing) on the first decompression/size 
error.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to