Skip to content

Commit 747be41

Browse files
author
Dmitri Tikhonov
committed
Release 2.8.4
- [HTTP3] Verify number of bytes in incoming DATA frames against content-length. - [HTTP3] Stop issuing streams credits if peer stops opening QPACK decoder window. This addresses a potential attack whereby client can cause the server to keep allocating memory. See Security Considerations in the QPACK draft. - [BUGFIX] Mini conn: don't shorten max packet size for Q050 and later. - [BUGFIX] Init IETF connection flow controller using correct setting. - Code cleanup and minor fixes.
1 parent 3f2ab35 commit 747be41

15 files changed

+286
-59
lines changed

CHANGELOG

+12
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,15 @@
1+
2020-01-06
2+
- 2.8.4
3+
- [HTTP3] Verify number of bytes in incoming DATA frames against
4+
content-length.
5+
- [HTTP3] Stop issuing streams credits if peer stops opening QPACK
6+
decoder window. This addresses a potential attack whereby client
7+
can cause the server to keep allocating memory. See Security
8+
Considerations in the QPACK draft.
9+
- [BUGFIX] Mini conn: don't shorten max packet size for Q050 and later.
10+
- [BUGFIX] Init IETF connection flow controller using correct setting.
11+
- Code cleanup and minor fixes.
12+
113
2019-12-30
214
- 2.8.1
315
- [FEATURE] Use occasional packet number gaps to detect optimistic

CMakeLists.txt

+4-1
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,10 @@ ENDIF()
196196
IF (CMAKE_SYSTEM_NAME STREQUAL Windows)
197197
FIND_LIBRARY(EVENT_LIB event)
198198
ELSE()
199-
FIND_LIBRARY(EVENT_LIB libevent.a libevent.so)
199+
FIND_LIBRARY(EVENT_LIB libevent.a)
200+
IF(NOT EVENT_LIB)
201+
FIND_LIBRARY(EVENT_LIB libevent.so)
202+
ENDIF()
200203
ENDIF()
201204
IF(EVENT_LIB)
202205
MESSAGE(STATUS "Found event: ${EVENT_LIB}")

include/lsquic.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ extern "C" {
2525

2626
#define LSQUIC_MAJOR_VERSION 2
2727
#define LSQUIC_MINOR_VERSION 8
28-
#define LSQUIC_PATCH_VERSION 1
28+
#define LSQUIC_PATCH_VERSION 4
2929

3030
/**
3131
* Engine flags:

src/liblsquic/lsquic_enc_sess_ietf.c

+1
Original file line numberDiff line numberDiff line change
@@ -1518,6 +1518,7 @@ iquic_esfi_handshake (struct enc_sess_iquic *enc_sess)
15181518
LSQ_DEBUG("early data rejected");
15191519
hsk_status = LSQ_HSK_0RTT_FAIL;
15201520
goto err;
1521+
/* fall through */
15211522
default:
15221523
LSQ_DEBUG("handshake: %s", ERR_error_string(err, errbuf));
15231524
hsk_status = LSQ_HSK_FAIL;

src/liblsquic/lsquic_full_conn_ietf.c

+36-25
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,7 @@ struct ietf_full_conn
294294
uint64_t ifc_max_stream_data_uni;
295295
enum ifull_conn_flags ifc_flags;
296296
enum send_flags ifc_send_flags;
297+
enum send_flags ifc_delayed_send;
297298
struct {
298299
uint64_t streams_blocked[N_SDS];
299300
} ifc_send;
@@ -920,7 +921,7 @@ ietf_full_conn_init (struct ietf_full_conn *conn,
920921
flags & IFC_SERVER ? &server_ver_neg : &conn->ifc_u.cli.ifcli_ver_neg,
921922
&conn->ifc_pub, SC_IETF|SC_NSTP|(ecn ? SC_ECN : 0));
922923
lsquic_cfcw_init(&conn->ifc_pub.cfcw, &conn->ifc_pub,
923-
conn->ifc_settings->es_cfcw);
924+
conn->ifc_settings->es_init_max_data);
924925
conn->ifc_pub.all_streams = lsquic_hash_create();
925926
if (!conn->ifc_pub.all_streams)
926927
return -1;
@@ -1979,34 +1980,44 @@ is_peer_initiated (const struct ietf_full_conn *conn,
19791980
}
19801981

19811982

1982-
#if 0
1983-
/* XXX seems we don't need this? */
1984-
static unsigned
1985-
count_streams (const struct ietf_full_conn *conn, enum stream_id_type sit)
1983+
static void
1984+
sched_max_bidi_streams (void *conn_p)
19861985
{
1987-
const struct lsquic_stream *stream;
1988-
struct lsquic_hash_elem *el;
1989-
unsigned count;
1990-
int peer;
1986+
struct ietf_full_conn *conn = conn_p;
19911987

1992-
peer = is_peer_initiated(conn, sit);
1993-
for (el = lsquic_hash_first(conn->ifc_pub.all_streams); el;
1994-
el = lsquic_hash_next(conn->ifc_pub.all_streams))
1995-
{
1996-
stream = lsquic_hashelem_getdata(el);
1997-
count += (stream->id & SIT_MASK) == sit
1998-
&& !lsquic_stream_is_closed(stream)
1999-
/* When counting peer-initiated streams, do not include those
2000-
* that have been reset:
2001-
*/
2002-
&& !(peer && lsquic_stream_is_reset(stream));
2003-
}
2004-
2005-
return count;
1988+
conn->ifc_send_flags |= SF_SEND_MAX_STREAMS_BIDI;
1989+
conn->ifc_delayed_send &= ~SF_SEND_MAX_STREAMS_BIDI;
1990+
LSQ_DEBUG("schedule MAX_STREAMS frame for bidirectional streams (was "
1991+
"delayed)");
20061992
}
20071993

20081994

2009-
#endif
1995+
/* Do not allow peer to open more streams while QPACK decoder stream has
1996+
* unsent data.
1997+
*/
1998+
static int
1999+
can_give_peer_streams_credit (struct ietf_full_conn *conn, enum stream_dir sd)
2000+
{
2001+
/* This logic only applies to HTTP servers. */
2002+
if ((conn->ifc_flags & (IFC_SERVER|IFC_HTTP)) != (IFC_SERVER|IFC_HTTP))
2003+
return 1;
2004+
/* HTTP client does not open unidirectional streams (other than the
2005+
* standard three), not applicable.
2006+
*/
2007+
if (SD_UNI == sd)
2008+
return 1;
2009+
if (conn->ifc_delayed_send & (SF_SEND_MAX_STREAMS << sd))
2010+
return 0;
2011+
if (lsquic_qdh_arm_if_unsent(&conn->ifc_qdh, sched_max_bidi_streams, conn))
2012+
{
2013+
LSQ_DEBUG("delay sending more streams credit to peer until QPACK "
2014+
"decoder sends unsent data");
2015+
conn->ifc_delayed_send |= SF_SEND_MAX_STREAMS << sd;
2016+
return 0;
2017+
}
2018+
else
2019+
return 1;
2020+
}
20102021

20112022

20122023
/* Because stream IDs are distributed unevenly, it is more efficient to
@@ -2034,7 +2045,7 @@ conn_mark_stream_closed (struct ietf_full_conn *conn,
20342045
max_allowed = conn->ifc_max_allowed_stream_id[idx] >> SIT_SHIFT;
20352046
thresh = conn->ifc_closed_peer_streams[sd]
20362047
+ conn->ifc_max_streams_in[sd] / 2;
2037-
if (thresh >= max_allowed)
2048+
if (thresh >= max_allowed && can_give_peer_streams_credit(conn, sd))
20382049
{
20392050
LSQ_DEBUG("closed incoming %sdirectional streams reached "
20402051
"%"PRIu64", scheduled MAX_STREAMS frame",

src/liblsquic/lsquic_handshake.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -4216,7 +4216,7 @@ gquic2_esf_decrypt_packet (enc_session_t *enc_session_p,
42164216
goto err;
42174217
}
42184218

4219-
/* Bits 2 and 3 are not set and don't need to be check in gQUIC */
4219+
/* Bits 2 and 3 are not set and don't need to be checked in gQUIC */
42204220

42214221
packet_in->pi_data_sz = packet_in->pi_header_sz + out_sz;
42224222
if (packet_in->pi_flags & PI_OWN_DATA)

src/liblsquic/lsquic_mini_conn.c

+13-2
Original file line numberDiff line numberDiff line change
@@ -546,8 +546,12 @@ process_stream_frame (struct mini_conn *mc, lsquic_packet_in_t *packet_in,
546546
mc->mc_flags |= MC_HAVE_NEW_HSK;
547547
MCHIST_APPEND(mc, MCHE_NEW_HSK);
548548
if (0 == stream_frame.data_frame.df_offset)
549+
{
549550
/* First CHLO message: update maximum packet size */
550551
mc->mc_path.np_pack_size = packet_in->pi_data_sz;
552+
LSQ_DEBUG("update packet size to %hu",
553+
mc->mc_path.np_pack_size);
554+
}
551555
}
552556
else
553557
{
@@ -577,7 +581,7 @@ process_crypto_frame (struct mini_conn *mc, struct lsquic_packet_in *packet_in,
577581
{ /* This is not supported for simplicity: assume a single CRYPTO frame
578582
* per packet. If this changes, we can revisit this code.
579583
*/
580-
LSQ_INFO("two handshake stream frames in single incoming packet");
584+
LSQ_INFO("two CRYPTO frames in single incoming packet");
581585
MCHIST_APPEND(mc, MCHE_2HSK_1STREAM);
582586
return 0;
583587
}
@@ -588,8 +592,15 @@ process_crypto_frame (struct mini_conn *mc, struct lsquic_packet_in *packet_in,
588592
mc->mc_flags |= MC_HAVE_NEW_HSK;
589593
MCHIST_APPEND(mc, MCHE_NEW_HSK);
590594
if (0 == stream_frame.data_frame.df_offset)
595+
{
591596
/* First CHLO message: update maximum packet size */
592-
mc->mc_path.np_pack_size = packet_in->pi_data_sz;
597+
mc->mc_path.np_pack_size = packet_in->pi_data_sz
598+
/* Q050 and later adjust pi_data_sz of Initial packets during
599+
* decryption, here we have to add the tag length back:
600+
*/
601+
+ mc->mc_conn.cn_esf_c->esf_tag_len;
602+
LSQ_DEBUG("update packet size to %hu", mc->mc_path.np_pack_size);
603+
}
593604
}
594605
else
595606
{

src/liblsquic/lsquic_mini_conn_ietf.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -1030,7 +1030,7 @@ static unsigned (*const imico_process_frames[N_QUIC_FRAMES])
10301030
[QUIC_FRAME_ACK] = imico_process_ack_frame,
10311031
[QUIC_FRAME_PING] = imico_process_ping_frame,
10321032
[QUIC_FRAME_CONNECTION_CLOSE] = imico_process_connection_close_frame,
1033-
/* XXX: Some of them are invalid, while others are unexpected. We treat
1033+
/* Some of them are invalid, while others are unexpected. We treat
10341034
* them the same: handshake cannot proceed.
10351035
*/
10361036
[QUIC_FRAME_RST_STREAM] = imico_process_invalid_frame,

src/liblsquic/lsquic_parse_ietf_v1.c

+3
Original file line numberDiff line numberDiff line change
@@ -172,10 +172,13 @@ write_packno (unsigned char *p, lsquic_packno_t packno,
172172
{
173173
case IQUIC_PACKNO_LEN_4:
174174
*p++ = packno >> 24;
175+
/* fall through */
175176
case IQUIC_PACKNO_LEN_3:
176177
*p++ = packno >> 16;
178+
/* fall through */
177179
case IQUIC_PACKNO_LEN_2:
178180
*p++ = packno >> 8;
181+
/* fall through */
179182
default:
180183
*p++ = packno;
181184
}

src/liblsquic/lsquic_qdec_hdl.c

+95-1
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,16 @@ qdh_out_on_write (struct lsquic_stream *stream, lsquic_stream_ctx_t *ctx)
223223
LSQ_DEBUG("wrote %zd bytes to stream", nw);
224224
(void) lsquic_stream_flush(stream);
225225
if (lsquic_frab_list_empty(&qdh->qdh_fral))
226+
{
226227
lsquic_stream_wantwrite(stream, 0);
228+
if (qdh->qdh_on_dec_sent_func)
229+
{
230+
LSQ_DEBUG("buffered data written: call callback");
231+
qdh->qdh_on_dec_sent_func(qdh->qdh_on_dec_sent_ctx);
232+
qdh->qdh_on_dec_sent_func = NULL;
233+
qdh->qdh_on_dec_sent_ctx = NULL;
234+
}
235+
}
227236
}
228237
else
229238
{
@@ -294,7 +303,7 @@ qdh_read_encoder_stream (void *ctx, const unsigned char *buf, size_t sz,
294303
s = lsqpack_dec_enc_in(&qdh->qdh_decoder, buf, sz);
295304
if (s != 0)
296305
{
297-
LSQ_INFO("error reading decoder stream");
306+
LSQ_INFO("error reading encoder stream");
298307
qerr = lsqpack_dec_get_err_info(&qdh->qdh_decoder);
299308
qdh->qdh_conn->cn_if->ci_abort_error(qdh->qdh_conn, 1,
300309
HEC_QPACK_DECODER_STREAM_ERROR, "Error interpreting QPACK encoder "
@@ -376,6 +385,63 @@ qdh_hblock_unblocked (void *stream_p)
376385
}
377386

378387

388+
struct cont_len
389+
{
390+
unsigned long long value;
391+
int has; /* 1: set, 0: not set, -1: invalid */
392+
};
393+
394+
395+
static void
396+
process_content_length (const struct qpack_dec_hdl *qdh /* for logging */,
397+
struct cont_len *cl, const char *val /* not NUL-terminated */,
398+
unsigned len)
399+
{
400+
char *endcl, cont_len_buf[30];
401+
402+
if (0 == cl->has)
403+
{
404+
if (len >= sizeof(cont_len_buf))
405+
{
406+
LSQ_DEBUG("content-length has invalid value `%.*s'",
407+
(int) len, val);
408+
cl->has = -1;
409+
return;
410+
}
411+
memcpy(cont_len_buf, val, len);
412+
cont_len_buf[len] = '\0';
413+
cl->value = strtoull(cont_len_buf, &endcl, 10);
414+
if (*endcl == '\0' && !(ULLONG_MAX == cl->value && ERANGE == errno))
415+
{
416+
cl->has = 1;
417+
LSQ_DEBUG("content length is %llu", cl->value);
418+
}
419+
else
420+
{
421+
cl->has = -1;
422+
LSQ_DEBUG("content-length has invalid value `%.*s'",
423+
(int) len, val);
424+
}
425+
}
426+
else if (cl->has > 0)
427+
{
428+
LSQ_DEBUG("header set has two content-length: ambiguous, "
429+
"turn off checking");
430+
cl->has = -1;
431+
}
432+
}
433+
434+
435+
static int
436+
is_content_length (const struct lsqpack_header *header)
437+
{
438+
return ((header->qh_flags & QH_ID_SET) && header->qh_static_id == 4)
439+
|| (header->qh_name_len == 14 && header->qh_name[0] == 'c'
440+
&& 0 == memcmp(header->qh_name + 1, "ontent-length", 13))
441+
;
442+
}
443+
444+
379445
static int
380446
qdh_supply_hset_to_stream (struct qpack_dec_hdl *qdh,
381447
struct lsquic_stream *stream, struct lsqpack_header_list *qlist)
@@ -387,6 +453,7 @@ qdh_supply_hset_to_stream (struct qpack_dec_hdl *qdh,
387453
int push_promise;
388454
unsigned i;
389455
void *hset;
456+
struct cont_len cl;
390457

391458
push_promise = lsquic_stream_header_is_pp(stream);
392459
hset = hset_if->hsi_create_header_set(qdh->qdh_hsi_ctx, push_promise);
@@ -398,6 +465,7 @@ qdh_supply_hset_to_stream (struct qpack_dec_hdl *qdh,
398465

399466
LSQ_DEBUG("got header set for stream %"PRIu64, stream->id);
400467

468+
cl.has = 0;
401469
for (i = 0; i < qlist->qhl_count; ++i)
402470
{
403471
header = qlist->qhl_headers[i];
@@ -412,6 +480,9 @@ qdh_supply_hset_to_stream (struct qpack_dec_hdl *qdh,
412480
LSQ_INFO("header process returned non-OK code %u", (unsigned) st);
413481
goto err;
414482
}
483+
if (is_content_length(header))
484+
process_content_length(qdh, &cl, header->qh_value,
485+
header->qh_value_len);
415486
}
416487

417488
lsqpack_dec_destroy_header_list(qlist);
@@ -434,6 +505,8 @@ qdh_supply_hset_to_stream (struct qpack_dec_hdl *qdh,
434505
goto err;
435506
LSQ_DEBUG("converted qlist to hset and gave it to stream %"PRIu64,
436507
stream->id);
508+
if (cl.has > 0)
509+
(void) lsquic_stream_verify_len(stream, cl.value);
437510
return 0;
438511

439512
err:
@@ -590,3 +663,24 @@ lsquic_qdh_cancel_stream (struct qpack_dec_hdl *qdh,
590663
lsquic_qdh_unref_stream(qdh, stream);
591664
}
592665
}
666+
667+
668+
int
669+
lsquic_qdh_arm_if_unsent (struct qpack_dec_hdl *qdh, void (*func)(void *),
670+
void *ctx)
671+
{
672+
size_t bytes;
673+
674+
/* Use size of a single frab list buffer as an arbitrary threshold */
675+
bytes = lsquic_frab_list_size(&qdh->qdh_fral);
676+
if (bytes <= qdh->qdh_fral.fl_buf_size)
677+
return 0;
678+
else
679+
{
680+
LSQ_DEBUG("have %zu bytes of unsent QPACK decoder stream data: set "
681+
"up callback", bytes);
682+
qdh->qdh_on_dec_sent_func = func;
683+
qdh->qdh_on_dec_sent_ctx = ctx;
684+
return 1;
685+
}
686+
}

src/liblsquic/lsquic_qdec_hdl.h

+5
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ struct qpack_dec_hdl
3131
*qdh_enpub;
3232
struct http1x_ctor_ctx qdh_h1x_ctor_ctx;
3333
void *qdh_hsi_ctx;
34+
void (*qdh_on_dec_sent_func)(void *);
35+
void *qdh_on_dec_sent_ctx;
3436
};
3537

3638
int
@@ -67,6 +69,9 @@ void
6769
lsquic_qdh_cancel_stream (struct qpack_dec_hdl *,
6870
struct lsquic_stream *);
6971

72+
int
73+
lsquic_qdh_arm_if_unsent (struct qpack_dec_hdl *, void (*)(void *), void *);
74+
7075
extern const struct lsquic_stream_if *const lsquic_qdh_enc_sm_in_if;
7176
extern const struct lsquic_stream_if *const lsquic_qdh_dec_sm_out_if;
7277

0 commit comments

Comments
 (0)