mirror of
https://git.haproxy.org/git/haproxy.git/
synced 2025-08-07 15:47:01 +02:00
MINOR: h3: prepare support for response parsing
Refactor HTTP/3 request headers transcoding to HTX done in h3_headers_to_htx(). Some operations are extracted into dedicated functions, to check pseudo-headers and headers conformity, and also trim the value of headers before encoding it in HTX. The objective will be to simplify implementation of HTTP/3 response transcoding by reusing these functions. Also, h3_headers_to_htx() has been renamed to h3_req_headers_to_htx(), to highlight that it is reserved to frontend usage.
This commit is contained in:
parent
555ec99d43
commit
0eb35029dc
138
src/h3.c
138
src/h3.c
@ -510,6 +510,84 @@ static int h3_set_authority(struct qcs *qcs, struct ist *auth, const struct ist
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Ensure pseudo-header <h> is valid as it does not contains any invalid character. */
|
||||||
|
static int _h3_handle_phdr(struct qcs *qcs, const struct http_hdr *h)
|
||||||
|
{
|
||||||
|
/* look for forbidden control characters in the pseudo-header value */
|
||||||
|
const char *ctl = ist_find_ctl(h->v);
|
||||||
|
if (unlikely(ctl) && http_header_has_forbidden_char(h->v, ctl)) {
|
||||||
|
TRACE_ERROR("control character present in pseudo-header value", H3_EV_RX_FRAME|H3_EV_RX_HDR, qcs->qcc->conn, qcs);
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
err:
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure header <hdr> is valid. */
|
||||||
|
static int _h3_handle_hdr(struct qcs *qcs, const struct http_hdr *hdr)
|
||||||
|
{
|
||||||
|
const struct ist name = hdr->n;
|
||||||
|
const struct ist value = hdr->v;
|
||||||
|
const char *ctl;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (isteq(name, ist("")))
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
if (istmatch(name, ist(":"))) {
|
||||||
|
TRACE_ERROR("pseudo-header field after fields", H3_EV_RX_FRAME|H3_EV_RX_HDR, qcs->qcc->conn, qcs);
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < istlen(name); ++i) {
|
||||||
|
const char c = istptr(name)[i];
|
||||||
|
if ((uint8_t)(c - 'A') < 'Z' - 'A' || !HTTP_IS_TOKEN(c)) {
|
||||||
|
TRACE_ERROR("invalid characters in field name", H3_EV_RX_FRAME|H3_EV_RX_HDR, qcs->qcc->conn, qcs);
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* RFC 9114 10.3 Intermediary-Encapsulation Attacks
|
||||||
|
*
|
||||||
|
* While most values that can be encoded will not alter field
|
||||||
|
* parsing, carriage return (ASCII 0x0d), line feed (ASCII 0x0a),
|
||||||
|
* and the null character (ASCII 0x00) might be exploited by an
|
||||||
|
* attacker if they are translated verbatim. Any request or
|
||||||
|
* response that contains a character not permitted in a field
|
||||||
|
* value MUST be treated as malformed
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* look for forbidden control characters in the header value */
|
||||||
|
ctl = ist_find_ctl(value);
|
||||||
|
if (unlikely(ctl) && http_header_has_forbidden_char(value, ctl)) {
|
||||||
|
TRACE_ERROR("control character present in header value", H3_EV_RX_FRAME|H3_EV_RX_HDR, qcs->qcc->conn, qcs);
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
err:
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Remove any leading/trailing HTTP whitespace from <value>. Used to sanitize HTTP header values. */
|
||||||
|
static struct ist _h3_trim_header(struct ist value)
|
||||||
|
{
|
||||||
|
struct ist v;
|
||||||
|
|
||||||
|
for (v = value; v.len; v.len--) {
|
||||||
|
if (unlikely(HTTP_IS_LWS(*v.ptr)))
|
||||||
|
v.ptr++;
|
||||||
|
else if (!unlikely(HTTP_IS_LWS(v.ptr[v.len - 1])))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
/* Parse from buffer <buf> a H3 HEADERS frame of length <len>. Data are copied
|
/* Parse from buffer <buf> a H3 HEADERS frame of length <len>. Data are copied
|
||||||
* in a local HTX buffer and transfer to the stream connector layer. <fin> must be
|
* in a local HTX buffer and transfer to the stream connector layer. <fin> must be
|
||||||
* set if this is the last data to transfer from this stream.
|
* set if this is the last data to transfer from this stream.
|
||||||
@ -518,7 +596,7 @@ static int h3_set_authority(struct qcs *qcs, struct ist *auth, const struct ist
|
|||||||
* either the connection should be closed or the stream reset using codes
|
* either the connection should be closed or the stream reset using codes
|
||||||
* provided in h3c.err / h3s.err.
|
* provided in h3c.err / h3s.err.
|
||||||
*/
|
*/
|
||||||
static ssize_t h3_headers_to_htx(struct qcs *qcs, const struct buffer *buf,
|
static ssize_t h3_req_headers_to_htx(struct qcs *qcs, const struct buffer *buf,
|
||||||
uint64_t len, char fin)
|
uint64_t len, char fin)
|
||||||
{
|
{
|
||||||
struct h3s *h3s = qcs->ctx;
|
struct h3s *h3s = qcs->ctx;
|
||||||
@ -532,10 +610,8 @@ static ssize_t h3_headers_to_htx(struct qcs *qcs, const struct buffer *buf,
|
|||||||
struct ist meth = IST_NULL, path = IST_NULL;
|
struct ist meth = IST_NULL, path = IST_NULL;
|
||||||
struct ist scheme = IST_NULL, authority = IST_NULL;
|
struct ist scheme = IST_NULL, authority = IST_NULL;
|
||||||
struct ist uri;
|
struct ist uri;
|
||||||
struct ist v;
|
|
||||||
int hdr_idx, ret;
|
int hdr_idx, ret;
|
||||||
int cookie = -1, last_cookie = -1, i;
|
int cookie = -1, last_cookie = -1, i;
|
||||||
const char *ctl;
|
|
||||||
int relaxed = !!(h3c->qcc->proxy->options2 & PR_O2_REQBUG_OK);
|
int relaxed = !!(h3c->qcc->proxy->options2 & PR_O2_REQBUG_OK);
|
||||||
int qpack_err;
|
int qpack_err;
|
||||||
|
|
||||||
@ -613,10 +689,7 @@ static ssize_t h3_headers_to_htx(struct qcs *qcs, const struct buffer *buf,
|
|||||||
* value MUST be treated as malformed
|
* value MUST be treated as malformed
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* look for forbidden control characters in the pseudo-header value */
|
if (_h3_handle_phdr(qcs, &list[hdr_idx])) {
|
||||||
ctl = ist_find_ctl(list[hdr_idx].v);
|
|
||||||
if (unlikely(ctl) && http_header_has_forbidden_char(list[hdr_idx].v, ctl)) {
|
|
||||||
TRACE_ERROR("control character present in pseudo-header value", H3_EV_RX_FRAME|H3_EV_RX_HDR, qcs->qcc->conn, qcs);
|
|
||||||
h3s->err = H3_ERR_MESSAGE_ERROR;
|
h3s->err = H3_ERR_MESSAGE_ERROR;
|
||||||
qcc_report_glitch(h3c->qcc, 1);
|
qcc_report_glitch(h3c->qcc, 1);
|
||||||
len = -1;
|
len = -1;
|
||||||
@ -658,7 +731,7 @@ static ssize_t h3_headers_to_htx(struct qcs *qcs, const struct buffer *buf,
|
|||||||
/* we need to reject any control chars or '#' from the path,
|
/* we need to reject any control chars or '#' from the path,
|
||||||
* unless option accept-unsafe-violations-in-http-request is set.
|
* unless option accept-unsafe-violations-in-http-request is set.
|
||||||
*/
|
*/
|
||||||
ctl = ist_find_range(list[hdr_idx].v, 0, '#');
|
const char *ctl = ist_find_range(list[hdr_idx].v, 0, '#');
|
||||||
if (unlikely(ctl) && http_path_has_forbidden_char(list[hdr_idx].v, ctl)) {
|
if (unlikely(ctl) && http_path_has_forbidden_char(list[hdr_idx].v, ctl)) {
|
||||||
TRACE_ERROR("forbidden character in ':path' pseudo-header", H3_EV_RX_FRAME|H3_EV_RX_HDR, qcs->qcc->conn, qcs);
|
TRACE_ERROR("forbidden character in ':path' pseudo-header", H3_EV_RX_FRAME|H3_EV_RX_HDR, qcs->qcc->conn, qcs);
|
||||||
h3s->err = H3_ERR_MESSAGE_ERROR;
|
h3s->err = H3_ERR_MESSAGE_ERROR;
|
||||||
@ -842,40 +915,7 @@ static ssize_t h3_headers_to_htx(struct qcs *qcs, const struct buffer *buf,
|
|||||||
if (isteq(list[hdr_idx].n, ist("")))
|
if (isteq(list[hdr_idx].n, ist("")))
|
||||||
break;
|
break;
|
||||||
|
|
||||||
if (istmatch(list[hdr_idx].n, ist(":"))) {
|
if (_h3_handle_hdr(qcs, &list[hdr_idx])) {
|
||||||
TRACE_ERROR("pseudo-header field after fields", H3_EV_RX_FRAME|H3_EV_RX_HDR, qcs->qcc->conn, qcs);
|
|
||||||
h3s->err = H3_ERR_MESSAGE_ERROR;
|
|
||||||
qcc_report_glitch(h3c->qcc, 1);
|
|
||||||
len = -1;
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (i = 0; i < list[hdr_idx].n.len; ++i) {
|
|
||||||
const char c = list[hdr_idx].n.ptr[i];
|
|
||||||
if ((uint8_t)(c - 'A') < 'Z' - 'A' || !HTTP_IS_TOKEN(c)) {
|
|
||||||
TRACE_ERROR("invalid characters in field name", H3_EV_RX_FRAME|H3_EV_RX_HDR, qcs->qcc->conn, qcs);
|
|
||||||
h3s->err = H3_ERR_MESSAGE_ERROR;
|
|
||||||
qcc_report_glitch(h3c->qcc, 1);
|
|
||||||
len = -1;
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* RFC 9114 10.3 Intermediary-Encapsulation Attacks
|
|
||||||
*
|
|
||||||
* While most values that can be encoded will not alter field
|
|
||||||
* parsing, carriage return (ASCII 0x0d), line feed (ASCII 0x0a),
|
|
||||||
* and the null character (ASCII 0x00) might be exploited by an
|
|
||||||
* attacker if they are translated verbatim. Any request or
|
|
||||||
* response that contains a character not permitted in a field
|
|
||||||
* value MUST be treated as malformed
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* look for forbidden control characters in the header value */
|
|
||||||
ctl = ist_find_ctl(list[hdr_idx].v);
|
|
||||||
if (unlikely(ctl) && http_header_has_forbidden_char(list[hdr_idx].v, ctl)) {
|
|
||||||
TRACE_ERROR("control character present in header value", H3_EV_RX_FRAME|H3_EV_RX_HDR, qcs->qcc->conn, qcs);
|
|
||||||
h3s->err = H3_ERR_MESSAGE_ERROR;
|
h3s->err = H3_ERR_MESSAGE_ERROR;
|
||||||
qcc_report_glitch(h3c->qcc, 1);
|
qcc_report_glitch(h3c->qcc, 1);
|
||||||
len = -1;
|
len = -1;
|
||||||
@ -966,15 +1006,7 @@ static ssize_t h3_headers_to_htx(struct qcs *qcs, const struct buffer *buf,
|
|||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* trim leading/trailing LWS */
|
if (!htx_add_header(htx, list[hdr_idx].n, _h3_trim_header(list[hdr_idx].v))) {
|
||||||
for (v = list[hdr_idx].v; v.len; v.len--) {
|
|
||||||
if (unlikely(HTTP_IS_LWS(*v.ptr)))
|
|
||||||
v.ptr++;
|
|
||||||
else if (!unlikely(HTTP_IS_LWS(v.ptr[v.len - 1])))
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!htx_add_header(htx, list[hdr_idx].n, v)) {
|
|
||||||
len = -1;
|
len = -1;
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
@ -1148,7 +1180,7 @@ static ssize_t h3_trailers_to_htx(struct qcs *qcs, const struct buffer *buf,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* forbidden HTTP/3 headers, cf h3_headers_to_htx() */
|
/* forbidden HTTP/3 headers, cf h3_req_headers_to_htx() */
|
||||||
if (isteq(list[hdr_idx].n, ist("host")) ||
|
if (isteq(list[hdr_idx].n, ist("host")) ||
|
||||||
isteq(list[hdr_idx].n, ist("content-length")) ||
|
isteq(list[hdr_idx].n, ist("content-length")) ||
|
||||||
isteq(list[hdr_idx].n, ist("connection")) ||
|
isteq(list[hdr_idx].n, ist("connection")) ||
|
||||||
@ -1522,7 +1554,7 @@ static ssize_t h3_rcv_buf(struct qcs *qcs, struct buffer *b, int fin)
|
|||||||
break;
|
break;
|
||||||
case H3_FT_HEADERS:
|
case H3_FT_HEADERS:
|
||||||
if (h3s->st_req == H3S_ST_REQ_BEFORE) {
|
if (h3s->st_req == H3S_ST_REQ_BEFORE) {
|
||||||
ret = h3_headers_to_htx(qcs, b, flen, last_stream_frame);
|
ret = h3_req_headers_to_htx(qcs, b, flen, last_stream_frame);
|
||||||
h3s->st_req = H3S_ST_REQ_HEADERS;
|
h3s->st_req = H3S_ST_REQ_HEADERS;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
Loading…
Reference in New Issue
Block a user