Skip to content

Commit 9d39f1f

Browse files
fix(parser): add a limit to the number of binary attachments
Backported from main: b25738c When a packet contains binary elements, the built-in parser does not modify them and simply sends them in their own WebSocket frame. Example: `socket.emit("some event", Buffer.of(1,2,3))` is encoded and transferred as: - 1st frame: 51-["some event",{"_placeholder":true,"num":0}] - 2nd frame: <buffer 01 02 03> where: - `5` is the type of the packet (binary message) - `1` is the number of binary attachments - `-` is the separator - `["some event",{"_placeholder":true,"num":0}]` is the payload (including the placeholder) On the receiving end, the parser reads the number of attachments and buffers them until they are all received. Before this change, the built-in parser accepted any number of binary attachments, which could be exploited to make the server run out of memory. The number of attachments is now limited to 10, which should be sufficient for most use cases. The limit can be increased with a custom `parser`: ```js import { Encoder, Decoder } from "socket.io-parser"; const io = new Server({ parser: { Encoder, Decoder: class extends Decoder { constructor() { super({ maxAttachments: 20 }); } } } }); ```
1 parent 6b2f875 commit 9d39f1f

File tree

2 files changed

+46
-4
lines changed

2 files changed

+46
-4
lines changed

index.js

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -218,8 +218,12 @@ function encodeAsBinary(obj, callback) {
218218
* @api public
219219
*/
220220

221-
function Decoder() {
221+
function Decoder(opts) {
222222
this.reconstructor = null;
223+
opts = opts || {};
224+
this.opts = {
225+
maxAttachments: opts.maxAttachments || 10,
226+
};
223227
}
224228

225229
/**
@@ -242,7 +246,7 @@ Decoder.prototype.add = function(obj) {
242246
if (this.reconstructor) {
243247
throw new Error("got plaintext data when reconstructing a packet");
244248
}
245-
packet = decodeString(obj);
249+
packet = decodeString(obj, this.opts.maxAttachments);
246250
if (exports.BINARY_EVENT === packet.type || exports.BINARY_ACK === packet.type) { // binary packet's json
247251
this.reconstructor = new BinaryReconstructor(packet);
248252

@@ -292,11 +296,12 @@ function isPayloadValid(type, payload) {
292296
* Decode a packet String (JSON data)
293297
*
294298
* @param {String} str
299+
* @param {Number} maxAttachments - the maximum number of binary attachments
295300
* @return {Object} packet
296301
* @api private
297302
*/
298303

299-
function decodeString(str) {
304+
function decodeString(str, maxAttachments) {
300305
var i = 0;
301306
// look up type
302307
var p = {
@@ -315,7 +320,13 @@ function decodeString(str) {
315320
if (buf != Number(buf) || str.charAt(i) !== '-') {
316321
throw new Error('Illegal attachments');
317322
}
318-
p.attachments = Number(buf);
323+
var n = Number(buf);
324+
if (!isInteger(n) || n < 0) {
325+
throw new Error("Illegal attachments");
326+
} else if (n > maxAttachments) {
327+
throw new Error("too many attachments");
328+
}
329+
p.attachments = n;
319330
}
320331

321332
// look up namespace (if any)
@@ -432,3 +443,13 @@ function error(msg) {
432443
data: 'parser error: ' + msg
433444
};
434445
}
446+
447+
var isInteger =
448+
Number.isInteger ||
449+
function (value) {
450+
return (
451+
typeof value === "number" &&
452+
isFinite(value) &&
453+
Math.floor(value) === value
454+
);
455+
};

test/parser.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,5 +101,26 @@ describe('parser', function(){
101101
isInvalidPayload('2[{"toString":"foo"}]');
102102
isInvalidPayload('2[true,"foo"]');
103103
isInvalidPayload('2[null,"bar"]');
104+
105+
function isInvalidAttachmentCount (str) {
106+
expect(() => new parser.Decoder().add(str)).to.throwException(
107+
/^Illegal attachments$/,
108+
);
109+
}
110+
111+
isInvalidAttachmentCount("5");
112+
isInvalidAttachmentCount("51");
113+
isInvalidAttachmentCount("5a-");
114+
isInvalidAttachmentCount("51.23-");
115+
});
116+
117+
it("throws an error when receiving too many attachments", () => {
118+
const decoder = new parser.Decoder({ maxAttachments: 2 });
119+
120+
expect(() => {
121+
decoder.add(
122+
'53-["hello",{"_placeholder":true,"num":0},{"_placeholder":true,"num":1},{"_placeholder":true,"num":2}]',
123+
);
124+
}).to.throwException(/^too many attachments$/);
104125
});
105126
});

0 commit comments

Comments
 (0)