http3: check stream_ctx more thoroughly in all backends

- callbacks and filter methods might be invoked at unexpected
  times, e.g. when the transfer's stream_ctx has not been initialized
  yet or, more likely, has already been taken down.
- check for existance of stream_ctx in such places and return
  an error or silently succeed the call.

Closes #10951
This commit is contained in:
Stefan Eissing 2023-04-13 11:03:50 +02:00 committed by Daniel Stenberg
parent 7e68133d04
commit be800a6cab
No known key found for this signature in database
GPG key ID: 5CC908FDB71E12C2
3 changed files with 108 additions and 25 deletions

View file

@ -288,6 +288,9 @@ static CURLcode write_resp_raw(struct Curl_easy *data,
CURLcode result = CURLE_OK;
ssize_t nwritten;
if(!stream)
return CURLE_RECV_ERROR;
nwritten = Curl_bufq_write(&stream->recvbuf, mem, memlen, &result);
if(nwritten < 0) {
return result;
@ -311,7 +314,7 @@ static void MSH3_CALL msh3_header_received(MSH3_REQUEST *Request,
CURLcode result;
(void)Request;
if(stream->recv_header_complete) {
if(!stream || stream->recv_header_complete) {
return;
}
@ -366,6 +369,9 @@ static bool MSH3_CALL msh3_data_received(MSH3_REQUEST *Request,
* length (buflen is an inout parameter).
*/
(void)Request;
if(!stream)
return FALSE;
msh3_lock_acquire(&stream->recv_lock);
if(!stream->recv_header_complete) {
@ -395,6 +401,8 @@ static void MSH3_CALL msh3_complete(MSH3_REQUEST *Request, void *IfContext,
struct stream_ctx *stream = H3_STREAM_CTX(data);
(void)Request;
if(!stream)
return;
msh3_lock_acquire(&stream->recv_lock);
stream->closed = TRUE;
stream->recv_header_complete = true;
@ -410,6 +418,9 @@ static void MSH3_CALL msh3_shutdown_complete(MSH3_REQUEST *Request,
{
struct Curl_easy *data = IfContext;
struct stream_ctx *stream = H3_STREAM_CTX(data);
if(!stream)
return;
(void)Request;
(void)stream;
}
@ -419,6 +430,8 @@ static void MSH3_CALL msh3_data_sent(MSH3_REQUEST *Request,
{
struct Curl_easy *data = IfContext;
struct stream_ctx *stream = H3_STREAM_CTX(data);
if(!stream)
return;
(void)Request;
(void)stream;
(void)SendContext;
@ -431,6 +444,10 @@ static ssize_t recv_closed_stream(struct Curl_cfilter *cf,
struct stream_ctx *stream = H3_STREAM_CTX(data);
ssize_t nread = -1;
if(!stream) {
*err = CURLE_RECV_ERROR;
return -1;
}
(void)cf;
if(stream->reset) {
failf(data, "HTTP/3 stream reset by server");
@ -480,6 +497,10 @@ static ssize_t cf_msh3_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
struct cf_call_data save;
(void)cf;
if(!stream) {
*err = CURLE_RECV_ERROR;
return -1;
}
CF_DATA_SAVE(save, cf, data);
DEBUGF(LOG_CF(data, cf, "req: recv with %zu byte buffer", len));
@ -535,6 +556,7 @@ static ssize_t cf_msh3_send(struct Curl_cfilter *cf, struct Curl_easy *data,
CF_DATA_SAVE(save, cf, data);
/* Sizes must match for cast below to work" */
DEBUGASSERT(stream);
DEBUGASSERT(sizeof(MSH3_HEADER) == sizeof(struct h2h3pseudo));
DEBUGF(LOG_CF(data, cf, "req: send %zu bytes", len));
@ -628,7 +650,7 @@ static bool cf_msh3_data_pending(struct Curl_cfilter *cf,
CF_DATA_SAVE(save, cf, data);
(void)cf;
if(stream->req) {
if(stream && stream->req) {
msh3_lock_acquire(&stream->recv_lock);
DEBUGF(LOG_CF((struct Curl_easy *)data, cf, "data pending = %zu",
Curl_bufq_len(&stream->recvbuf)));
@ -678,11 +700,14 @@ static CURLcode cf_msh3_data_event(struct Curl_cfilter *cf,
break;
case CF_CTRL_DATA_DONE_SEND:
DEBUGF(LOG_CF(data, cf, "req: send done"));
stream->upload_done = TRUE;
if(stream && stream->req) {
char buf[1];
if(!MsH3RequestSend(stream->req, MSH3_REQUEST_FLAG_FIN, buf, 0, data)) {
result = CURLE_SEND_ERROR;
if(stream) {
stream->upload_done = TRUE;
if(stream->req) {
char buf[1];
if(!MsH3RequestSend(stream->req, MSH3_REQUEST_FLAG_FIN,
buf, 0, data)) {
result = CURLE_SEND_ERROR;
}
}
}
break;

View file

@ -688,6 +688,8 @@ static void report_consumed_data(struct Curl_cfilter *cf,
struct stream_ctx *stream = H3_STREAM_CTX(data);
struct cf_ngtcp2_ctx *ctx = cf->ctx;
if(!stream)
return;
/* the HTTP/1.1 response headers are written to the buffer, but
* consuming those does not count against flow control. */
if(stream->recv_buf_nonflow) {
@ -984,7 +986,7 @@ static int cf_ngtcp2_get_select_socks(struct Curl_cfilter *cf,
if((k->keepon & KEEP_SENDBITS) == KEEP_SEND &&
ngtcp2_conn_get_cwnd_left(ctx->qconn) &&
ngtcp2_conn_get_max_data_left(ctx->qconn) &&
nghttp3_conn_is_stream_writable(ctx->h3conn, stream->id))
stream && nghttp3_conn_is_stream_writable(ctx->h3conn, stream->id))
rv |= GETSOCK_WRITESOCK(0);
DEBUGF(LOG_CF(data, cf, "get_select_socks -> %x (sock=%d)",
@ -1015,6 +1017,10 @@ static int cb_h3_stream_close(nghttp3_conn *conn, int64_t stream_id,
(void)app_error_code;
(void)cf;
/* we might be called by nghttp3 after we already cleaned up */
if(!stream)
return 0;
DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] h3 close(err=%" PRId64 ")",
stream_id, app_error_code));
stream->closed = TRUE;
@ -1041,6 +1047,9 @@ static CURLcode write_resp_raw(struct Curl_cfilter *cf,
ssize_t nwritten;
(void)cf;
if(!stream) {
return CURLE_RECV_ERROR;
}
nwritten = Curl_bufq_write(&stream->recvbuf, mem, memlen, &result);
/* DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] add recvbuf(len=%zu) "
"-> %zd, %d", stream->id, memlen, nwritten, result));
@ -1107,6 +1116,8 @@ static int cb_h3_end_headers(nghttp3_conn *conn, int64_t stream_id,
(void)fin;
(void)cf;
if(!stream)
return 0;
/* add a CRLF only if we've received some headers */
result = write_resp_raw(cf, data, "\r\n", 2, FALSE);
if(result) {
@ -1141,6 +1152,10 @@ static int cb_h3_recv_header(nghttp3_conn *conn, int64_t stream_id,
(void)flags;
(void)cf;
/* we might have cleaned up this transfer already */
if(!stream)
return 0;
if(token == NGHTTP3_QPACK_TOKEN__STATUS) {
char line[14]; /* status line is always 13 characters long */
size_t ncopy;
@ -1306,7 +1321,7 @@ static ssize_t recv_closed_stream(struct Curl_cfilter *cf,
ssize_t nread = -1;
(void)cf;
DEBUGASSERT(stream);
if(stream->reset) {
failf(data,
"HTTP/3 stream %" PRId64 " reset by server", stream->id);
@ -1365,6 +1380,11 @@ static ssize_t cf_ngtcp2_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
DEBUGASSERT(ctx->h3conn);
*err = CURLE_OK;
if(!stream) {
*err = CURLE_RECV_ERROR;
goto out;
}
if(!Curl_bufq_is_empty(&stream->recvbuf)) {
nread = Curl_bufq_read(&stream->recvbuf,
(unsigned char *)buf, len, err);
@ -1414,7 +1434,7 @@ out:
goto out;
}
DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] cf_recv(len=%zu) -> %zd, %d",
stream->id, len, nread, *err));
stream? stream->id : -1, len, nread, *err));
CF_DATA_RESTORE(cf, save);
return nread;
}
@ -1428,6 +1448,8 @@ static int cb_h3_acked_req_body(nghttp3_conn *conn, int64_t stream_id,
struct stream_ctx *stream = H3_STREAM_CTX(data);
(void)cf;
if(!stream)
return 0;
/* The server ackknowledged `datalen` of bytes from our request body.
* This is a delta. We have kept this data in `sendbuf` for
* re-transmissions and can free it now. */
@ -1471,6 +1493,8 @@ cb_h3_read_req_body(nghttp3_conn *conn, int64_t stream_id,
(void)user_data;
(void)veccnt;
if(!stream)
return NGHTTP3_ERR_CALLBACK_FAILURE;
/* nghttp3 keeps references to the sendbuf data until it is ACKed
* by the server (see `cb_h3_acked_req_body()` for updates).
* `sendbuf_len_in_flight` is the amount of bytes in `sendbuf`
@ -2065,7 +2089,7 @@ static bool cf_ngtcp2_data_pending(struct Curl_cfilter *cf,
{
const struct stream_ctx *stream = H3_STREAM_CTX(data);
(void)cf;
return !Curl_bufq_is_empty(&stream->recvbuf);
return stream && !Curl_bufq_is_empty(&stream->recvbuf);
}
static CURLcode cf_ngtcp2_data_event(struct Curl_cfilter *cf,
@ -2088,8 +2112,10 @@ static CURLcode cf_ngtcp2_data_event(struct Curl_cfilter *cf,
}
case CF_CTRL_DATA_DONE_SEND: {
struct stream_ctx *stream = H3_STREAM_CTX(data);
stream->upload_done = TRUE;
(void)nghttp3_conn_resume_stream(ctx->h3conn, stream->id);
if(stream) {
stream->upload_done = TRUE;
(void)nghttp3_conn_resume_stream(ctx->h3conn, stream->id);
}
break;
}
case CF_CTRL_DATA_IDLE:

View file

@ -344,6 +344,8 @@ static CURLcode write_resp_raw(struct Curl_cfilter *cf,
ssize_t nwritten;
(void)cf;
if(!stream)
return CURLE_RECV_ERROR;
nwritten = Curl_bufq_write(&stream->recvbuf, mem, memlen, &result);
if(nwritten < 0)
return result;
@ -390,7 +392,7 @@ static int cb_each_header(uint8_t *name, size_t name_len,
if(result) {
DEBUGF(LOG_CF(x->data, x->cf,
"[h3sid=%"PRId64"][HEADERS][%.*s: %.*s] error %d",
stream->id, (int)name_len, name,
stream? stream->id : -1, (int)name_len, name,
(int)value_len, value, result));
}
return result;
@ -405,6 +407,11 @@ static ssize_t stream_resp_read(void *reader_ctx,
struct stream_ctx *stream = H3_STREAM_CTX(x->data);
ssize_t nread;
if(!stream) {
*err = CURLE_RECV_ERROR;
return -1;
}
nread = quiche_h3_recv_body(ctx->h3c, ctx->qconn, stream->id,
buf, len);
if(nread >= 0) {
@ -429,6 +436,9 @@ static CURLcode cf_recv_body(struct Curl_cfilter *cf,
struct cb_ctx cb_ctx;
CURLcode result = CURLE_OK;
if(!stream)
return CURLE_RECV_ERROR;
if(!stream->resp_hds_complete) {
result = write_resp_raw(cf, data, "\r\n", 2);
if(result)
@ -486,6 +496,8 @@ static CURLcode h3_process_event(struct Curl_cfilter *cf,
CURLcode result = CURLE_OK;
int rc;
if(!stream)
return CURLE_OK;
DEBUGASSERT(stream3_id == stream->id);
switch(quiche_h3_event_type(ev)) {
case QUICHE_H3_EVENT_HEADERS:
@ -764,6 +776,7 @@ static ssize_t recv_closed_stream(struct Curl_cfilter *cf,
struct stream_ctx *stream = H3_STREAM_CTX(data);
ssize_t nread = -1;
DEBUGASSERT(stream);
if(stream->reset) {
failf(data,
"HTTP/3 stream %" PRId64 " reset by server", stream->id);
@ -798,6 +811,11 @@ static ssize_t cf_quiche_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
ssize_t nread = -1;
CURLcode result;
if(!stream) {
*err = CURLE_RECV_ERROR;
goto out;
}
if(!Curl_bufq_is_empty(&stream->recvbuf)) {
nread = Curl_bufq_read(&stream->recvbuf,
(unsigned char *)buf, len, err);
@ -868,6 +886,14 @@ static ssize_t h3_open_stream(struct Curl_cfilter *cf,
quiche_h3_header *nva = NULL;
struct h2h3req *hreq = NULL;
if(!stream) {
*err = h3_data_setup(cf, data);
if(*err)
goto fail;
stream = H3_STREAM_CTX(data);
DEBUGASSERT(stream);
}
if(!stream->req_hds_len) {
stream->req_hds_len = len; /* fist call */
}
@ -969,10 +995,11 @@ static ssize_t cf_quiche_send(struct Curl_cfilter *cf, struct Curl_easy *data,
goto out;
}
if(stream->id < 0) {
if(!stream || stream->id < 0) {
nwritten = h3_open_stream(cf, data, buf, len, err);
if(nwritten < 0)
goto out;
stream = H3_STREAM_CTX(data);
}
else {
nwritten = quiche_h3_send_body(ctx->h3c, ctx->qconn, stream->id,
@ -1019,7 +1046,7 @@ out:
nwritten = -1;
}
DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] cf_send(len=%zu) -> %zd, %d",
stream->id, len, nwritten, *err));
stream? stream->id : -1, len, nwritten, *err));
return nwritten;
}
@ -1028,10 +1055,13 @@ static bool stream_is_writeable(struct Curl_cfilter *cf,
{
struct cf_quiche_ctx *ctx = cf->ctx;
struct stream_ctx *stream = H3_STREAM_CTX(data);
quiche_stream_iter *qiter;
bool is_writable = FALSE;
if(!stream)
return FALSE;
/* surely, there must be a better way */
quiche_stream_iter *qiter = quiche_conn_writable(ctx->qconn);
qiter = quiche_conn_writable(ctx->qconn);
if(qiter) {
uint64_t stream_id;
while(quiche_stream_iter_next(qiter, &stream_id)) {
@ -1076,7 +1106,7 @@ static bool cf_quiche_data_pending(struct Curl_cfilter *cf,
{
const struct stream_ctx *stream = H3_STREAM_CTX(data);
(void)cf;
return !Curl_bufq_is_empty(&stream->recvbuf);
return stream && !Curl_bufq_is_empty(&stream->recvbuf);
}
static CURLcode cf_quiche_data_event(struct Curl_cfilter *cf,
@ -1098,14 +1128,16 @@ static CURLcode cf_quiche_data_event(struct Curl_cfilter *cf,
}
case CF_CTRL_DATA_DONE_SEND: {
struct stream_ctx *stream = H3_STREAM_CTX(data);
unsigned char body[1];
ssize_t sent;
stream->upload_done = TRUE;
if(stream) {
unsigned char body[1];
ssize_t sent;
stream->upload_done = TRUE;
body[0] = 'X';
sent = cf_quiche_send(cf, data, body, 0, &result);
DEBUGF(LOG_CF(data, cf, "[h3sid=%"PRId64"] DONE_SEND -> %zd, %d",
stream->id, sent, result));
body[0] = 'X';
sent = cf_quiche_send(cf, data, body, 0, &result);
DEBUGF(LOG_CF(data, cf, "[h3sid=%"PRId64"] DONE_SEND -> %zd, %d",
stream->id, sent, result));
}
break;
}
case CF_CTRL_DATA_IDLE: