CVE-2022-21208 (JSON, published 2022-08-23T05:15Z)
Description: value: The package node-opcua before 2.74.0 are vulnerable to Denial of Service (DoS) due to a missing limitation on the number of received chunks - per single session or in total for all concurrent sessions. An attacker can exploit this vulnerability by sending an unlimited number of huge chunks (e.g. 2GB each) without sending the Final closing chunk.
Severity: nan (CVSS v3 base score: 7.5)
Weakness: CWE-1284 \u2014 Improper Validation of Specified Quantity in Input
The product receives input that is expected to specify a quantity (such as size or length), but it does not validate or incorrectly validates that the quantity has the required properties.
Repository: https://github.com/node-opcua/node-opcua
Files changed:
packages/node-opcua-packet-assembler/package.json
packages/node-opcua-packet-assembler/source/packet_assembler.ts
packages/node-opcua-packet-assembler/test/test_packet_assembler.js
Commit message: PacketAssembler add chunksize verification & refactor


Vulnerable code:
        "node-opcua-assert": "2.66.0"
export type ReadMessageFuncType = (data: Buffer) => PacketInfo;
    readMessageFunc: ReadMessageFuncType;

    // the minimum number of bytes that need to be received before the readMessageFunc can be called
    private readonly readMessageFunc: ReadMessageFuncType;
        this.readMessageFunc = options.readMessageFunc;
        assert(typeof this.readMessageFunc === "function", "packet assembler requires a readMessageFunc");
            this.expectedLength = this.packetInfo.length;
            assert(this.expectedLength > 0);
            this.emit("newMessage", this.packetInfo, data);
            this.emit("message", messageChunk);
        return this.readMessageFunc(data);
    private _buildData(data: Buffer) {
const PacketAssembler = require("..").PacketAssembler;
function makeMessage(msgType, length) {

function readerHeader(data) {
    return {length: length, extra: msgType};


    it("should assemble a single packet", function (done) {


        const packet_assembler = new PacketAssembler({readMessageFunc: readerHeader, minimumSizeInBytes: 5})
            .on("message", function (message) {

                const info = readerHeader(message);
                info.length.should.equal(message.length);
            });

        packet_assembler.feed(makeMessage("A", 200));

    it("should assemble a message sent over several packets", function (done) {

        const packet_assembler = new PacketAssembler({readMessageFunc: readerHeader, minimumSizeInBytes: 5})
            .on("message", function (message) {

                const info = readerHeader(message);
                info.length.should.equal(message.length);
            });

        const message1 = makeMessage("A", 2000);
        const packet1 = message1.slice(0, 100);
        const packet2 = message1.slice(100, 200);
        const packet3 = message1.slice(200);
        packet_assembler.feed(packet1);
        packet_assembler.feed(packet2);
        packet_assembler.feed(packet3);
    it("should assemble a message sent one byte at a time", function (done) {

        const packet_assembler = new PacketAssembler({readMessageFunc: readerHeader, minimumSizeInBytes: 5})
            .on("message", function (message) {

                const info = readerHeader(message);
                info.length.should.equal(message.length);

                done();
            });

        const message = makeMessage("A", 200);

        for (let i = 0; i < message.length; i++) {
            const single_byte_chunk = message.slice(i, i + 1);
            packet_assembler.feed(single_byte_chunk);
    });
    it("should deal with packets containing data from 2 different messages", function (done) {
        const packet_assembler = new PacketAssembler({readMessageFunc: readerHeader, minimumSizeInBytes: 5})
            .on("message", function (message) {

                const info = readerHeader(message);
                info.length.should.equal(message.length);
                    done();
            });
        const message1 = makeMessage("A", 200);
        const message2 = makeMessage("B", 200);
        const packet1 = message1.slice(0, 150);
        const packet2_a = message1.slice(150);
        const packet2_b = message2.slice(0, 150);
        const packet3 = message2.slice(150);
        packet_assembler.feed(packet1);
        packet_assembler.feed(packet2);
        packet_assembler.feed(packet3);

Fixed code:
        "node-opcua-assert": "2.66.0",
        "node-opcua-debug": "2.66.0"
import { make_warningLog } from "node-opcua-debug";
const warningLog = make_warningLog("PacketAssembler");
export type ReadChunkFuncType = (data: Buffer) => PacketInfo;
    readChunkFunc: ReadChunkFuncType;
    // the minimum number of bytes that need to be received before the readChunkFunc can be called
    maxChunkSize: number;
export interface PacketAssembler {
    on(eventName: "startChunk", eventHandler: (packetInfo: PacketInfo, partial: Buffer) => void): this;
    on(eventName: "chunk", eventHandler: (chunk: Buffer) => void): this;
    on(eventName: "error", eventHandler: (err: Error) => void): this;
}
/**
 * this class is used to assemble partial data from the tranport layer
 * into message chunks
 */
    public static defaultMaxChunkCount = 777;
    public static defaultMaxMessageSize = 1024 * 64 - 7;


    private maxChunkSize: number;

    private readonly readChunkFunc: ReadChunkFuncType;
        this.readChunkFunc = options.readChunkFunc;
        assert(typeof this.readChunkFunc === "function", "packet assembler requires a readChunkFunc");

        // istanbul ignore next
        assert(options.maxChunkSize === undefined || options.maxChunkSize !== 0);

        this.maxChunkSize = options.maxChunkSize || PacketAssembler.defaultMaxMessageSize;
        assert(this.maxChunkSize >= this.minimumSizeInBytes);

            if (this.packetInfo.length < this.minimumSizeInBytes) {
                this.emit("error", new Error("maximum message size exceeded"));
                return;
            }
            if (this.packetInfo.length > this.maxChunkSize) {
                const message = `maximum chunk size exceeded (maxChunkSize=${this.maxChunkSize} current chunk size = ${this.packetInfo.length})`;
                warningLog(message);
                this.emit("error", new Error(message));
                return;
            }
            this.emit("startChunk", this.packetInfo, data);
            this.expectedLength = this.packetInfo.length;
            this.emit("chunk", messageChunk);
        return this.readChunkFunc(data);
    private _buildData(data: Buffer): Buffer {
const sinon = require("sinon");
const { PacketAssembler } = require("..");
function makeChunk(msgType, length) {
function readChunkHeader(data) {
    return { length: length, extra: msgType };
    
    it("should assemble a single packet", (done) => {
        const packetAssembler = new PacketAssembler({ readChunkFunc: readChunkHeader, minimumSizeInBytes: 5 });
        packetAssembler.on(
            "chunk",
            (chunk) => {
                const info = readChunkHeader(chunk);
                info.length.should.equal(chunk.length);
            }
        );
        packetAssembler.feed(makeChunk("A", 200));
    it("should assemble a chunk sent over several packets", (done) => {
        const packetAssembler = new PacketAssembler({ readChunkFunc: readChunkHeader, minimumSizeInBytes: 5 });
        packetAssembler.on(
            "chunk",
            (chunk) => {
                const info = readChunkHeader(chunk);
                info.length.should.equal(chunk.length);
            }
        );
        const chunk1 = makeChunk("A", 2000);
        const packet1 = chunk1.slice(0, 100);
        const packet2 = chunk1.slice(100, 200);
        const packet3 = chunk1.slice(200);
        packetAssembler.feed(packet1);
        packetAssembler.feed(packet2);
        packetAssembler.feed(packet3);
    it("should assemble a chunk sent one byte at a time", () => {
        const packetAssembler = new PacketAssembler({ 
            readChunkFunc: readChunkHeader, 
            minimumSizeInBytes: 5,
            maxChunkCount: 10000
        });
        packetAssembler.on(
            "chunk",
            (chunk) => {
                const info = readChunkHeader(chunk);
                info.length.should.equal(chunk.length);
            }
        );
        const onChunkSpy = sinon.spy();
        const errorSpy = sinon.spy();
        packetAssembler.on("chunk", onChunkSpy);
        packetAssembler.on("error", errorSpy);


        const chunk = makeChunk("A", 200);

        for (let i = 0; i < chunk.length; i++) {
            const single_byte_chunk = chunk.slice(i, i + 1);
            packetAssembler.feed(single_byte_chunk);
        errorSpy.callCount.should.equal(0);
        onChunkSpy.callCount.should.equal(1);
    });
    it("should deal with packets containing data from 2 different chunks", () => {
        
        const packetAssembler = new PacketAssembler({ 
            readChunkFunc: readChunkHeader, 
            minimumSizeInBytes: 5 
        });
        
        packetAssembler.on(
            "chunk",
            (chunk) => {
                const info = readChunkHeader(chunk);
                info.length.should.equal(chunk.length);
            }
        );

        const onChunkSpy = sinon.spy();
        const errorSpy = sinon.spy();
        packetAssembler.on("chunk", onChunkSpy);
        packetAssembler.on("error", errorSpy);
        const chunk1 = makeChunk("A", 200);
        const chunk2 = makeChunk("B", 200);

        const packet1 = chunk1.slice(0, 150);
        const packet2_a = chunk1.slice(150);
        const packet2_b = chunk2.slice(0, 150);
        const packet3 = chunk2.slice(150);

        packetAssembler.feed(packet1);
        packetAssembler.feed(packet2);
        packetAssembler.feed(packet3);
        errorSpy.callCount.should.equal(0);
        onChunkSpy.callCount.should.equal(2);

    });
    it("limits: max chunk size exceeded", () => {
        const packetAssembler = new PacketAssembler({
            readChunkFunc: readChunkHeader,
            minimumSizeInBytes: 5,
            maxChunkSize: 100
        });

        const onChunkSpy = sinon.spy();
        const errorSpy = sinon.spy();
        packetAssembler.on("chunk", onChunkSpy);
        packetAssembler.on("error", errorSpy);

        let counter = 0;
        packetAssembler.on("chunk", (chunk) => {
            const info = readChunkHeader(chunk);
            info.length.should.equal(chunk.length);
            info.length.should.equal(200 + 5);
            counter += 1;
            if (counter === 1) {
                info.extra.should.equal("A");
            }
            if (counter === 2) {
                info.extra.should.equal("B");
            }
        });
        let errorCount = 0;
        packetAssembler.on("error", (err) => {
            err.message.should.match(/maximum chunk size exceeded/);
            errorCount += 1;
        });
        const chunk1 = makeChunk("A", 200);
        const chunk2 = makeChunk("B", 200);
        packetAssembler.feed(chunk1);
        packetAssembler.feed(chunk2);

        errorCount.should.eql(2);

        onChunkSpy.callCount.should.eql(0);
        errorSpy.callCount.should.eql(2);