Skip to content

Commit 5a97f08

Browse files
committed
Fix websocket 64-bit length overflow
Signed-off-by: Matteo Collina <hello@matteocollina.com> (cherry picked from commit 84235c6)
1 parent e43e898 commit 5a97f08

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
@@ -189,21 +189,20 @@ class ByteParser extends Writable {
189189

190190
const buffer = this.consume(8)
191191
const upper = buffer.readUInt32BE(0)
192+
const lower = buffer.readUInt32BE(4)
192193

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

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

test/websocket/receive.js

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

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

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)