strcase: add and use Curl_timestrcmp

This is a strcmp() alternative function for comparing "secrets",
designed to take the same time no matter the content to not leak
match/non-match info to observers based on how fast it is.

The time this function takes is only a function of the shortest input
string.

Reported-by: Trail of Bits

Closes #9658
This commit is contained in:
Daniel Stenberg 2022-10-06 00:49:10 +02:00
Родитель b90f857fab
Коммит ed5095ed94
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 5CC908FDB71E12C2
6 изменённых файлов: 43 добавлений и 28 удалений

Просмотреть файл

@ -198,9 +198,9 @@ static int parsenetrc(const char *host,
/* we are now parsing sub-keywords concerning "our" host */ /* we are now parsing sub-keywords concerning "our" host */
if(state_login) { if(state_login) {
if(specific_login) { if(specific_login) {
state_our_login = !strcmp(login, tok); state_our_login = !Curl_timestrcmp(login, tok);
} }
else if(!login || strcmp(login, tok)) { else if(!login || Curl_timestrcmp(login, tok)) {
if(login_alloc) { if(login_alloc) {
free(login); free(login);
login_alloc = FALSE; login_alloc = FALSE;
@ -216,7 +216,7 @@ static int parsenetrc(const char *host,
} }
else if(state_password) { else if(state_password) {
if((state_our_login || !specific_login) if((state_our_login || !specific_login)
&& (!password || strcmp(password, tok))) { && (!password || Curl_timestrcmp(password, tok))) {
if(password_alloc) { if(password_alloc) {
free(password); free(password);
password_alloc = FALSE; password_alloc = FALSE;

Просмотреть файл

@ -177,6 +177,28 @@ bool Curl_safecmp(char *a, char *b)
return !a && !b; return !a && !b;
} }
/*
* Curl_timestrcmp() returns 0 if the two strings are identical. The time this
* function spends is a function of the shortest string, not of the contents.
*/
int Curl_timestrcmp(const char *a, const char *b)
{
int match = 0;
int i = 0;
if(a && b) {
while(1) {
match |= a[i]^b[i];
if(!a[i] || !b[i])
break;
i++;
}
}
else
return a || b;
return match;
}
/* --- public functions --- */ /* --- public functions --- */
int curl_strequal(const char *first, const char *second) int curl_strequal(const char *first, const char *second)

Просмотреть файл

@ -53,5 +53,6 @@ void Curl_strntoupper(char *dest, const char *src, size_t n);
void Curl_strntolower(char *dest, const char *src, size_t n); void Curl_strntolower(char *dest, const char *src, size_t n);
bool Curl_safecmp(char *a, char *b); bool Curl_safecmp(char *a, char *b);
int Curl_timestrcmp(const char *first, const char *second);
#endif /* HEADER_CURL_STRCASE_H */ #endif /* HEADER_CURL_STRCASE_H */

Просмотреть файл

@ -957,21 +957,11 @@ socks_proxy_info_matches(const struct proxy_info *data,
/* the user information is case-sensitive /* the user information is case-sensitive
or at least it is not defined as case-insensitive or at least it is not defined as case-insensitive
see https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.1 */ see https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.1 */
if(!data->user != !needle->user)
return FALSE;
/* curl_strequal does a case insensitive comparison, /* curl_strequal does a case insensitive comparison,
so do not use it here! */ so do not use it here! */
if(data->user && if(Curl_timestrcmp(data->user, needle->user) ||
needle->user && Curl_timestrcmp(data->passwd, needle->passwd))
strcmp(data->user, needle->user) != 0)
return FALSE;
if(!data->passwd != !needle->passwd)
return FALSE;
/* curl_strequal does a case insensitive comparison,
so do not use it here! */
if(data->passwd &&
needle->passwd &&
strcmp(data->passwd, needle->passwd) != 0)
return FALSE; return FALSE;
return TRUE; return TRUE;
} }
@ -1373,10 +1363,10 @@ ConnectionExists(struct Curl_easy *data,
if(!(needle->handler->flags & PROTOPT_CREDSPERREQUEST)) { if(!(needle->handler->flags & PROTOPT_CREDSPERREQUEST)) {
/* This protocol requires credentials per connection, /* This protocol requires credentials per connection,
so verify that we're using the same name and password as well */ so verify that we're using the same name and password as well */
if(strcmp(needle->user, check->user) || if(Curl_timestrcmp(needle->user, check->user) ||
strcmp(needle->passwd, check->passwd) || Curl_timestrcmp(needle->passwd, check->passwd) ||
!Curl_safecmp(needle->sasl_authzid, check->sasl_authzid) || Curl_timestrcmp(needle->sasl_authzid, check->sasl_authzid) ||
!Curl_safecmp(needle->oauth_bearer, check->oauth_bearer)) { Curl_timestrcmp(needle->oauth_bearer, check->oauth_bearer)) {
/* one of them was different */ /* one of them was different */
continue; continue;
} }
@ -1452,8 +1442,8 @@ ConnectionExists(struct Curl_easy *data,
possible. (Especially we must not reuse the same connection if possible. (Especially we must not reuse the same connection if
partway through a handshake!) */ partway through a handshake!) */
if(wantNTLMhttp) { if(wantNTLMhttp) {
if(strcmp(needle->user, check->user) || if(Curl_timestrcmp(needle->user, check->user) ||
strcmp(needle->passwd, check->passwd)) { Curl_timestrcmp(needle->passwd, check->passwd)) {
/* we prefer a credential match, but this is at least a connection /* we prefer a credential match, but this is at least a connection
that can be reused and "upgraded" to NTLM */ that can be reused and "upgraded" to NTLM */
@ -1475,8 +1465,10 @@ ConnectionExists(struct Curl_easy *data,
if(!check->http_proxy.user || !check->http_proxy.passwd) if(!check->http_proxy.user || !check->http_proxy.passwd)
continue; continue;
if(strcmp(needle->http_proxy.user, check->http_proxy.user) || if(Curl_timestrcmp(needle->http_proxy.user,
strcmp(needle->http_proxy.passwd, check->http_proxy.passwd)) check->http_proxy.user) ||
Curl_timestrcmp(needle->http_proxy.passwd,
check->http_proxy.passwd))
continue; continue;
} }
else if(check->proxy_ntlm_state != NTLMSTATE_NONE) { else if(check->proxy_ntlm_state != NTLMSTATE_NONE) {

Просмотреть файл

@ -431,8 +431,8 @@ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data,
has changed then delete that context. */ has changed then delete that context. */
if((userp && !digest->user) || (!userp && digest->user) || if((userp && !digest->user) || (!userp && digest->user) ||
(passwdp && !digest->passwd) || (!passwdp && digest->passwd) || (passwdp && !digest->passwd) || (!passwdp && digest->passwd) ||
(userp && digest->user && strcmp(userp, digest->user)) || (userp && digest->user && Curl_timestrcmp(userp, digest->user)) ||
(passwdp && digest->passwd && strcmp(passwdp, digest->passwd))) { (passwdp && digest->passwd && Curl_timestrcmp(passwdp, digest->passwd))) {
if(digest->http_context) { if(digest->http_context) {
s_pSecFn->DeleteSecurityContext(digest->http_context); s_pSecFn->DeleteSecurityContext(digest->http_context);
Curl_safefree(digest->http_context); Curl_safefree(digest->http_context);

Просмотреть файл

@ -146,8 +146,8 @@ Curl_ssl_config_matches(struct ssl_primary_config *data,
Curl_safecmp(data->issuercert, needle->issuercert) && Curl_safecmp(data->issuercert, needle->issuercert) &&
Curl_safecmp(data->clientcert, needle->clientcert) && Curl_safecmp(data->clientcert, needle->clientcert) &&
#ifdef USE_TLS_SRP #ifdef USE_TLS_SRP
Curl_safecmp(data->username, needle->username) && !Curl_timestrcmp(data->username, needle->username) &&
Curl_safecmp(data->password, needle->password) && !Curl_timestrcmp(data->password, needle->password) &&
(data->authtype == needle->authtype) && (data->authtype == needle->authtype) &&
#endif #endif
Curl_safe_strcasecompare(data->cipher_list, needle->cipher_list) && Curl_safe_strcasecompare(data->cipher_list, needle->cipher_list) &&