Skip to content

Commit 279e7ee

Browse files
Sashanjogme
authored andcommitted
Limit packet buffer overhead to ~64kB per stream.
There is currently no limit on how many bytes of packet buffers each stream can hold in memory. To monitor and limit the size of packet buffers used by stream the change accounts so called stream chunk overhead which is a delta between actual datagram size of packet delivering the particular stream chunk and chunk itself. As soon as the chunk overhead exceeds ~64kB for all stream chunks kept in receive buffer, the newly received chunks will be moved from packet buffer to stream buffer. The packet buffer overhead limit is held in newly introduced structure QUIC_RSTREAM_QPARAM (RX stream quality parameter). If new additional quality parameters are added, then those should be part of QUIC_RSTREAM_QPARAM structure. this changeset introduces QUIC_RSTREAM_QPARAM the the rsqp (reead stream quality parameter) enables channel to control quality of RX stream. quality currently means the packet buffer overhead. more parameters may be added later. Fixes: CVE-2026-54873 Reviewed-by: Tomas Mraz <tomas@openssl.foundation> Reviewed-by: Milan Broz <mbroz@openssl.org> Merge-date: Sat Sep 26 11:53:40 2026 Merged-from: #32769
1 parent ca8402e commit 279e7ee

9 files changed

Lines changed: 1177 additions & 146 deletions

File tree

‎include/internal/quic_predef.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ typedef struct quic_conn_st QUIC_CONNECTION;
4444
typedef struct quic_xso_st QUIC_XSO;
4545
typedef struct quic_listener_st QUIC_LISTENER;
4646
typedef struct quic_domain_st QUIC_DOMAIN;
47+
typedef struct quic_rstream_qparm_st QUIC_RSTREAM_QPARM;
4748

4849
#endif
4950

‎include/internal/quic_stream.h‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -320,7 +320,7 @@ void ossl_quic_sstream_set_cleanse(QUIC_SSTREAM *qss, int cleanse);
320320
* is read by application. `statm` is queried for current rtt.
321321
*/
322322
QUIC_RSTREAM *ossl_quic_rstream_new(QUIC_RXFC *rxfc,
323-
OSSL_STATM *statm);
323+
OSSL_STATM *statm, QUIC_RSTREAM_QPARM *rsqp);
324324

325325
/*
326326
* Frees a QUIC_RSTREAM and any associated storage.
@@ -407,6 +407,9 @@ size_t ossl_quic_rstream_get_chunk_count(QUIC_RSTREAM *qrs);
407407
* returns the number of stream ranges kept in rstream
408408
*/
409409
size_t ossl_quic_rstream_get_range_count(QUIC_RSTREAM *qrs);
410+
411+
QUIC_RSTREAM_QPARM *ossl_quic_rstream_qparm_new(void);
412+
void ossl_quic_rstream_qparm_destroy(QUIC_RSTREAM_QPARM *rsqp);
410413
#endif
411414

412415
#endif

‎include/internal/quic_strm_reas.h‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,13 @@ typedef struct sframe_set_t {
3131
/* Cleanse data on release? */
3232
int cleanse;
3333
int move_buffers;
34+
QUIC_RSTREAM_QPARM *rsqp;
3435
} SFRAME_SET;
3536

3637
/*
3738
* Initializes the stream frame list fs.
3839
*/
39-
void ossl_sframe_set_init(SFRAME_SET *fs);
40+
void ossl_sframe_set_init(SFRAME_SET *fs, QUIC_RSTREAM_QPARM *rsqp);
4041

4142
/*
4243
* Destroys the stream frame list fs releasing any data

‎ssl/quic/quic_channel.c‎

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -332,8 +332,13 @@ static int ch_init(QUIC_CHANNEL *ch)
332332
goto err;
333333
}
334334

335+
ch->rsqp = ossl_quic_rstream_qparm_new();
336+
if (ch->rsqp == NULL)
337+
goto err;
338+
335339
for (pn_space = QUIC_PN_SPACE_INITIAL; pn_space < QUIC_PN_SPACE_NUM; ++pn_space) {
336-
ch->crypto_recv[pn_space] = ossl_quic_rstream_new(NULL, NULL);
340+
/* no quality control for crypto stream. */
341+
ch->crypto_recv[pn_space] = ossl_quic_rstream_new(NULL, NULL, ch->rsqp);
337342
if (ch->crypto_recv[pn_space] == NULL)
338343
goto err;
339344
}
@@ -415,6 +420,9 @@ static void ch_cleanup(QUIC_CHANNEL *ch)
415420
ossl_quic_rstream_free(ch->crypto_recv[pn_space]);
416421
}
417422

423+
ossl_quic_rstream_qparm_destroy(ch->rsqp);
424+
ch->rsqp = NULL;
425+
418426
ossl_qrx_pkt_release(ch->qrx_pkt);
419427
ch->qrx_pkt = NULL;
420428

@@ -3753,7 +3761,7 @@ static int ch_init_new_stream(QUIC_CHANNEL *ch, QUIC_STREAM *qs,
37533761
goto err;
37543762

37553763
if (can_recv)
3756-
if ((qs->rstream = ossl_quic_rstream_new(NULL, NULL)) == NULL)
3764+
if ((qs->rstream = ossl_quic_rstream_new(NULL, NULL, ch->rsqp)) == NULL)
37573765
goto err;
37583766

37593767
/* TXFC */

‎ssl/quic/quic_channel_local.h‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -501,6 +501,12 @@ struct quic_channel_st {
501501

502502
/* Title for qlog purposes. We own this copy. */
503503
char *qlog_title;
504+
505+
/*
506+
* RX stream quality parameter.
507+
*/
508+
QUIC_RSTREAM_QPARM *rsqp;
509+
504510
/*
505511
* number of path responses waiting to be dispatched
506512
* from control frame queue (CFQ)

‎ssl/quic/quic_rstream.c‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,14 @@ struct quic_rstream_st {
2828
#endif
2929

3030
QUIC_RSTREAM *ossl_quic_rstream_new(QUIC_RXFC *rxfc,
31-
OSSL_STATM *statm)
31+
OSSL_STATM *statm, QUIC_RSTREAM_QPARM *rsqp)
3232
{
3333
QUIC_RSTREAM *ret = OPENSSL_zalloc(sizeof(*ret));
3434

3535
if (ret == NULL)
3636
return NULL;
3737

38-
ossl_sframe_set_init(&ret->fs);
38+
ossl_sframe_set_init(&ret->fs, rsqp);
3939
ret->rxfc = rxfc;
4040
ret->statm = statm;
4141
return ret;

‎ssl/quic/quic_strm_reas.c‎

Lines changed: 110 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
#include "internal/uint_set.h"
1111
#include "internal/common.h"
12+
#include "internal/quic_stream.h"
1213
#include "internal/quic_strm_reas.h"
1314
#include "internal/list.h"
1415

@@ -21,6 +22,13 @@
2122

2223
#define DIRECT_STORAGE_SZ (2 * sizeof(void *))
2324

25+
/*
26+
* Maximal allocation overhead in packet buffers is ~64kB for
27+
* connection. If ~64kB limit is exceeded, then the newly received
28+
* chunks are moved from the packet to the stream buffer.
29+
*/
30+
#define PKT_BUFFER_OVERHEAD_TRESHOLD (65535)
31+
2432
/*
2533
* storage type indicates where stream data bytes
2634
* are stored.
@@ -50,6 +58,11 @@ struct stream_chunk_t {
5058
} sc_storage_u;
5159
};
5260

61+
struct quic_rstream_qparm_st {
62+
size_t rsqp_pkt_overhead_treshold;
63+
size_t rsqp_pkt_overhead_sz;
64+
};
65+
5366
#define sc_data sc_data_u.u_data
5467
#define sc_data_w sc_data_u.u_data_w
5568

@@ -61,6 +74,7 @@ DEFINE_LIST_OF(sc, struct stream_chunk_t);
6174

6275
#define SCHUNK_SIZE(_sc) ((_sc)->sc_range.end - (_sc)->sc_range.start)
6376
#define SRANGE_SIZE(_sr) ((_sr)->sr_range.end - (_sr)->sr_range.start)
77+
#define SCHUNK_OVERHEAD(_pkt, _sc) ((_pkt)->datagram_len - SCHUNK_SIZE(_sc))
6478

6579
/*
6680
* Stream range keeps list of continuous stream chunks. The range
@@ -88,6 +102,18 @@ OSSL_RBT_GENERATE(srange, stream_range_t, sr_rbe, srange_cmp);
88102

89103
#define UINT64_TO_SIZE_T(_x) ((size_t)(((_x) > SIZE_MAX) ? SIZE_MAX : (_x)))
90104

105+
static void rsqp_add_overhead(QUIC_RSTREAM_QPARM *rsqp, size_t sc_overhead)
106+
{
107+
if (rsqp != NULL)
108+
rsqp->rsqp_pkt_overhead_sz += sc_overhead;
109+
}
110+
111+
static void rsqp_sub_overhead(QUIC_RSTREAM_QPARM *rsqp, size_t sc_overhead)
112+
{
113+
if (rsqp != NULL)
114+
rsqp->rsqp_pkt_overhead_sz -= sc_overhead;
115+
}
116+
91117
/*
92118
* Cleansing (SSL_OP_CLEANSE_PLAINTEXT) must write through the const
93119
* data pointers received from ossl_sframe_set_insert(), which may
@@ -192,27 +218,9 @@ static int srange_cmp(const struct stream_range_t *a_sr,
192218
static int keep_schunk_data_on_packet(SFRAME_SET *fs, OSSL_QRX_PKT *pkt,
193219
UINT_RANGE *r)
194220
{
195-
/*
196-
* the function decides whether stream data should be moved
197-
* from packet buffer to stream buffer or if data can stay
198-
* at packet buffer.
199-
*
200-
* Keeping the data at packet saves yet another buffer
201-
* allocation at heap (+ data transfer). On the other hand
202-
* it opens door to malicious peer to force stack to use more
203-
* memory than necessary.
204-
*
205-
* The function here should asses a current stream quality:
206-
* how many stream chunks are there
207-
* the time elapsed since the arrival of earlier chunk
208-
* the time elapsed since the application consumed the data
209-
* the size of the chunk compared with the whole packet size
210-
* the size of chunk with respect to DIRECT_STORAGE_SZ
211-
* ...
212-
* the code to collect those parameters is still missing, once
213-
* this gap will be filled this function will be able to
214-
* make the decision.
215-
*/
221+
if (fs->rsqp != NULL
222+
&& fs->rsqp->rsqp_pkt_overhead_sz >= fs->rsqp->rsqp_pkt_overhead_treshold)
223+
return 0;
216224

217225
return 1;
218226
}
@@ -222,6 +230,7 @@ static struct stream_chunk_t *new_schunk(SFRAME_SET *fs, OSSL_QRX_PKT *pkt,
222230
{
223231
struct stream_chunk_t *sc;
224232
uint64_t rsize;
233+
size_t overhead;
225234

226235
if (pkt == NULL)
227236
return NULL;
@@ -230,14 +239,29 @@ static struct stream_chunk_t *new_schunk(SFRAME_SET *fs, OSSL_QRX_PKT *pkt,
230239
if (sc == NULL)
231240
return NULL;
232241

242+
rsize = r->end - r->start;
243+
assert(rsize <= pkt->datagram_len);
244+
overhead = UINT64_TO_SIZE_T(pkt->datagram_len - rsize);
245+
rsqp_add_overhead(fs->rsqp, overhead);
246+
233247
if (keep_schunk_data_on_packet(fs, pkt, r) == 1) {
234248
sc->sc_st = ST_TYPE_PKT;
235249
sc->sc_pkt = pkt;
236250
ossl_qrx_pkt_up_ref(pkt);
237251
sc->sc_data = data;
238252
sc->sc_range = *r;
253+
if (fs->rsqp != NULL)
254+
DEBUG_PRINT(stderr,
255+
"%s sc: %p sc overhead: %d pkt_buf_overhead_sz: %zu -> %zu\n",
256+
OPENSSL_FUNC, (void *)sc, SCHUNK_OVERHEAD(pkt, sc),
257+
fs->rsqp->rsqp_pkt_overhead_sz - SCHUNK_OVERHEAD(pkt, sc),
258+
fs->rsqp->rsqp_pkt_overhead_sz);
239259
} else {
240-
rsize = r->end - r->start;
260+
/*
261+
* Only data which stay on packet must be accounted as overhead.
262+
*/
263+
rsqp_sub_overhead(fs->rsqp, overhead);
264+
241265
if (rsize <= DIRECT_STORAGE_SZ) {
242266
DEBUG_PRINT(stderr, "%s ST_TYPE_DIRECT sc: %p %llu\n", OPENSSL_FUNC,
243267
(void *)sc, rsize);
@@ -277,6 +301,16 @@ static void destroy_schunk(SFRAME_SET *fs, struct stream_chunk_t *sc)
277301

278302
switch (sc->sc_st) {
279303
case ST_TYPE_PKT:
304+
assert(fs->rsqp == NULL
305+
|| fs->rsqp->rsqp_pkt_overhead_sz >= SCHUNK_OVERHEAD(sc->sc_pkt, sc));
306+
if (fs->rsqp != NULL)
307+
DEBUG_PRINT(stderr,
308+
"%s sc: %p sc overhead: %d pkt_buf_overhead_sz: %zu -> %zu\n",
309+
OPENSSL_FUNC, (void *)sc, SCHUNK_OVERHEAD(sc->sc_pkt, sc),
310+
fs->rsqp->rsqp_pkt_overhead_sz,
311+
fs->rsqp->rsqp_pkt_overhead_sz - SCHUNK_OVERHEAD(sc->sc_pkt, sc));
312+
rsqp_sub_overhead(fs->rsqp,
313+
UINT64_TO_SIZE_T(SCHUNK_OVERHEAD(sc->sc_pkt, sc)));
280314
ossl_qrx_pkt_release(sc->sc_pkt);
281315
break;
282316
case ST_TYPE_HEAP:
@@ -338,10 +372,11 @@ static struct stream_range_t *create_range(SFRAME_SET *fs,
338372
return sr;
339373
}
340374

341-
void ossl_sframe_set_init(SFRAME_SET *fs)
375+
void ossl_sframe_set_init(SFRAME_SET *fs, QUIC_RSTREAM_QPARM *rsqp)
342376
{
343377
memset(fs, 0, sizeof(*fs));
344378
OSSL_RBT_INIT(srange, &fs->ranges);
379+
fs->rsqp = rsqp;
345380
}
346381

347382
static uint64_t get_sc_dstorage_sz(struct stream_chunk_t *sc)
@@ -715,6 +750,7 @@ static int chop_range(SFRAME_SET *fs, struct stream_range_t *sr,
715750
uint64_t new_end)
716751
{
717752
struct stream_chunk_t *sc;
753+
size_t unused_sz;
718754

719755
assert(sr->sr_range.end >= new_end);
720756

@@ -744,6 +780,15 @@ static int chop_range(SFRAME_SET *fs, struct stream_range_t *sr,
744780
sc->sc_range.end = new_end;
745781
sr->sr_range.end = new_end;
746782

783+
if (sc->sc_st == ST_TYPE_PKT) {
784+
rsqp_add_overhead(fs->rsqp, unused_sz);
785+
if (fs->rsqp != NULL)
786+
DEBUG_PRINT(stderr, "%s sc: %p unused_sz: %zu %zu -> %zu\n",
787+
OPENSSL_FUNC, (void *)sc, unused_sz,
788+
fs->rsqp->rsqp_pkt_overhead_sz - unused_sz,
789+
fs->rsqp->rsqp_pkt_overhead_sz);
790+
}
791+
747792
return 1;
748793
}
749794

@@ -764,7 +809,8 @@ static struct stream_range_t *merge_ranges(SFRAME_SET *fs,
764809
* sub_sr and super_sr are equal ranges (sets) super_sr
765810
* sub_sr is subset of super_sr (super_sr includes sub_sr).
766811
*/
767-
assert(super_sr->sr_range.start <= sub_sr->sr_range.start && super_sr->sr_range.end >= sub_sr->sr_range.end);
812+
assert(super_sr->sr_range.start <= sub_sr->sr_range.start
813+
&& super_sr->sr_range.end >= sub_sr->sr_range.end);
768814

769815
DEBUG_PRINT(stderr, "%s super: %p [ %llu, %llu ], sub: %p [ %llu, %llu]\n",
770816
OPENSSL_FUNC, (void *)super_sr, super_sr->sr_range.start,
@@ -953,7 +999,7 @@ int ossl_sframe_set_insert(SFRAME_SET *fs, UINT_RANGE *r, OSSL_QRX_PKT *pkt,
953999
(void *)sr, sr->sr_range.start, sr->sr_range.end);
9541000

9551001
/*
956-
* sandwich, append, prepend can still be improved to handle
1002+
* Following calls can still be improved to handle
9571003
* chunks with direct storage better, but I don't think it's
9581004
* worth the effort. out of order short data chunks (less
9591005
* than DIRECT_STORAGE_SZ) should be considered exceptional.
@@ -1006,23 +1052,27 @@ int ossl_sframe_set_insert(SFRAME_SET *fs, UINT_RANGE *r, OSSL_QRX_PKT *pkt,
10061052
adjacent_sr->sr_range.end);
10071053
fs->stream_ranges--;
10081054

1009-
if (sr->sr_range.start <= adjacent_sr->sr_range.start && sr->sr_range.end >= adjacent_sr->sr_range.end) {
1055+
if (sr->sr_range.start <= adjacent_sr->sr_range.start
1056+
&& sr->sr_range.end >= adjacent_sr->sr_range.end) {
10101057
/*
10111058
* adjacent_sr subset of sr
10121059
*/
10131060
joined_sr = merge_ranges(fs, sr, adjacent_sr);
1014-
} else if (sr->sr_range.start >= adjacent_sr->sr_range.start && sr->sr_range.end <= adjacent_sr->sr_range.end) {
1061+
} else if (sr->sr_range.start >= adjacent_sr->sr_range.start
1062+
&& sr->sr_range.end <= adjacent_sr->sr_range.end) {
10151063
/*
10161064
* sr subset of adjacent_sr
10171065
*/
10181066
joined_sr = merge_ranges(fs, adjacent_sr, sr);
1019-
} else if (sr->sr_range.start < adjacent_sr->sr_range.start && sr->sr_range.end >= adjacent_sr->sr_range.start) {
1067+
} else if (sr->sr_range.start < adjacent_sr->sr_range.start
1068+
&& sr->sr_range.end >= adjacent_sr->sr_range.start) {
10201069
/*
10211070
* adjacent_sr follows sr
10221071
*/
10231072
assert(sr->sr_range.end < adjacent_sr->sr_range.end);
10241073
joined_sr = append_range(fs, sr, adjacent_sr);
1025-
} else if (sr->sr_range.start <= adjacent_sr->sr_range.end && sr->sr_range.end > adjacent_sr->sr_range.end) {
1074+
} else if (sr->sr_range.start <= adjacent_sr->sr_range.end
1075+
&& sr->sr_range.end > adjacent_sr->sr_range.end) {
10261076
/*
10271077
* sr follows adjacent_sr
10281078
*/
@@ -1185,6 +1235,7 @@ int ossl_sframe_set_move_offset(SFRAME_SET *fs, uint64_t new_offset)
11851235
{
11861236
struct stream_range_t *sr = OSSL_RBT_MIN(srange, &fs->ranges);
11871237
struct stream_chunk_t *sc, *save_sc;
1238+
size_t unused_sz;
11881239

11891240
if (new_offset == fs->offset)
11901241
return 1;
@@ -1231,6 +1282,15 @@ int ossl_sframe_set_move_offset(SFRAME_SET *fs, uint64_t new_offset)
12311282
sr->sr_range.start = new_offset;
12321283
DEBUG_PRINT(stderr, "[ %lli, %llu ]\n",
12331284
sr->sr_range.start, sr->sr_range.end);
1285+
1286+
if (sc->sc_st == ST_TYPE_PKT) {
1287+
rsqp_add_overhead(fs->rsqp, unused_sz);
1288+
if (fs->rsqp != NULL)
1289+
DEBUG_PRINT(stderr, "%s sc: %p unused_sz: %zu %zu -> %zu\n",
1290+
OPENSSL_FUNC, (void *)sc, unused_sz,
1291+
fs->rsqp->rsqp_pkt_overhead_sz - unused_sz,
1292+
fs->rsqp->rsqp_pkt_overhead_sz);
1293+
}
12341294
}
12351295

12361296
return 1;
@@ -1249,3 +1309,24 @@ int ossl_sframe_set_avail(SFRAME_SET *fs, uint64_t *avail, int *fin)
12491309
*fin = (fs->fin && fs->offset + *avail == fs->fin_off) ? 1 : 0;
12501310
return 1;
12511311
}
1312+
1313+
QUIC_RSTREAM_QPARM *ossl_quic_rstream_qparm_new(void)
1314+
{
1315+
QUIC_RSTREAM_QPARM *rsqp;
1316+
1317+
rsqp = OPENSSL_malloc(sizeof(QUIC_RSTREAM_QPARM));
1318+
if (rsqp != NULL) {
1319+
rsqp->rsqp_pkt_overhead_treshold = PKT_BUFFER_OVERHEAD_TRESHOLD;
1320+
rsqp->rsqp_pkt_overhead_sz = 0;
1321+
}
1322+
1323+
return rsqp;
1324+
}
1325+
1326+
void ossl_quic_rstream_qparm_destroy(QUIC_RSTREAM_QPARM *rsqp)
1327+
{
1328+
if (rsqp != NULL) {
1329+
assert(rsqp->rsqp_pkt_overhead_sz == 0);
1330+
OPENSSL_free(rsqp);
1331+
}
1332+
}

0 commit comments

Comments
 (0)