From: Stefan Eissing Date: Fri, 1 Aug 2025 12:55:52 +0000 (+0200) Subject: openssl: check SSL_write() length on retries X-Git-Tag: curl-8_16_0~289 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=ec4c559104ad2af536e0a549ef87623da275d13e;p=thirdparty%2Fcurl.git openssl: check SSL_write() length on retries When an SSL_write() blocks we need to retry it with the same length as before or stupid OpenSSL freaks out. Remember it, limit any longer sends and fail shorter ones. Fixes #18121 Reported-by: adamse on github Closes #18132 --- diff --git a/lib/request.c b/lib/request.c index 0d9b23ef0c..9fc9ab4d63 100644 --- a/lib/request.c +++ b/lib/request.c @@ -380,8 +380,14 @@ CURLcode Curl_req_send(struct Curl_easy *data, struct dynbuf *req, data->req.httpversion_sent = httpversion; buf = curlx_dyn_ptr(req); blen = curlx_dyn_len(req); - if(!Curl_creader_total_length(data)) { - /* Request without body. Try to send directly from the buf given. */ + /* if the sendbuf is empty and the request without body and + * the length to send fits info a sendbuf chunk, we send it directly. + * If `blen` is larger then `chunk_size`, we can not. Because we + * might have to retry a blocked send later from sendbuf and that + * would result in retry sends with a shrunken length. That is trouble. */ + if(Curl_bufq_is_empty(&data->req.sendbuf) && + !Curl_creader_total_length(data) && + (blen <= data->req.sendbuf.chunk_size)) { data->req.eos_read = TRUE; result = xfer_send(data, buf, blen, blen, &nwritten); if(result) diff --git a/lib/vtls/openssl.c b/lib/vtls/openssl.c index a6b7d4e8e2..3cc0d8630b 100644 --- a/lib/vtls/openssl.c +++ b/lib/vtls/openssl.c @@ -5316,6 +5316,17 @@ static CURLcode ossl_send(struct Curl_cfilter *cf, connssl->io_need = CURL_SSL_IO_NEED_NONE; memlen = (len > (size_t)INT_MAX) ? INT_MAX : (int)len; + if(octx->blocked_ssl_write_len && (octx->blocked_ssl_write_len != memlen)) { + /* The previous SSL_write() call was blocked, using that length. + * We need to use that again or OpenSSL will freak out. A shorter + * length should not happen and is a bug in libcurl. */ + if(octx->blocked_ssl_write_len > memlen) { + DEBUGASSERT(0); + return CURLE_BAD_FUNCTION_ARGUMENT; + } + memlen = octx->blocked_ssl_write_len; + } + octx->blocked_ssl_write_len = 0; nwritten = SSL_write(octx->ssl, mem, memlen); if(nwritten > 0) @@ -5326,16 +5337,19 @@ static CURLcode ossl_send(struct Curl_cfilter *cf, switch(err) { case SSL_ERROR_WANT_READ: connssl->io_need = CURL_SSL_IO_NEED_RECV; + octx->blocked_ssl_write_len = memlen; result = CURLE_AGAIN; goto out; case SSL_ERROR_WANT_WRITE: result = CURLE_AGAIN; + octx->blocked_ssl_write_len = memlen; goto out; case SSL_ERROR_SYSCALL: { int sockerr = SOCKERRNO; if(octx->io_result == CURLE_AGAIN) { + octx->blocked_ssl_write_len = memlen; result = CURLE_AGAIN; goto out; } diff --git a/lib/vtls/openssl.h b/lib/vtls/openssl.h index 581afee068..54cfc5d663 100644 --- a/lib/vtls/openssl.h +++ b/lib/vtls/openssl.h @@ -68,6 +68,8 @@ struct ossl_ctx { X509* server_cert; BIO_METHOD *bio_method; CURLcode io_result; /* result of last BIO cfilter operation */ + /* blocked writes need to retry with same length, remember it */ + int blocked_ssl_write_len; #ifndef HAVE_KEYLOG_CALLBACK /* Set to true once a valid keylog entry has been created to avoid dupes. This is a bool and not a bitfield because it is passed by address. */ diff --git a/tests/http/test_02_download.py b/tests/http/test_02_download.py index 462e7645df..4ec781148c 100644 --- a/tests/http/test_02_download.py +++ b/tests/http/test_02_download.py @@ -754,7 +754,7 @@ class TestDownload: # download with looong urls @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3']) - @pytest.mark.parametrize("url_junk", [1024, 16*1024, 32*1024, 64*1024]) + @pytest.mark.parametrize("url_junk", [1024, 16*1024, 32*1024, 64*1024, 80*1024, 96*1024]) def test_02_36_looong_urls(self, env: Env, httpd, nghttpx, proto, url_junk): if proto == 'h3' and not env.have_h3(): pytest.skip("h3 not supported") @@ -784,6 +784,11 @@ class TestDownload: # h2 is unable to send such large headers (frame limits) r.check_exit_code(55) elif proto == 'h3': - r.check_exit_code(0) - # nghttpx reports 431 Request Header Field too Large - r.check_response(http_status=431) + if url_junk <= 64*1024: + r.check_exit_code(0) + # nghttpx reports 431 Request Header Field too Large + r.check_response(http_status=431) + else: + # nghttpx destroys the connection with internal error + # ERR_QPACK_HEADER_TOO_LARGE + r.check_exit_code(56)