Browse Source

zlib: decompression throw on truncated input

Check for unexpected end-of-file error when decompressing. If the output
buffer still has space after decompressing and deflate returned Z_OK or
Z_BUF_ERROR - that means unexpected end-of-file. Added
test-zlib-truncated.js for the case of truncated input. Fixed the zlib
dictionary test to not end the inflate stream on a truncated output (no
crc) of deflate

Fixes: https://github.com/nodejs/node/issues/2043
PR-URL: https://github.com/nodejs/node/pull/2595
Reviewed-By: Trevor Norris <trev.norris@gmail.com>
v5.x
Yuval Brik 10 years ago
committed by Rod Vagg
parent
commit
80169b1f0a
  1. 6
      src/node_zlib.cc
  2. 81
      test/parallel/test-zlib-dictionary.js
  3. 49
      test/parallel/test-zlib-truncated.js

6
src/node_zlib.cc

@ -271,8 +271,12 @@ class ZCtx : public AsyncWrap {
// Acceptable error states depend on the type of zlib stream. // Acceptable error states depend on the type of zlib stream.
switch (ctx->err_) { switch (ctx->err_) {
case Z_OK: case Z_OK:
case Z_STREAM_END:
case Z_BUF_ERROR: case Z_BUF_ERROR:
if (ctx->strm_.avail_out != 0 && ctx->flush_ == Z_FINISH) {
ZCtx::Error(ctx, "unexpected end of file");
return false;
}
case Z_STREAM_END:
// normal statuses, not fatal // normal statuses, not fatal
break; break;
case Z_NEED_DICT: case Z_NEED_DICT:

81
test/parallel/test-zlib-dictionary.js

@ -1,12 +1,12 @@
'use strict'; 'use strict';
// test compression/decompression with dictionary // test compression/decompression with dictionary
var common = require('../common'); const common = require('../common');
var assert = require('assert'); const assert = require('assert');
var zlib = require('zlib'); const zlib = require('zlib');
var path = require('path'); const path = require('path');
var spdyDict = new Buffer([ const spdyDict = new Buffer([
'optionsgetheadpostputdeletetraceacceptaccept-charsetaccept-encodingaccept-', 'optionsgetheadpostputdeletetraceacceptaccept-charsetaccept-encodingaccept-',
'languageauthorizationexpectfromhostif-modified-sinceif-matchif-none-matchi', 'languageauthorizationexpectfromhostif-modified-sinceif-matchif-none-matchi',
'f-rangeif-unmodifiedsincemax-forwardsproxy-authorizationrangerefererteuser', 'f-rangeif-unmodifiedsincemax-forwardsproxy-authorizationrangerefererteuser',
@ -22,54 +22,69 @@ var spdyDict = new Buffer([
'.1statusversionurl\0' '.1statusversionurl\0'
].join('')); ].join(''));
var deflate = zlib.createDeflate({ dictionary: spdyDict }); const input = [
var input = [
'HTTP/1.1 200 Ok', 'HTTP/1.1 200 Ok',
'Server: node.js', 'Server: node.js',
'Content-Length: 0', 'Content-Length: 0',
'' ''
].join('\r\n'); ].join('\r\n');
var called = 0; function basicDictionaryTest() {
let output = '';
// const deflate = zlib.createDeflate({ dictionary: spdyDict });
// We'll use clean-new inflate stream each time const inflate = zlib.createInflate({ dictionary: spdyDict });
// and .reset() old dirty deflate one
//
function run(num) {
var inflate = zlib.createInflate({ dictionary: spdyDict });
if (num === 2) {
deflate.reset();
deflate.removeAllListeners('data');
}
// Put data into deflate stream
deflate.on('data', function(chunk) { deflate.on('data', function(chunk) {
inflate.write(chunk); inflate.write(chunk);
}); });
// Get data from inflate stream
var output = [];
inflate.on('data', function(chunk) { inflate.on('data', function(chunk) {
output.push(chunk); output += chunk;
});
deflate.on('end', function() {
inflate.end();
}); });
inflate.on('end', function() { inflate.on('end', function() {
called++; assert.equal(input, output);
});
deflate.write(input);
deflate.end();
}
assert.equal(output.join(''), input); function deflateResetDictionaryTest() {
let doneReset = false;
let output = '';
const deflate = zlib.createDeflate({ dictionary: spdyDict });
const inflate = zlib.createInflate({ dictionary: spdyDict });
if (num < 2) run(num + 1); deflate.on('data', function(chunk) {
if (doneReset)
inflate.write(chunk);
});
inflate.on('data', function(chunk) {
output += chunk;
});
deflate.on('end', function() {
inflate.end();
});
inflate.on('end', function() {
assert.equal(input, output);
}); });
deflate.write(input); deflate.write(input);
deflate.flush(function() { deflate.flush(function() {
inflate.end(); deflate.reset();
doneReset = true;
deflate.write(input);
deflate.end();
}); });
} }
run(1);
process.on('exit', function() { basicDictionaryTest();
assert.equal(called, 2); deflateResetDictionaryTest();
});

49
test/parallel/test-zlib-truncated.js

@ -0,0 +1,49 @@
'use strict';
// tests zlib streams with truncated compressed input
const common = require('../common');
const assert = require('assert');
const zlib = require ('zlib');
const inputString = 'ΩΩLorem ipsum dolor sit amet, consectetur adipiscing el' +
'it. Morbi faucibus, purus at gravida dictum, libero arcu convallis la' +
'cus, in commodo libero metus eu nisi. Nullam commodo, neque nec porta' +
' placerat, nisi est fermentum augue, vitae gravida tellus sapien sit ' +
'amet tellus. Aenean non diam orci. Proin quis elit turpis. Suspendiss' +
'e non diam ipsum. Suspendisse nec ullamcorper odio. Vestibulum arcu m' +
'i, sodales non suscipit id, ultrices ut massa. Sed ac sem sit amet ar' +
'cu malesuada fermentum. Nunc sed. ';
[
{ comp: 'gzip', decomp: 'gunzip', decompSync: 'gunzipSync' },
{ comp: 'gzip', decomp: 'unzip', decompSync: 'unzipSync' },
{ comp: 'deflate', decomp: 'inflate', decompSync: 'inflateSync' },
{ comp: 'deflateRaw', decomp: 'inflateRaw', decompSync: 'inflateRawSync' }
].forEach(function(methods) {
zlib[methods.comp](inputString, function(err, compressed) {
assert(!err);
let truncated = compressed.slice(0, compressed.length / 2);
// sync sanity
assert.doesNotThrow(function() {
let decompressed = zlib[methods.decompSync](compressed);
assert.equal(decompressed, inputString);
});
// async sanity
zlib[methods.decomp](compressed, function(err, result) {
assert.ifError(err);
assert.equal(result, inputString);
});
// sync truncated input test
assert.throws(function() {
zlib[methods.decompSync](truncated);
}, /unexpected end of file/);
// async truncated input test
zlib[methods.decomp](truncated, function(err, result) {
assert(/unexpected end of file/.test(err.message));
});
});
});
Loading…
Cancel
Save