Skip to content

Commit 67866d7

Browse files
authored
Merge commit from fork
`ws_read_frame()` sizes `wsh->bbuffer` from the length declared in each frame header, so that length needs a bound that holds for every library consumer and across the fragments of one message. The receive adapter then has to hand the message to the parser with the length it actually transferred, which is what lets the SIP layer decide the outcome for an oversized message rather than the transport deciding for it. `libsofia-sip-ua/tport/ws.c`: - `ws_global_payload_size_max` defaults to `WS_PAYLOAD_SIZE_MAX_DEFAULT` (4 MiB) instead of `0`, so the cap applies without a call to `ws_set_global_payload_size_max()`. The cap bounds what one message can make this layer allocate, not what size a message may be: keeping it above NTA's 2 MiB `sa_maxsize` leaves the size limit with the SIP layer, which can answer 413, and reserves the cap for cutting off anything beyond that. - `ws_set_global_payload_size_max()` maps a non-positive argument to the default. Once the check below runs unconditionally there is no value left that means unlimited: `0` would reject every non-empty frame. A caller passing `0` to mean unlimited gets the default instead. - The 64-bit length form is bounded against the cap while still `uint64_t`, then narrowed. Passing that test makes the assignment to `wsh->plen` exact even where `ssize_t` is 32 bits, and it enforces the RFC 6455 5.2 rule that the top bit of that form be 0. - The accumulated length is checked once the frame header is decoded, on every frame, rather than inside the branch that grows `wsh->bbuffer`. The decision no longer depends on `blen + wsh->plen`, the sum it is there to bound, and comparing against the cap by subtraction keeps that sum in range for the sizing arithmetic that follows. - `blen` becomes `ssize_t`, matching the `wsh->body - wsh->bbuffer` difference it holds. `libsofia-sip-ua/tport/tport_type_ws.c`: - `tport_recv_stream_ws()` commits the number of bytes it copied into the message rather than the number the frame held. The two differ when the message exceeds `sa_maxsize`: `tport_recv_iovec()` then provisions a short buffer so the parser still receives a prefix. The assertion after the copy loop is relaxed to match, and the message reaches the parser flagged `MSG_FLG_TOOLARGE`, which the transaction layer answers with 413.
1 parent 8492b0f commit 67866d7

2 files changed

Lines changed: 46 additions & 19 deletions

File tree

‎libsofia-sip-ua/tport/tport_type_ws.c‎

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -242,19 +242,23 @@ int tport_recv_stream_ws(tport_t *self)
242242
msg_set_address(msg, self->tp_addr, self->tp_addrlen);
243243

244244
for (i = 0, n = 0; i < veclen; i++) {
245-
m = iovec[i].mv_len; assert(N >= n + m);
245+
m = iovec[i].mv_len;
246+
assert(N >= n + m);
247+
246248
memcpy(iovec[i].mv_base, data + n, m);
247249
n += m;
248250
}
249251

250-
assert(N == n);
252+
assert(n <= N);
251253

252254
/* Write the received data to the message dump file */
253255
if (self->tp_master->mr_dump_file)
254256
tport_dump_iovec(self, msg, n, iovec, veclen, "recv", "from");
255257

256-
/* Mark buffer as used */
257-
msg_recv_commit(msg, N, 0);
258+
/* Mark buffer as used. The provisioned buffer is shorter than the frame
259+
* when the message exceeds its size limit: the parser gets a prefix,
260+
* flags it, and the reply path answers 413. */
261+
msg_recv_commit(msg, n, 0);
258262

259263
return 1;
260264
}

‎libsofia-sip-ua/tport/ws.c‎

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,14 @@
2929
#define WS_WRITE_SANITY 200
3030

3131
#define SHA1_HASH_SIZE 20
32+
33+
/* Caps a whole message, not one frame. A backstop on how much a peer can
34+
* make this layer allocate; the message size policy belongs to the SIP
35+
* layer, so keep this above NTA's 2 MiB sa_maxsize default. */
36+
#define WS_PAYLOAD_SIZE_MAX_DEFAULT (4 * 1024 * 1024)
37+
3238
static struct ws_globals_s ws_globals;
33-
ssize_t ws_global_payload_size_max = 0;
39+
ssize_t ws_global_payload_size_max = WS_PAYLOAD_SIZE_MAX_DEFAULT;
3440

3541
#ifndef WSS_STANDALONE
3642

@@ -713,9 +719,14 @@ int establish_logical_layer(wsh_t *wsh)
713719
return 0;
714720
}
715721

722+
/* The cap is what bounds a peer-driven allocation, so it cannot be turned
723+
* off: a non-positive limit falls back to the default. Applies to handles
724+
* created after this call. A consumer that raises NTA's sa_maxsize above the
725+
* cap has to raise the cap too, otherwise a message sized between the two is
726+
* dropped here instead of reaching the SIP layer. */
716727
void ws_set_global_payload_size_max(ssize_t bytes)
717728
{
718-
ws_global_payload_size_max = bytes;
729+
ws_global_payload_size_max = bytes > 0 ? bytes : WS_PAYLOAD_SIZE_MAX_DEFAULT;
719730
}
720731

721732
int ws_init(wsh_t *wsh, ws_socket_t sock, SSL_CTX *ssl_ctx, int close_sock, int block, int stay_open)
@@ -911,7 +922,7 @@ ssize_t ws_read_frame(wsh_t *wsh, ws_opcode_t *oc, uint8_t **data)
911922
char *maskp;
912923
int ll = 0;
913924
int frag = 0;
914-
int blen;
925+
ssize_t blen;
915926

916927
wsh->body = wsh->bbuffer;
917928
wsh->packetlen = 0;
@@ -994,7 +1005,7 @@ ssize_t ws_read_frame(wsh_t *wsh, ws_opcode_t *oc, uint8_t **data)
9941005
wsh->payload = &wsh->buffer[2];
9951006

9961007
if (wsh->plen == 127) {
997-
uint64_t *u64;
1008+
uint64_t len;
9981009

9991010
need += 8;
10001011

@@ -1007,9 +1018,19 @@ ssize_t ws_read_frame(wsh_t *wsh, ws_opcode_t *oc, uint8_t **data)
10071018
}
10081019
}
10091020

1010-
u64 = (uint64_t *) wsh->payload;
1021+
len = ntoh64(*(uint64_t *) wsh->payload);
10111022
wsh->payload += 8;
1012-
wsh->plen = ntoh64(*u64);
1023+
1024+
/* Bound it while still unsigned: the cap is an ssize_t, so
1025+
* passing this makes the assignment below exact even where
1026+
* ssize_t is 32 bits. Subsumes RFC 6455 5.2's top-bit rule. */
1027+
if (len > (uint64_t)wsh->payload_size_max) {
1028+
/* size limit */
1029+
*oc = WSOC_CLOSE;
1030+
return ws_close(wsh, WS_NONE);
1031+
}
1032+
1033+
wsh->plen = (ssize_t)len;
10131034
} else if (wsh->plen == 126) {
10141035
uint16_t *u16;
10151036

@@ -1034,6 +1055,16 @@ ssize_t ws_read_frame(wsh_t *wsh, ws_opcode_t *oc, uint8_t **data)
10341055
wsh->payload += 4;
10351056
}
10361057

1058+
blen = wsh->body - wsh->bbuffer;
1059+
1060+
/* Subtract rather than test blen + plen, the sum that must not
1061+
* overflow; bounding it here keeps the sizing below overflow-free. */
1062+
if (wsh->plen > wsh->payload_size_max - blen) {
1063+
/* size limit */
1064+
*oc = WSOC_CLOSE;
1065+
return ws_close(wsh, WS_NONE);
1066+
}
1067+
10371068
need = (wsh->plen - (wsh->datalen - need));
10381069

10391070
if (need < 0) {
@@ -1042,21 +1073,13 @@ ssize_t ws_read_frame(wsh_t *wsh, ws_opcode_t *oc, uint8_t **data)
10421073
return ws_close(wsh, WS_NONE);
10431074
}
10441075

1045-
blen = wsh->body - wsh->bbuffer;
1046-
10471076
/* Body must hold blen accumulated bytes plus this frame's
1048-
* full payload. */
1077+
* full payload; the cap check above bounds that sum. */
10491078
if (blen + wsh->plen > (ssize_t)wsh->bbuflen) {
10501079
void *tmp;
10511080

10521081
wsh->bbuflen = blen + wsh->plen;
10531082

1054-
if (wsh->payload_size_max && wsh->bbuflen > wsh->payload_size_max) {
1055-
/* size limit */
1056-
*oc = WSOC_CLOSE;
1057-
return ws_close(wsh, WS_NONE);
1058-
}
1059-
10601083
/* +1 NUL slot — see wsh_t in ws.h. */
10611084
if ((tmp = realloc(wsh->bbuffer, wsh->bbuflen + 1))) {
10621085
wsh->bbuffer = tmp;

0 commit comments

Comments
 (0)