Merge commit from fork

The 127-length extended payload field was decoded with `ntohl()`, a
32-bit byte swap, dropping the upper word of the 64-bit length. Combined
with the signed `issize_t plen`, a high low-word truncated to a negative
length that slipped past the signed size guard and became `SIZE_MAX` in
the read loop, driving an out-of-bounds write past `wsh->buffer`.

Decode the full 64-bit length in network byte order and reject any value
that cannot fit the buffer with an unsigned comparison before narrowing
to `issize_t`, so a truncated or oversized length can no longer yield a
negative `plen` or pass the guard.

`ws_write_frame()` had the symmetric defect: it encoded the 64-bit length
field with a 32-bit `htonl()`, mis-framing any payload large enough to
use the 8-byte (127) length field. Emit the full 8 bytes there too.

Factor the byte assembly into `ws_get_be64()` / `ws_put_be64()` helpers.
This commit is contained in:
Dmitry Verenitsin
2026-08-08 18:27:47 +03:00
committed by GitHub
parent fbac12b451
commit d557ca6d29
+36 -7
View File
@@ -384,6 +384,32 @@ issize_t ws_close(wsh_t *wsh, int16_t reason)
return reason * -1;
}
/* Read a big-endian (network byte order) 64-bit integer from a byte buffer. */
static uint64_t ws_get_be64(const uint8_t *p)
{
return ((uint64_t)p[0] << 56) |
((uint64_t)p[1] << 48) |
((uint64_t)p[2] << 40) |
((uint64_t)p[3] << 32) |
((uint64_t)p[4] << 24) |
((uint64_t)p[5] << 16) |
((uint64_t)p[6] << 8) |
((uint64_t)p[7]);
}
/* Write a big-endian (network byte order) 64-bit integer to a byte buffer. */
static void ws_put_be64(uint8_t *p, uint64_t v)
{
p[0] = (uint8_t)(v >> 56);
p[1] = (uint8_t)(v >> 48);
p[2] = (uint8_t)(v >> 40);
p[3] = (uint8_t)(v >> 32);
p[4] = (uint8_t)(v >> 24);
p[5] = (uint8_t)(v >> 16);
p[6] = (uint8_t)(v >> 8);
p[7] = (uint8_t)(v);
}
issize_t ws_read_frame(wsh_t *wsh, ws_opcode_t *oc, uint8_t **data)
{
@@ -447,7 +473,7 @@ issize_t ws_read_frame(wsh_t *wsh, ws_opcode_t *oc, uint8_t **data)
wsh->payload = &wsh->buffer[2];
if (wsh->plen == 127) {
uint64_t *u64;
uint64_t plen64;
need += 8;
@@ -457,10 +483,16 @@ issize_t ws_read_frame(wsh_t *wsh, ws_opcode_t *oc, uint8_t **data)
return ws_close(wsh, WS_PROTO_ERR);
}
u64 = (uint64_t *) wsh->payload;
plen64 = ws_get_be64((const uint8_t *)wsh->payload);
wsh->payload += 8;
wsh->plen = ntohl((u_long)*u64);
/* Bound-check unsigned, before narrowing to the signed issize_t plen. */
if (plen64 >= wsh->buflen) {
*oc = WSOC_CLOSE;
return ws_close(wsh, WS_DATA_TOO_BIG);
}
wsh->plen = (issize_t)plen64;
} else if (wsh->plen == 126) {
uint16_t *u16;
@@ -598,13 +630,10 @@ issize_t ws_write_frame(wsh_t *wsh, ws_opcode_t oc, void *data, size_t bytes)
*u16 = htons((uint16_t) bytes);
} else {
uint64_t *u64;
hdr[1] = 127;
hlen += 8;
u64 = (uint64_t *) &hdr[2];
*u64 = htonl((unsigned long)bytes);
ws_put_be64(&hdr[2], (uint64_t)bytes);
}
if (ws_raw_write(wsh, (void *) &hdr[0], hlen) != (issize_t)hlen) {