Skip to content

Commit 84235c6

Browse files
committed
Fix websocket 64-bit length overflow
Signed-off-by: Matteo Collina <hello@matteocollina.com>
1 parent 77594f9 commit 84235c6

File tree

3 files changed

+66
-4
lines changed

3 files changed

+66
-4
lines changed

lib/web/websocket/receiver.js

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -190,21 +190,20 @@ class ByteParser extends Writable {
190190

191191
const buffer = this.consume(8)
192192
const upper = buffer.readUInt32BE(0)
193+
const lower = buffer.readUInt32BE(4)
193194

194195
// 2^31 is the maximum bytes an arraybuffer can contain
195196
// on 32-bit systems. Although, on 64-bit systems, this is
196197
// 2^53-1 bytes.
197198
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Invalid_array_length
198199
// https://source.chromium.org/chromium/chromium/src/+/main:v8/src/common/globals.h;drc=1946212ac0100668f14eb9e2843bdd846e510a1e;bpv=1;bpt=1;l=1275
199200
// https://source.chromium.org/chromium/chromium/src/+/main:v8/src/objects/js-array-buffer.h;l=34;drc=1946212ac0100668f14eb9e2843bdd846e510a1e
200-
if (upper > 2 ** 31 - 1) {
201+
if (upper !== 0 || lower > 2 ** 31 - 1) {
201202
failWebsocketConnection(this.#handler, 1009, 'Received payload length > 2^31 bytes.')
202203
return
203204
}
204205

205-
const lower = buffer.readUInt32BE(4)
206-
207-
this.#info.payloadLength = (upper << 8) + lower
206+
this.#info.payloadLength = lower
208207
this.#state = parserStates.READ_DATA
209208
} else if (this.#state === parserStates.READ_DATA) {
210209
if (this.#byteOffset < this.#info.payloadLength) {

test/websocket/receive.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,29 @@ test('Receiving a frame with a payload length > 2^31-1 bytes', (t) => {
2727
})
2828
})
2929

30+
test('Receiving a 64-bit payload length with a non-zero upper word', (t) => {
31+
const server = new WebSocketServer({ port: 0 })
32+
33+
server.on('connection', (ws) => {
34+
const socket = ws._socket
35+
36+
socket.write(Buffer.from([0x82, 0x7F, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]))
37+
})
38+
39+
const ws = new WebSocket(`ws://localhost:${server.address().port}`)
40+
41+
return new Promise((resolve, reject) => {
42+
ws.onmessage = reject
43+
44+
ws.addEventListener('error', (event) => {
45+
t.assert.ok(event.error instanceof Error)
46+
ws.close()
47+
server.close()
48+
resolve()
49+
})
50+
})
51+
})
52+
3053
test('Receiving an ArrayBuffer', (t) => {
3154
const server = new WebSocketServer({ port: 0 })
3255

test/websocket/receiver-unit.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
'use strict'
2+
3+
const { test } = require('node:test')
4+
const { ByteParser } = require('../../lib/web/websocket/receiver')
5+
const { states } = require('../../lib/web/websocket/constants')
6+
7+
const invalidFrame = Buffer.from([0x82, 0x7F, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01])
8+
9+
test('ByteParser rejects 64-bit payload lengths with a non-zero upper word', (t) => {
10+
const calls = {
11+
abort: 0,
12+
close: 0
13+
}
14+
15+
const handler = {
16+
readyState: states.CONNECTING,
17+
controller: {
18+
abort: () => {
19+
calls.abort += 1
20+
}
21+
},
22+
onSocketClose: () => {
23+
calls.close += 1
24+
},
25+
closeState: new Set()
26+
}
27+
28+
const parser = new ByteParser(handler)
29+
30+
parser.write(invalidFrame)
31+
32+
return new Promise((resolve) => {
33+
setImmediate(() => {
34+
t.assert.strictEqual(calls.abort, 1)
35+
t.assert.strictEqual(calls.close, 1)
36+
parser.destroy()
37+
resolve()
38+
})
39+
})
40+
})

0 commit comments

Comments
 (0)