Description
Version: 10.9.0
Platform: Linux Ubuntu 3.13.0-36-generic
Subsystem: zlib
I've recently updated a large project from Node 8.11.3 to 10.9.0 but the service crashes after a few minutes because of memory exhaustion (4GB RAM). I've narrowed down the issue to the use of the asynchronous zlib.inflate method if the input isn't a valid input.
I've been able to create a small script which shows the issue
const crypto = require('crypto'), zlib = require('zlib');
const LOOPS = 10 * 1000;
let body = process.argv[2] === "valid" ? zlib.deflateSync(crypto.randomBytes(1024)) :
crypto.randomBytes(1024);
let totalRuns = 0;
let initialRSS = process.memoryUsage().rss;
setInterval(() => {
for (let i = 0; i < LOOPS; i++) {
zlib.inflate(body, (error, result) => { });
}
totalRuns += LOOPS
let currentRSS = process.memoryUsage().rss;
console.log(totalRuns, currentRSS, currentRSS - initialRSS);
}, 100);
When using Node 8.11.3 I get the following output from that script, you can see memory usage levels out around 280MiB.
Loops ** Current RSS** RSS Increase
10000 150556672 132362240
100000 281853952 263659520
200000 294563840 276369408
1000000 297381888 279158784
Switching to Node 10.9.0 I get the following output
Loops ** Current RSS** RSS Increase
10000 149655552 130969600
100000 790159360 771473408
200000 1477070848 1458384896
After 200k iterations the 8.11.3 version uses 280MiB but the 10.9.0 version is using 1400MiB of memory. After about 300k iterations using 10.9.0 the process crashes because it runs out of RAM.
Running the script with the "valid" parameter to send zlib a valid stream shows an increase in memory but it's small and eventually levels out and doesn't run out of memory.
node memoryleak.js valid
10000 156794880 137863168
100000 505155584 486223872
200000 546017280 527085568
I've corrected our service so that we check for invalid streams before trying to inflate them but I thought that there is potential for a denial of service against any service which uses zlib.inflate to decompress user input.