Skip to content

Commit 7ca0b37

Browse files
authored
Release 4.7.1 (#648)
- [FIX] Content-length check in QPACK decoder handler. - [FIX] HTTP/1.x module: stricter validation for field names and values. - [FIX] Sample tools: fix recvmmsg receive batching (issue #434). - [FIX] crypto object ownership on promotion failure. - [IMPROVEMENT] Use ls-hpack v2.3.5.
1 parent 5f196cf commit 7ca0b37

8 files changed

Lines changed: 286 additions & 15 deletions

File tree

CHANGELOG

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
2026-05-25
2+
- 4.7.1
3+
- [FIX] Content-length check in QPACK decoder handler.
4+
- [FIX] HTTP/1.x module: stricter validation for field names and values.
5+
- [FIX] Sample tools: fix recvmmsg receive batching (issue #434).
6+
- [FIX] crypto object ownership on promotion failure.
7+
- [IMPROVEMENT] Use ls-hpack v2.3.5.
8+
19
2026-05-16
210
- 4.7.0
311
- [IMPROVEMENT, API] Refactor: drop mini-conn promotion shortcut.

docs/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
# The short X.Y version
2727
version = u'4.7'
2828
# The full version, including alpha/beta/rc tags
29-
release = u'4.7.0'
29+
release = u'4.7.1'
3030

3131

3232
# -- General configuration ---------------------------------------------------

include/lsquic.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ extern "C" {
2727

2828
#define LSQUIC_MAJOR_VERSION 4
2929
#define LSQUIC_MINOR_VERSION 7
30-
#define LSQUIC_PATCH_VERSION 0
30+
#define LSQUIC_PATCH_VERSION 1
3131

3232
#define LSQUIC_QUOTE(x) #x
3333
#define LSQUIC_SVAL(v) LSQUIC_QUOTE(v)

src/liblsquic/lsquic_full_conn_ietf.c

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@
9797
#define MAX(a, b) ((a) > (b) ? (a) : (b))
9898

9999

100+
100101
/* IMPORTANT: Keep values of IFC_SERVER and IFC_HTTP same as LSENG_SERVER
101102
* and LSENG_HTTP.
102103
*/
@@ -1448,6 +1449,18 @@ typedef char mini_conn_does_not_have_more_cces[
14481449
sizeof(((struct ietf_mini_conn *)0)->imc_cces)
14491450
<= sizeof(((struct ietf_full_conn *)0)->ifc_cces) ? 1 : -1];
14501451

1452+
1453+
/* Promotion failed: give ownership of crypto object back to mini conn. */
1454+
static void
1455+
give_enc_session_back_to_mini (struct lsquic_conn *mini_conn,
1456+
struct ietf_full_conn *conn)
1457+
{
1458+
mini_conn->cn_enc_session = conn->ifc_conn.cn_enc_session;
1459+
conn->ifc_conn.cn_enc_session = NULL;
1460+
mini_conn->cn_esf_c->esf_set_conn(mini_conn->cn_enc_session, mini_conn);
1461+
}
1462+
1463+
14511464
struct lsquic_conn *
14521465
lsquic_ietf_full_conn_server_new (struct lsquic_engine_public *enpub,
14531466
unsigned flags, struct lsquic_conn *mini_conn)
@@ -1653,12 +1666,17 @@ lsquic_ietf_full_conn_server_new (struct lsquic_engine_public *enpub,
16531666
return &conn->ifc_conn;
16541667

16551668
err3:
1669+
assert(mini_conn->cn_enc_session == NULL);
1670+
give_enc_session_back_to_mini(mini_conn, conn);
16561671
ietf_full_conn_ci_destroy(&conn->ifc_conn);
16571672
return NULL;
16581673

16591674
err2:
1675+
assert(mini_conn->cn_enc_session == NULL);
1676+
give_enc_session_back_to_mini(mini_conn, conn);
16601677
lsquic_malo_destroy(conn->ifc_pub.packet_out_malo);
16611678
err1:
1679+
assert(mini_conn->cn_enc_session != NULL);
16621680
lsquic_send_ctl_cleanup(&conn->ifc_send_ctl);
16631681
if (conn->ifc_pub.all_streams)
16641682
lsquic_hash_destroy(conn->ifc_pub.all_streams);
@@ -9784,4 +9802,6 @@ lsquic_ietf_full_conn_test_push_disabled (unsigned results[5])
97849802
free(conn.ifc_errmsg);
97859803
}
97869804

9805+
97879806
typedef char dcid_elem_fits_in_128_bytes[sizeof(struct dcid_elem) <= 128 ? 1 : - 1];
9807+

src/liblsquic/lsquic_http1x_if.c

Lines changed: 78 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,59 @@ struct header_writer_ctx
6464
/* flags for frames with request headers */
6565
#define HWC_REQUEST_HANDLING_FLAGS (HWC_SERVER|HWC_PUSH_PROMISE)
6666

67+
68+
/* Field name and value grammar: [RFC 9110] Sections 5.1 (Field Names),
69+
* 5.5 (Field Values), and 5.6.2 (Tokens).
70+
* Lowercase field names: [RFC 9113] Section 8.2 (HTTP Fields) and
71+
* [RFC 9114] Section 4.1.2 (Malformed Requests and Responses).
72+
*/
73+
static const unsigned char is_tchar[0x100] =
74+
{
75+
['0'] = 1, ['1'] = 1, ['2'] = 1, ['3'] = 1, ['4'] = 1, ['5'] = 1,
76+
['6'] = 1, ['7'] = 1, ['8'] = 1, ['9'] = 1, ['a'] = 1, ['b'] = 1,
77+
['c'] = 1, ['d'] = 1, ['e'] = 1, ['f'] = 1, ['g'] = 1, ['h'] = 1,
78+
['i'] = 1, ['j'] = 1, ['k'] = 1, ['l'] = 1, ['m'] = 1, ['n'] = 1,
79+
['o'] = 1, ['p'] = 1, ['q'] = 1, ['r'] = 1, ['s'] = 1, ['t'] = 1,
80+
['u'] = 1, ['v'] = 1, ['w'] = 1, ['x'] = 1, ['y'] = 1, ['z'] = 1,
81+
['!'] = 1, ['#'] = 1, ['$'] = 1, ['%'] = 1, ['&'] = 1, ['\''] = 1,
82+
['*'] = 1, ['+'] = 1, ['-'] = 1, ['.'] = 1, ['^'] = 1, ['_'] = 1,
83+
['`'] = 1, ['|'] = 1, ['~'] = 1,
84+
};
85+
86+
87+
static int
88+
valid_field_name (const char *name, unsigned name_len)
89+
{
90+
unsigned i;
91+
92+
if (name_len == 0)
93+
return 0;
94+
95+
for (i = 0; i < name_len; ++i)
96+
if (!is_tchar[(unsigned char) name[i]])
97+
return 0;
98+
99+
return 1;
100+
}
101+
102+
103+
static int
104+
valid_field_value (const char *val, unsigned val_len)
105+
{
106+
unsigned i;
107+
108+
for (i = 0; i < val_len; ++i)
109+
if (!((unsigned char) val[i] == '\t' ||
110+
((unsigned char) val[i] >= 0x20 && (unsigned char) val[i] <= 0x7E) ||
111+
(unsigned char) val[i] >= 0x80))
112+
{
113+
return 0;
114+
}
115+
116+
return 1;
117+
}
118+
119+
67120
static void *
68121
h1h_create_header_set (void *ctx, lsquic_stream_t *stream, int is_push_promise)
69122
{
@@ -147,6 +200,13 @@ add_pseudo_header (struct header_writer_ctx *hwc, struct lsxpack_header *xhdr)
147200
name_len = xhdr->name_len;
148201
val_len = xhdr->val_len;
149202

203+
if (!valid_field_value(val, val_len))
204+
{
205+
LSQ_INFO("pseudo-header `%.*s' contains invalid characters",
206+
name_len, name);
207+
return 1;
208+
}
209+
150210
switch (name_len)
151211
{
152212
case 5:
@@ -382,8 +442,6 @@ static int
382442
add_real_header (struct header_writer_ctx *hwc, struct lsxpack_header *xhdr)
383443
{
384444
int err;
385-
unsigned i;
386-
int n_upper;
387445
const char *name, *val;
388446
unsigned name_len, val_len;
389447

@@ -399,6 +457,19 @@ add_real_header (struct header_writer_ctx *hwc, struct lsxpack_header *xhdr)
399457
name_len = xhdr->name_len;
400458
val_len = xhdr->val_len;
401459

460+
if (!valid_field_name(name, name_len))
461+
{
462+
LSQ_INFO("invalid header name `%.*s'", name_len, name);
463+
return 1;
464+
}
465+
466+
if (!valid_field_value(val, val_len))
467+
{
468+
LSQ_INFO("header `%.*s' contains invalid value characters",
469+
name_len, name);
470+
return 1;
471+
}
472+
402473
if (4 == name_len && 0 == memcmp(name, "host", 4))
403474
{
404475
if(hwc->pseh_mask & BIT(PSEH_AUTHORITY))
@@ -410,16 +481,6 @@ add_real_header (struct header_writer_ctx *hwc, struct lsxpack_header *xhdr)
410481
hwc->hwc_flags |= HWC_SEEN_HOST;
411482
}
412483

413-
n_upper = 0;
414-
for (i = 0; i < name_len; ++i)
415-
n_upper += isupper(name[i]);
416-
if (n_upper > 0)
417-
{
418-
LSQ_INFO("Header name `%.*s' contains uppercase letters",
419-
name_len, name);
420-
return 1;
421-
}
422-
423484
if (6 == name_len && memcmp(name, "cookie", 6) == 0)
424485
{
425486
return save_cookie(hwc, val, val_len);
@@ -453,6 +514,11 @@ add_header_to_uh (struct header_writer_ctx *hwc, struct lsxpack_header *xhdr)
453514
const char *name;
454515

455516
name = lsxpack_header_get_name(xhdr);
517+
if (xhdr->name_len == 0)
518+
{
519+
LSQ_INFO("empty header name");
520+
return 1;
521+
}
456522
LSQ_DEBUG("Got header '%.*s': '%.*s'", (int) xhdr->name_len, name,
457523
(int) xhdr->val_len, lsxpack_header_get_value(xhdr));
458524
if (':' == name[0])

src/liblsquic/lsquic_qdec_hdl.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -509,7 +509,7 @@ is_content_length (const struct lsxpack_header *xhdr)
509509
return ((xhdr->flags & LSXPACK_QPACK_IDX)
510510
&& xhdr->qpack_index == LSQPACK_TNV_CONTENT_LENGTH_0)
511511
|| (xhdr->name_len == 14 && 0 == memcmp(lsxpack_header_get_name(xhdr),
512-
"content-length", 13))
512+
"content-length", 14))
513513
;
514514
}
515515

tests/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ SET(TESTS
4545
goaway_gquic_be
4646
hkdf
4747
hpi
48+
http1x_if
4849
lsquic_hash
4950
mini_conn_delay
5051
packet_out

0 commit comments

Comments
 (0)