url: connection credentials origin

When tying credentials to a connection (NTLM, Negotiate) also link the
origin the credentials are for. This prevents a connection reuse with
the same credentials, but intended for another origin.

The mis-reuse could happen for a forwarding proxy and NTLM (although, in
the mind of the person writing this, it is an insane setup).

Closes #22040
This commit is contained in:
Stefan Eissing 2026-06-16 10:12:24 +02:00 committed by Daniel Stenberg
parent c2b050e4e4
commit bd10924b47
No known key found for this signature in database
GPG key ID: 5CC908FDB71E12C2
4 changed files with 28 additions and 14 deletions

View file

@ -42,6 +42,7 @@ static void http_auth_nego_reset(struct connectdata *conn,
conn->proxy_negotiate_state = GSS_AUTHNONE;
else {
conn->http_negotiate_state = GSS_AUTHNONE;
Curl_peer_unlink(&conn->creds_origin);
Curl_creds_unlink(&conn->creds);
}
if(neg_ctx)
@ -132,13 +133,19 @@ CURLcode Curl_input_negotiate(struct Curl_easy *data, struct connectdata *conn,
if(result)
http_auth_nego_reset(conn, neg_ctx, proxy);
if(!proxy) {
if(!result && !proxy) {
/* Start it up. From this time onwards, the connection is tied
* tp the credentials used. */
if(conn->creds_origin &&
!Curl_peer_equal(conn->creds_origin, data->state.origin)) {
DEBUGASSERT(0); /* should not happen. */
return CURLE_FAILED_INIT;
}
if(conn->creds && !Curl_creds_same(creds, conn->creds)) {
DEBUGASSERT(0); /* should not happen. */
return CURLE_FAILED_INIT;
}
Curl_peer_link(&conn->creds_origin, data->state.origin);
Curl_creds_link(&conn->creds, creds);
}

View file

@ -93,6 +93,8 @@ CURLcode Curl_input_ntlm(struct Curl_easy *data,
else if(*state == NTLMSTATE_TYPE3) {
infof(data, "NTLM handshake rejected");
Curl_auth_ntlm_remove(conn, proxy);
Curl_peer_unlink(&conn->creds_origin);
Curl_creds_unlink(&conn->creds);
*state = NTLMSTATE_NONE;
return CURLE_REMOTE_ACCESS_DENIED;
}
@ -184,10 +186,16 @@ CURLcode Curl_output_ntlm(struct Curl_easy *data, bool proxy)
if(!proxy) {
/* Start it up. From this time onwards, the connection is tied
* tp the credentials used. */
if(conn->creds_origin &&
!Curl_peer_equal(conn->creds_origin, data->state.origin)) {
DEBUGASSERT(0); /* should not happen. */
return CURLE_FAILED_INIT;
}
if(conn->creds && !Curl_creds_same(creds, conn->creds)) {
DEBUGASSERT(0); /* should not happen. */
return CURLE_FAILED_INIT;
}
Curl_peer_link(&conn->creds_origin, data->state.origin);
Curl_creds_link(&conn->creds, creds);
}
result = Curl_auth_create_ntlm_type1_message(data, creds, "HTTP",

View file

@ -515,6 +515,7 @@ void Curl_conn_free(struct Curl_easy *data, struct connectdata *conn)
Curl_creds_unlink(&conn->socks_proxy.creds);
#endif
Curl_creds_unlink(&conn->creds);
Curl_peer_unlink(&conn->creds_origin);
curlx_safefree(conn->options);
curlx_safefree(conn->localdev);
Curl_ssl_conn_config_cleanup(conn);
@ -1011,18 +1012,11 @@ static bool url_match_auth_ntlm(struct connectdata *conn,
possible. (Especially we must not reuse the same connection if
partway through a handshake!) */
if(m->want_ntlm_http) {
if(!Curl_creds_same(m->data->state.creds, conn->creds)) {
/* we prefer a credential match, but this is at least a connection
that can be reused and "upgraded" to NTLM if it does
not have any auth ongoing. */
#ifdef USE_SPNEGO
if((conn->http_ntlm_state == NTLMSTATE_NONE) &&
(conn->http_negotiate_state == GSS_AUTHNONE)) {
#else
if(conn->http_ntlm_state == NTLMSTATE_NONE) {
#endif
m->found = conn;
}
if(conn->creds &&
(!Curl_creds_same(conn->creds, m->data->state.creds) ||
!Curl_peer_equal(conn->creds_origin, m->data->state.origin))) {
/* connection credentials in play and not the same or not for the
* same origin. */
return FALSE;
}
}
@ -1079,7 +1073,9 @@ static bool url_match_auth_nego(struct connectdata *conn,
already authenticating with the right credentials. If not, keep looking
so that we can reuse Negotiate connections if possible. */
if(m->want_nego_http) {
if(!Curl_creds_same(m->needle->creds, conn->creds))
if(conn->creds &&
(!Curl_creds_same(conn->creds, m->data->state.creds) ||
!Curl_peer_equal(conn->creds_origin, m->data->state.origin)))
return FALSE;
}
else if(conn->http_negotiate_state != GSS_AUTHNONE) {
@ -1816,6 +1812,7 @@ static CURLcode url_set_conn_login(struct Curl_easy *data,
{
/* If our protocol needs a password and we have none, use the defaults */
if((conn->scheme->flags & PROTOPT_NEEDSPWD) && !conn->creds) {
Curl_peer_link(&conn->creds_origin, data->state.origin);
if(data->state.creds)
Curl_creds_link(&conn->creds, data->state.creds);
else
@ -1825,6 +1822,7 @@ static CURLcode url_set_conn_login(struct Curl_easy *data,
else if(!(conn->scheme->flags & PROTOPT_CREDSPERREQUEST)) {
/* for protocols that do not handle credentials per request,
* the connection credentials are set by the initial transfer. */
Curl_peer_link(&conn->creds_origin, data->state.origin);
Curl_creds_link(&conn->creds, data->state.creds);
}

View file

@ -301,6 +301,7 @@ struct connectdata {
struct proxy_info http_proxy;
#endif
struct Curl_creds *creds; /* When connection itself is tied to credentials */
struct Curl_peer *creds_origin; /* origin tied credentials are for */
char *options; /* options string, allocated */
struct curltime created; /* creation time */
struct curltime lastused; /* when returned to the connection pool as idle */