Skip to content

very long urls and openssl and curl_easy_perform fails #18121

@adamse

Description

@adamse

I did this

This test program fails with CURLE_SEND_ERROR if the https url in the file in argv[1] is longer than ~u16 max.

CURLE_SEND_ERROR curl_easy_perform() failed: Failed sending data to the peer
SSL_write() error: error:0A00010F:SSL routines::bad length

(Please excuse the C++)

#include <iostream>
#include <fstream>
#include <string>
#include <cassert>

#include <curl/curl.h>

size_t WriteCallback(void* contents, size_t size, size_t nmemb, void* userp) {
    ((std::string*)userp)->append((char*)contents, size * nmemb);
    return size * nmemb;
}

int main(int argc, char* argv[]) {
    if (argc < 2) {
        std::cerr << "Usage: " << argv[0] << " <url_file>" << std::endl;
        return 1;
    }

    std::ifstream urlFile(argv[1]);
    if (!urlFile.is_open()) {
        std::cerr << "Failed to open file: " << argv[1] << std::endl;
        return 1;
    }

    std::string url;
    std::getline(urlFile, url);
    urlFile.close();

    CURL* curl;
    CURLcode res;
    std::string response;

    curl = curl_easy_init();
    assert(curl && "Failed to initialize CURL.");

    char* err = (char*)malloc(CURL_ERROR_SIZE);
    assert(err && "Failed to alloc err buf");

    curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
    curl_easy_setopt(curl, CURLOPT_SSL_OPTIONS, CURLSSLOPT_NATIVE_CA);
    curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, err);

    res = curl_easy_perform(curl);
    if (res == CURLE_SEND_ERROR) {
        std::cerr << "CURLE_SEND_ERROR curl_easy_perform() failed: " << curl_easy_strerror(res) << std::endl;
        std::cerr << err << std::endl;
    } else if (res != CURLE_OK) {
        std::cerr << "curl_easy_perform() failed: " << curl_easy_strerror(res) << std::endl;
        std::cerr << std::string(err) << std::endl;
    } else {
        std::cout << "Successful" << std::endl;
    }

    curl_easy_cleanup(curl);
    free(err);

    return 0;
}

I think this is an issue with how openssl SSL_write is used, where it errors with SSL_ERROR_WANT_WRITE and expects to be presented with the same buffer again until it succeeds, but curl's openssl integration does something that is not exactly that.

I tried a hacky workaround to confirm if this could be the issue, and it seems to work.

diff --git a/lib/vtls/openssl.c b/lib/vtls/openssl.c
index 6ded5f3c0..712c70657 100644
--- a/lib/vtls/openssl.c
+++ b/lib/vtls/openssl.c
@@ -5086,9 +5086,22 @@ static ssize_t ossl_send(struct Curl_cfilter *cf,
   memlen = (len > (size_t)INT_MAX) ? INT_MAX : (int)len;
   rc = SSL_write(octx->ssl, mem, memlen);

-  if(rc <= 0) {
+  while (rc <= 0) {
     err = SSL_get_error(octx->ssl, rc);
+    if (err == SSL_ERROR_WANT_WRITE) {
+      // HACK: when erroring with SSL_ERROR_WANT_WRITE openssl wants you to
+      // present the same buffer again once the underlying socket is ready.
+      // Here we just try in a loop instead of polling the socket.
+      //
+      // This works around an issue with curl and https and _very_ long urls
+      // (u16 max seems to be the breaking point for the size of the headers).
+      rc = SSL_write(octx->ssl, mem, memlen);
+    } else {
+      break;
+    }
+  }

+  if(rc <= 0) {
     switch(err) {
     case SSL_ERROR_WANT_READ:
       connssl->io_need = CURL_SSL_IO_NEED_RECV;

I expected the following

No response

curl/libcurl version

libcurl 8.12.1 & libcurl 8.15.0
openssl 3.4.1

operating system

Windows 10

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions