urlapi: detect and error on illegal IPv4 addresses

Using bad numbers in an IPv4 numerical address now returns
CURLUE_BAD_HOSTNAME.

I noticed while working on trurl and it was originally reported here:
https://github.com/curl/trurl/issues/78

Updated test 1560 accordingly.

Closes #10894
This commit is contained in:
Daniel Stenberg 2023-04-05 23:13:33 +02:00
parent 98fac31b06
commit 17a15d8846
No known key found for this signature in database
GPG key ID: 5CC908FDB71E12C2
2 changed files with 48 additions and 34 deletions

View file

@ -649,7 +649,12 @@ static CURLUcode hostname_check(struct Curl_URL *u, char *hostname,
* Output the "normalized" version of that input string in plain quad decimal * Output the "normalized" version of that input string in plain quad decimal
* integers and return TRUE. * integers and return TRUE.
*/ */
static bool ipv4_normalize(const char *hostname, char *outp, size_t olen)
#define IPV4_NOTANIP 1
#define IPV4_BAD 2
#define IPV4_CLEANED 3
static int ipv4_normalize(const char *hostname, char *outp, size_t olen)
{ {
bool done = FALSE; bool done = FALSE;
int n = 0; int n = 0;
@ -659,28 +664,18 @@ static bool ipv4_normalize(const char *hostname, char *outp, size_t olen)
while(!done) { while(!done) {
char *endp; char *endp;
unsigned long l; unsigned long l;
if((*c < '0') || (*c > '9')) if(!ISDIGIT(*c))
/* most importantly this doesn't allow a leading plus or minus */ /* most importantly this doesn't allow a leading plus or minus */
return FALSE; return n ? IPV4_BAD :IPV4_NOTANIP;
l = strtoul(c, &endp, 0); l = strtoul(c, &endp, 0);
/* overflow or nothing parsed at all */
if(((l == ULONG_MAX) && (errno == ERANGE)) || (endp == c))
return FALSE;
#if SIZEOF_LONG > 4
/* a value larger than 32 bits */
if(l > UINT_MAX)
return FALSE;
#endif
parts[n] = l; parts[n] = l;
c = endp; c = endp;
switch (*c) { switch (*c) {
case '.' : case '.' :
if(n == 3) if(n == 3)
return FALSE; return IPV4_BAD;
n++; n++;
c++; c++;
break; break;
@ -690,8 +685,18 @@ static bool ipv4_normalize(const char *hostname, char *outp, size_t olen)
break; break;
default: default:
return FALSE; return n ? IPV4_BAD : IPV4_NOTANIP;
} }
/* overflow */
if((l == ULONG_MAX) && (errno == ERANGE))
return IPV4_BAD;
#if SIZEOF_LONG > 4
/* a value larger than 32 bits */
if(l > UINT_MAX)
return IPV4_BAD;
#endif
} }
/* this is deemed a valid IPv4 numerical address */ /* this is deemed a valid IPv4 numerical address */
@ -704,14 +709,14 @@ static bool ipv4_normalize(const char *hostname, char *outp, size_t olen)
break; break;
case 1: /* a.b -- 8.24 bits */ case 1: /* a.b -- 8.24 bits */
if((parts[0] > 0xff) || (parts[1] > 0xffffff)) if((parts[0] > 0xff) || (parts[1] > 0xffffff))
return FALSE; return IPV4_BAD;
msnprintf(outp, olen, "%u.%u.%u.%u", msnprintf(outp, olen, "%u.%u.%u.%u",
parts[0], (parts[1] >> 16) & 0xff, parts[0], (parts[1] >> 16) & 0xff,
(parts[1] >> 8) & 0xff, parts[1] & 0xff); (parts[1] >> 8) & 0xff, parts[1] & 0xff);
break; break;
case 2: /* a.b.c -- 8.8.16 bits */ case 2: /* a.b.c -- 8.8.16 bits */
if((parts[0] > 0xff) || (parts[1] > 0xff) || (parts[2] > 0xffff)) if((parts[0] > 0xff) || (parts[1] > 0xff) || (parts[2] > 0xffff))
return FALSE; return IPV4_BAD;
msnprintf(outp, olen, "%u.%u.%u.%u", msnprintf(outp, olen, "%u.%u.%u.%u",
parts[0], parts[1], (parts[2] >> 8) & 0xff, parts[0], parts[1], (parts[2] >> 8) & 0xff,
parts[2] & 0xff); parts[2] & 0xff);
@ -719,12 +724,12 @@ static bool ipv4_normalize(const char *hostname, char *outp, size_t olen)
case 3: /* a.b.c.d -- 8.8.8.8 bits */ case 3: /* a.b.c.d -- 8.8.8.8 bits */
if((parts[0] > 0xff) || (parts[1] > 0xff) || (parts[2] > 0xff) || if((parts[0] > 0xff) || (parts[1] > 0xff) || (parts[2] > 0xff) ||
(parts[3] > 0xff)) (parts[3] > 0xff))
return FALSE; return IPV4_BAD;
msnprintf(outp, olen, "%u.%u.%u.%u", msnprintf(outp, olen, "%u.%u.%u.%u",
parts[0], parts[1], parts[2], parts[3]); parts[0], parts[1], parts[2], parts[3]);
break; break;
} }
return TRUE; return IPV4_CLEANED;
} }
/* if necessary, replace the host content with a URL decoded version */ /* if necessary, replace the host content with a URL decoded version */
@ -1247,6 +1252,7 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags)
if(Curl_dyn_len(&host)) { if(Curl_dyn_len(&host)) {
char normalized_ipv4[sizeof("255.255.255.255") + 1]; char normalized_ipv4[sizeof("255.255.255.255") + 1];
int norm;
/* /*
* Parse the login details and strip them out of the host name. * Parse the login details and strip them out of the host name.
@ -1262,21 +1268,28 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags)
goto fail; goto fail;
} }
if(ipv4_normalize(Curl_dyn_ptr(&host), norm = ipv4_normalize(Curl_dyn_ptr(&host),
normalized_ipv4, sizeof(normalized_ipv4))) { normalized_ipv4, sizeof(normalized_ipv4));
switch(norm) {
case IPV4_CLEANED:
Curl_dyn_reset(&host); Curl_dyn_reset(&host);
if(Curl_dyn_add(&host, normalized_ipv4)) { if(Curl_dyn_add(&host, normalized_ipv4))
result = CURLUE_OUT_OF_MEMORY; result = CURLUE_OUT_OF_MEMORY;
goto fail; break;
}
} case IPV4_NOTANIP:
else {
result = decode_host(&host); result = decode_host(&host);
if(!result) if(!result)
result = hostname_check(u, Curl_dyn_ptr(&host), Curl_dyn_len(&host)); result = hostname_check(u, Curl_dyn_ptr(&host), Curl_dyn_len(&host));
if(result) break;
goto fail;
case IPV4_BAD:
default:
result = CURLUE_BAD_HOSTNAME; /* Bad IPv4 address even */
break;
} }
if(result)
goto fail;
if((flags & CURLU_GUESS_SCHEME) && !schemep) { if((flags & CURLU_GUESS_SCHEME) && !schemep) {
const char *hostname = Curl_dyn_ptr(&host); const char *hostname = Curl_dyn_ptr(&host);

View file

@ -531,13 +531,14 @@ static const struct urltestcase get_url_list[] = {
{"https://a127.0.0.1", "https://a127.0.0.1/", 0, 0, CURLUE_OK}, {"https://a127.0.0.1", "https://a127.0.0.1/", 0, 0, CURLUE_OK},
{"https://\xff.127.0.0.1", "https://%FF.127.0.0.1/", 0, CURLU_URLENCODE, {"https://\xff.127.0.0.1", "https://%FF.127.0.0.1/", 0, CURLU_URLENCODE,
CURLUE_OK}, CURLUE_OK},
{"https://127.-0.0.1", "https://127.-0.0.1/", 0, 0, CURLUE_OK}, {"https://127.-0.0.1", "https://127.-0.0.1/", 0, 0, CURLUE_BAD_HOSTNAME},
{"https://127.0. 1", "https://127.0.0.1/", 0, 0, CURLUE_BAD_HOSTNAME}, {"https://127.0. 1", "https://127.0.0.1/", 0, 0, CURLUE_BAD_HOSTNAME},
{"https://1.0x1000000", "https://1.0x1000000/", 0, 0, CURLUE_OK}, {"https://1.0x1000000", "https://1.0x1000000/", 0, 0, CURLUE_BAD_HOSTNAME},
{"https://1.2.3.256", "https://1.2.3.256/", 0, 0, CURLUE_OK}, {"https://1.2.3.256", "https://1.2.3.256/", 0, 0, CURLUE_BAD_HOSTNAME},
{"https://1.2.3.4.5", "https://1.2.3.4.5/", 0, 0, CURLUE_OK}, {"https://1.2.3.4.5", "https://1.2.3.4.5/", 0, 0, CURLUE_BAD_HOSTNAME},
{"https://1.2.0x100.3", "https://1.2.0x100.3/", 0, 0, CURLUE_OK}, {"https://1.2.0x100.3", "https://1.2.0x100.3/", 0, 0, CURLUE_BAD_HOSTNAME},
{"https://4294967296", "https://4294967296/", 0, 0, CURLUE_OK}, {"https://4294967296", "https://4294967296/", 0, 0, CURLUE_BAD_HOSTNAME},
{"https://123host", "https://123host/", 0, 0, CURLUE_OK},
/* 40 bytes scheme is the max allowed */ /* 40 bytes scheme is the max allowed */
{"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA://hostname/path", {"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA://hostname/path",
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa://hostname/path", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa://hostname/path",