From df64bf30d87c79c274856413ede507a4b3cc8b68 Mon Sep 17 00:00:00 2001 From: sherlock2010 <15151851377@163.com> Date: Tue, 7 Jan 2025 08:12:07 +0000 Subject: [PATCH] fix CVE-2024-11053 (cherry picked from commit 5f58cdffa3f30fef73fa8c6226a34b0032aa60dc) --- backport-CVE-2024-11053-post1.patch | 37 ++ backport-CVE-2024-11053-post2.patch | 129 +++++ backport-CVE-2024-11053-pre1.patch | 453 ++++++++++++++++ backport-CVE-2024-11053-pre2.patch | 800 ++++++++++++++++++++++++++++ backport-CVE-2024-11053-pre3.patch | 349 ++++++++++++ backport-CVE-2024-11053-pre4.patch | 223 ++++++++ backport-CVE-2024-11053-pre5.patch | 37 ++ backport-CVE-2024-11053.patch | 728 +++++++++++++++++++++++++ curl.spec | 16 +- 9 files changed, 2771 insertions(+), 1 deletion(-) create mode 100644 backport-CVE-2024-11053-post1.patch create mode 100644 backport-CVE-2024-11053-post2.patch create mode 100644 backport-CVE-2024-11053-pre1.patch create mode 100644 backport-CVE-2024-11053-pre2.patch create mode 100644 backport-CVE-2024-11053-pre3.patch create mode 100644 backport-CVE-2024-11053-pre4.patch create mode 100644 backport-CVE-2024-11053-pre5.patch create mode 100644 backport-CVE-2024-11053.patch diff --git a/backport-CVE-2024-11053-post1.patch b/backport-CVE-2024-11053-post1.patch new file mode 100644 index 0000000..1d69ba5 --- /dev/null +++ b/backport-CVE-2024-11053-post1.patch @@ -0,0 +1,37 @@ +From 4b07b7ebadfbff1d26622719b9048673a78f0bf0 Mon Sep 17 00:00:00 2001 +From: Viktor Szakats +Date: Sun, 17 Nov 2024 12:46:25 +0100 +Subject: [PATCH] netrc: fix pointer to bool conversion + +with MSVC 2008 and 2010: +``` +lib/netrc.c(107): error C2440: 'initializing' : cannot convert from 'char *' to 'bool' +``` +Ref: https://ci.appveyor.com/project/curlorg/curl/builds/51002792/job/jtoxd4mk984oi6fd#L164 +Ref: https://ci.appveyor.com/project/curlorg/curl/builds/51002792/job/0wxlw9a8g04e56vt#L177 + +Follow-up to e9b9bbac22c26cf67316fa8e6c6b9e831af31949 #15586 +Closes #15601 + +Conflict:NA +Reference:https://github.com/curl/curl/commit/4b07b7ebadfbff1d26622719b9048673a78f0bf0 +--- + lib/netrc.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/lib/netrc.c b/lib/netrc.c +index e787a6ffc..d5ee3c0fd 100644 +--- a/lib/netrc.c ++++ b/lib/netrc.c +@@ -104,7 +104,7 @@ static int parsenetrc(struct store_netrc *store, + int retcode = NETRC_FILE_MISSING; + char *login = *loginp; + char *password = NULL; +- bool specific_login = login; /* points to something */ ++ bool specific_login = !!login; /* points to something */ + enum host_lookup_state state = NOTHING; + enum found_state keyword = NONE; + unsigned char found = 0; /* login + password found bits, as they can come in +-- +2.33.0 + diff --git a/backport-CVE-2024-11053-post2.patch b/backport-CVE-2024-11053-post2.patch new file mode 100644 index 0000000..2b9fd4e --- /dev/null +++ b/backport-CVE-2024-11053-post2.patch @@ -0,0 +1,129 @@ +From 9fce2c55d4b0273ac99b59bd8cb982a6d96b88cf Mon Sep 17 00:00:00 2001 +From: Daniel Stenberg +Date: Tue, 17 Dec 2024 23:56:42 +0100 +Subject: [PATCH] netrc: fix password-only entries + +When a specific hostname matched, and only a password is set before +another machine is specified in the netrc file, the parser would not be +happy and stop there and return the password-only state. It instead +continued and did not return a match. + +Add test 2005 to verify this case + +Regression from e9b9bba, shipped in 8.11.1. + +Reported-by: Ben Zanin +Fixes #15767 +Closes #15768 + +Conflict:context adapt +Reference:https://github.com/curl/curl/commit/9fce2c55d4b0273ac99b59bd8cb982a6d96b88cf +--- + lib/netrc.c | 7 +++++- + tests/data/Makefile.inc | 2 +- + tests/data/test2005 | 55 ++++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 62 insertions(+), 2 deletions(-) + create mode 100644 tests/data/test2005 + +diff --git a/lib/netrc.c b/lib/netrc.c +index cbc86484f..b517c1dfa 100644 +--- a/lib/netrc.c ++++ b/lib/netrc.c +@@ -267,7 +267,8 @@ static int parsenetrc(struct store_netrc *store, + retcode = NETRC_FAILED; /* allocation failed */ + goto out; + } +- found |= FOUND_PASSWORD; ++ if(!specific_login || our_login) ++ found |= FOUND_PASSWORD; + keyword = NONE; + } + else if(strcasecompare("login", tok)) +@@ -276,6 +277,10 @@ static int parsenetrc(struct store_netrc *store, + keyword = PASSWORD; + else if(strcasecompare("machine", tok)) { + /* a new machine here */ ++ if(found & FOUND_PASSWORD) { ++ done = TRUE; ++ break; ++ } + state = HOSTFOUND; + keyword = NONE; + found = 0; +diff --git a/tests/data/Makefile.inc b/tests/data/Makefile.inc +index bd9a0bbaa..105108309 100644 +--- a/tests/data/Makefile.inc ++++ b/tests/data/Makefile.inc +@@ -238,7 +238,7 @@ test1941 test1942 test1943 test1944 test1945 test1946 test1947 test1948 \ + test1955 test1956 test1957 test1958 test1959 test1960 test1964 \ + test1970 test1971 test1972 test1973 test1974 test1975 \ + \ +-test2000 test2001 test2002 test2003 test2004 \ ++test2000 test2001 test2002 test2003 test2004 test2005 \ + \ + test2023 \ + test2024 test2025 test2026 test2027 test2028 test2029 test2030 test2031 \ +diff --git a/tests/data/test2005 b/tests/data/test2005 +new file mode 100644 +index 000000000..91e256298 +--- /dev/null ++++ b/tests/data/test2005 +@@ -0,0 +1,55 @@ ++ ++ ++ ++HTTP ++netrc ++ ++ ++# ++# Server-side ++ ++ ++HTTP/1.1 200 OK ++Date: Fri, 05 Aug 2022 10:09:00 GMT ++Server: test-server/fake ++Content-Type: text/plain ++Content-Length: 6 ++Connection: close ++ ++-foo- ++ ++ ++ ++# ++# Client-side ++ ++ ++http ++ ++ ++netrc match with password only in file, no username. machine follows ++ ++ ++--netrc-optional --netrc-file %LOGDIR/netrc%TESTNUMBER http://%HOSTIP:%HTTPPORT/ ++ ++ ++machine %HOSTIP ++password 5up3r53cr37 ++ ++machine example.com ++ ++ ++ ++# ++# Verify data after the test has been "shot" ++ ++ ++GET / HTTP/1.1 ++Host: %HOSTIP:%HTTPPORT ++Authorization: Basic %b64[:5up3r53cr37]b64% ++User-Agent: curl/%VERSION ++Accept: */* ++ ++ ++ ++ +-- +2.33.0 + diff --git a/backport-CVE-2024-11053-pre1.patch b/backport-CVE-2024-11053-pre1.patch new file mode 100644 index 0000000..1a55294 --- /dev/null +++ b/backport-CVE-2024-11053-pre1.patch @@ -0,0 +1,453 @@ +From 142ac257b3242459b284020c59f1902b9687a954 Mon Sep 17 00:00:00 2001 +From: Daniel Stenberg +Date: Tue, 6 Feb 2024 10:15:52 +0100 +Subject: [PATCH] lib: convert Curl_get_line to use dynbuf + +Create the line in a dynbuf. Aborts the reading of the file on +errors. Avoids having to always allocate maximum amount from the +start. Avoids direct malloc. + +Closes #12846 + +Conflict:context adapt +Reference:https://github.com/curl/curl/commit/142ac257b3242459b284020c59f1902b9687a954 +--- + lib/altsvc.c | 18 +++------- + lib/cookie.c | 29 ++++------------ + lib/curl_get_line.c | 55 +++++++++++++---------------- + lib/curl_get_line.h | 7 ++-- + lib/hsts.c | 17 +++------ + lib/netrc.c | 10 ++++-- + tests/unit/unit3200.c | 80 ++++++++++++++++++++++++------------------- + 7 files changed, 96 insertions(+), 120 deletions(-) + +diff --git a/lib/altsvc.c b/lib/altsvc.c +index e9f62bf0e..c12d7bda1 100644 +--- a/lib/altsvc.c ++++ b/lib/altsvc.c +@@ -209,7 +209,6 @@ static CURLcode altsvc_add(struct altsvcinfo *asi, char *line) + static CURLcode altsvc_load(struct altsvcinfo *asi, const char *file) + { + CURLcode result = CURLE_OK; +- char *line = NULL; + FILE *fp; + + /* we need a private copy of the file name so that the altsvc cache file +@@ -221,11 +220,10 @@ static CURLcode altsvc_load(struct altsvcinfo *asi, const char *file) + + fp = fopen(file, FOPEN_READTEXT); + if(fp) { +- line = malloc(MAX_ALTSVC_LINE); +- if(!line) +- goto fail; +- while(Curl_get_line(line, MAX_ALTSVC_LINE, fp)) { +- char *lineptr = line; ++ struct dynbuf buf; ++ Curl_dyn_init(&buf, MAX_ALTSVC_LINE); ++ while(Curl_get_line(&buf, fp)) { ++ char *lineptr = Curl_dyn_ptr(&buf); + while(*lineptr && ISBLANK(*lineptr)) + lineptr++; + if(*lineptr == '#') +@@ -234,16 +232,10 @@ static CURLcode altsvc_load(struct altsvcinfo *asi, const char *file) + + altsvc_add(asi, lineptr); + } +- free(line); /* free the line buffer */ ++ Curl_dyn_free(&buf); /* free the line buffer */ + fclose(fp); + } + return result; +- +-fail: +- Curl_safefree(asi->filename); +- free(line); +- fclose(fp); +- return CURLE_OUT_OF_MEMORY; + } + + /* +diff --git a/lib/cookie.c b/lib/cookie.c +index dc319b611..d10dd572b 100644 +--- a/lib/cookie.c ++++ b/lib/cookie.c +@@ -1205,7 +1205,6 @@ struct CookieInfo *Curl_cookie_init(struct Curl_easy *data, + bool newsession) + { + struct CookieInfo *c; +- char *line = NULL; + FILE *handle = NULL; + + if(!inc) { +@@ -1241,16 +1240,14 @@ struct CookieInfo *Curl_cookie_init(struct Curl_easy *data, + + c->running = FALSE; /* this is not running, this is init */ + if(fp) { +- +- line = malloc(MAX_COOKIE_LINE); +- if(!line) +- goto fail; +- while(Curl_get_line(line, MAX_COOKIE_LINE, fp)) { +- char *lineptr = line; ++ struct dynbuf buf; ++ Curl_dyn_init(&buf, MAX_COOKIE_LINE); ++ while(Curl_get_line(&buf, fp)) { ++ char *lineptr = Curl_dyn_ptr(&buf); + bool headerline = FALSE; +- if(checkprefix("Set-Cookie:", line)) { ++ if(checkprefix("Set-Cookie:", lineptr)) { + /* This is a cookie line, get it! */ +- lineptr = &line[11]; ++ lineptr += 11; + headerline = TRUE; + while(*lineptr && ISBLANK(*lineptr)) + lineptr++; +@@ -1258,7 +1255,7 @@ struct CookieInfo *Curl_cookie_init(struct Curl_easy *data, + + Curl_cookie_add(data, c, headerline, TRUE, lineptr, NULL, NULL, TRUE); + } +- free(line); /* free the line buffer */ ++ Curl_dyn_free(&buf); /* free the line buffer */ + + /* + * Remove expired cookies from the hash. We must make sure to run this +@@ -1274,18 +1271,6 @@ struct CookieInfo *Curl_cookie_init(struct Curl_easy *data, + c->running = TRUE; /* now, we're running */ + + return c; +- +-fail: +- free(line); +- /* +- * Only clean up if we allocated it here, as the original could still be in +- * use by a share handle. +- */ +- if(!inc) +- Curl_cookie_cleanup(c); +- if(handle) +- fclose(handle); +- return NULL; /* out of memory */ + } + + /* +diff --git a/lib/curl_get_line.c b/lib/curl_get_line.c +index 686abe751..100207331 100644 +--- a/lib/curl_get_line.c ++++ b/lib/curl_get_line.c +@@ -33,14 +33,16 @@ + #include "memdebug.h" + + /* +- * Curl_get_line() makes sure to only return complete whole lines that fit in +- * 'len' bytes and end with a newline. ++ * Curl_get_line() makes sure to only return complete whole lines that end ++ * newlines. + */ +-char *Curl_get_line(char *buf, int len, FILE *input) ++int Curl_get_line(struct dynbuf *buf, FILE *input) + { +- bool partial = FALSE; ++ CURLcode result; ++ char buffer[128]; ++ Curl_dyn_reset(buf); + while(1) { +- char *b = fgets(buf, len, input); ++ char *b = fgets(buffer, sizeof(buffer), input); + + if(b) { + size_t rlen = strlen(b); +@@ -48,39 +50,28 @@ char *Curl_get_line(char *buf, int len, FILE *input) + if(!rlen) + break; + +- if(b[rlen-1] == '\n') { +- /* b is \n terminated */ +- if(partial) { +- partial = FALSE; +- continue; +- } +- return b; +- } +- else if(feof(input)) { +- if(partial) +- /* Line is already too large to return, ignore rest */ +- break; ++ result = Curl_dyn_addn(buf, b, rlen); ++ if(result) ++ /* too long line or out of memory */ ++ return 0; /* error */ + +- if(rlen + 1 < (size_t) len) { +- /* b is EOF terminated, insert missing \n */ +- b[rlen] = '\n'; +- b[rlen + 1] = '\0'; +- return b; +- } +- else +- /* Maximum buffersize reached + EOF +- * This line is impossible to add a \n to so we'll ignore it +- */ +- break; ++ else if(b[rlen-1] == '\n') ++ /* end of the line */ ++ return 1; /* all good */ ++ ++ else if(feof(input)) { ++ /* append a newline */ ++ result = Curl_dyn_addn(buf, "\n", 1); ++ if(result) ++ /* too long line or out of memory */ ++ return 0; /* error */ ++ return 1; /* all good */ + } +- else +- /* Maximum buffersize reached */ +- partial = TRUE; + } + else + break; + } +- return NULL; ++ return 0; + } + + #endif /* if not disabled */ +diff --git a/lib/curl_get_line.h b/lib/curl_get_line.h +index 0ff32c5c2..7907cde88 100644 +--- a/lib/curl_get_line.h ++++ b/lib/curl_get_line.h +@@ -24,8 +24,9 @@ + * + ***************************************************************************/ + +-/* get_line() makes sure to only return complete whole lines that fit in 'len' +- * bytes and end with a newline. */ +-char *Curl_get_line(char *buf, int len, FILE *input); ++#include "dynbuf.h" ++ ++/* Curl_get_line() returns complete lines that end with a newline. */ ++int Curl_get_line(struct dynbuf *buf, FILE *input); + + #endif /* HEADER_CURL_GET_LINE_H */ +diff --git a/lib/hsts.c b/lib/hsts.c +index 8725a35c1..607755e6b 100644 +--- a/lib/hsts.c ++++ b/lib/hsts.c +@@ -511,7 +511,6 @@ static CURLcode hsts_pull(struct Curl_easy *data, struct hsts *h) + static CURLcode hsts_load(struct hsts *h, const char *file) + { + CURLcode result = CURLE_OK; +- char *line = NULL; + FILE *fp; + + /* we need a private copy of the file name so that the hsts cache file +@@ -523,11 +522,10 @@ static CURLcode hsts_load(struct hsts *h, const char *file) + + fp = fopen(file, FOPEN_READTEXT); + if(fp) { +- line = malloc(MAX_HSTS_LINE); +- if(!line) +- goto fail; +- while(Curl_get_line(line, MAX_HSTS_LINE, fp)) { +- char *lineptr = line; ++ struct dynbuf buf; ++ Curl_dyn_init(&buf, MAX_HSTS_LINE); ++ while(Curl_get_line(&buf, fp)) { ++ char *lineptr = Curl_dyn_ptr(&buf); + while(*lineptr && ISBLANK(*lineptr)) + lineptr++; + if(*lineptr == '#') +@@ -536,15 +534,10 @@ static CURLcode hsts_load(struct hsts *h, const char *file) + + hsts_add(h, lineptr); + } +- free(line); /* free the line buffer */ ++ Curl_dyn_free(&buf); /* free the line buffer */ + fclose(fp); + } + return result; +- +-fail: +- Curl_safefree(h->filename); +- fclose(fp); +- return CURLE_OUT_OF_MEMORY; + } + + /* +diff --git a/lib/netrc.c b/lib/netrc.c +index 038c6dca6..cd2a2844e 100644 +--- a/lib/netrc.c ++++ b/lib/netrc.c +@@ -53,6 +53,8 @@ enum host_lookup_state { + #define NETRC_FAILED -1 + #define NETRC_SUCCESS 0 + ++#define MAX_NETRC_LINE 4096 ++ + /* + * Returns zero on success. + */ +@@ -80,13 +82,14 @@ static int parsenetrc(const char *host, + file = fopen(netrcfile, FOPEN_READTEXT); + if(file) { + bool done = FALSE; +- char netrcbuffer[4096]; +- int netrcbuffsize = (int)sizeof(netrcbuffer); ++ struct dynbuf buf; ++ Curl_dyn_init(&buf, MAX_NETRC_LINE); + +- while(!done && Curl_get_line(netrcbuffer, netrcbuffsize, file)) { ++ while(!done && Curl_get_line(&buf, file)) { + char *tok; + char *tok_end; + bool quoted; ++ char *netrcbuffer = Curl_dyn_ptr(&buf); + if(state == MACDEF) { + if((netrcbuffer[0] == '\n') || (netrcbuffer[0] == '\r')) + state = NOTHING; +@@ -245,6 +248,7 @@ static int parsenetrc(const char *host, + } /* while Curl_get_line() */ + + out: ++ Curl_dyn_free(&buf); + if(!retcode) { + /* success */ + if(login_alloc) { +diff --git a/tests/unit/unit3200.c b/tests/unit/unit3200.c +index 0544bcc93..6f508ce07 100644 +--- a/tests/unit/unit3200.c ++++ b/tests/unit/unit3200.c +@@ -69,7 +69,7 @@ static const char *filecontents[] = { + "LINE1\n" + C4096 "SOME EXTRA TEXT", + +- /* First and third line should be read */ ++ /* Only first should be read */ + "LINE1\n" + C4096 "SOME EXTRA TEXT\n" + "LINE3\n", +@@ -84,11 +84,13 @@ static const char *filecontents[] = { + + UNITTEST_START + size_t i; ++ int rc = 0; + for(i = 0; i < NUMTESTS; i++) { + FILE *fp; +- char buf[4096]; ++ struct dynbuf buf; + int len = 4096; + char *line; ++ Curl_dyn_init(&buf, len); + + fp = fopen(arg, "wb"); + abort_unless(fp != NULL, "Cannot open testfile"); +@@ -101,65 +103,73 @@ UNITTEST_START + fprintf(stderr, "Test %zd...", i); + switch(i) { + case 0: +- line = Curl_get_line(buf, len, fp); ++ rc = Curl_get_line(&buf, fp); ++ line = Curl_dyn_ptr(&buf); + fail_unless(line && !strcmp("LINE1\n", line), +- "First line failed (1)"); +- line = Curl_get_line(buf, len, fp); ++ "First line failed (1)"); ++ rc = Curl_get_line(&buf, fp); ++ line = Curl_dyn_ptr(&buf); + fail_unless(line && !strcmp("LINE2 NEWLINE\n", line), +- "Second line failed (1)"); +- line = Curl_get_line(buf, len, fp); +- abort_unless(line == NULL, "Missed EOF (1)"); ++ "Second line failed (1)"); ++ rc = Curl_get_line(&buf, fp); ++ abort_unless(!Curl_dyn_len(&buf), "Missed EOF (1)"); + break; + case 1: +- line = Curl_get_line(buf, len, fp); ++ rc = Curl_get_line(&buf, fp); ++ line = Curl_dyn_ptr(&buf); + fail_unless(line && !strcmp("LINE1\n", line), +- "First line failed (2)"); +- line = Curl_get_line(buf, len, fp); ++ "First line failed (2)"); ++ rc = Curl_get_line(&buf, fp); ++ line = Curl_dyn_ptr(&buf); + fail_unless(line && !strcmp("LINE2 NONEWLINE\n", line), +- "Second line failed (2)"); +- line = Curl_get_line(buf, len, fp); +- abort_unless(line == NULL, "Missed EOF (2)"); ++ "Second line failed (2)"); ++ rc = Curl_get_line(&buf, fp); ++ abort_unless(!Curl_dyn_len(&buf), "Missed EOF (2)"); + break; + case 2: +- line = Curl_get_line(buf, len, fp); ++ rc = Curl_get_line(&buf, fp); ++ line = Curl_dyn_ptr(&buf); + fail_unless(line && !strcmp("LINE1\n", line), +- "First line failed (3)"); +- line = Curl_get_line(buf, len, fp); +- fail_unless(line == NULL, +- "Did not detect max read on EOF (3)"); ++ "First line failed (3)"); ++ rc = Curl_get_line(&buf, fp); ++ fail_unless(!Curl_dyn_len(&buf), ++ "Did not detect max read on EOF (3)"); + break; + case 3: +- line = Curl_get_line(buf, len, fp); ++ rc = Curl_get_line(&buf, fp); ++ line = Curl_dyn_ptr(&buf); + fail_unless(line && !strcmp("LINE1\n", line), +- "First line failed (4)"); +- line = Curl_get_line(buf, len, fp); +- fail_unless(line == NULL, +- "Did not ignore partial on EOF (4)"); ++ "First line failed (4)"); ++ rc = Curl_get_line(&buf, fp); ++ fail_unless(!Curl_dyn_len(&buf), ++ "Did not ignore partial on EOF (4)"); + break; + case 4: +- line = Curl_get_line(buf, len, fp); ++ rc = Curl_get_line(&buf, fp); ++ line = Curl_dyn_ptr(&buf); + fail_unless(line && !strcmp("LINE1\n", line), +- "First line failed (5)"); +- line = Curl_get_line(buf, len, fp); +- fail_unless(line && !strcmp("LINE3\n", line), +- "Third line failed (5)"); +- line = Curl_get_line(buf, len, fp); +- abort_unless(line == NULL, "Missed EOF (5)"); ++ "First line failed (5)"); ++ rc = Curl_get_line(&buf, fp); ++ fail_unless(!Curl_dyn_len(&buf), ++ "Did not bail out on too long line"); + break; + case 5: +- line = Curl_get_line(buf, len, fp); ++ rc = Curl_get_line(&buf, fp); ++ line = Curl_dyn_ptr(&buf); + fail_unless(line && !strcmp("LINE1\x1aTEST\n", line), +- "Missed/Misinterpreted ^Z (6)"); +- line = Curl_get_line(buf, len, fp); +- abort_unless(line == NULL, "Missed EOF (6)"); ++ "Missed/Misinterpreted ^Z (6)"); ++ rc = Curl_get_line(&buf, fp); ++ abort_unless(!Curl_dyn_len(&buf), "Missed EOF (6)"); + break; + default: + abort_unless(1, "Unknown case"); + break; + } ++ Curl_dyn_free(&buf); + fclose(fp); + fprintf(stderr, "OK\n"); + } ++ return rc; + UNITTEST_STOP + + #else +-- +2.33.0 + diff --git a/backport-CVE-2024-11053-pre2.patch b/backport-CVE-2024-11053-pre2.patch new file mode 100644 index 0000000..eff59cc --- /dev/null +++ b/backport-CVE-2024-11053-pre2.patch @@ -0,0 +1,800 @@ +From 3b43a05e000aa8f65bda513f733a73fefe35d5ca Mon Sep 17 00:00:00 2001 +From: Daniel Stenberg +Date: Thu, 10 Oct 2024 18:08:07 +0200 +Subject: [PATCH] netrc: cache the netrc file in memory + +So that on redirects etc it does not reread the file but just parses it +again. + +Reported-by: Pierre-Etienne Meunier +Fixes #15248 +Closes #15259 + +Conflict:context adapt +Reference:https://github.com/curl/curl/commit/3b43a05e000aa8f65bda513f733a73fefe35d5ca +--- + lib/multi.c | 2 + + lib/netrc.c | 407 ++++++++++++++++++++++++------------------ + lib/netrc.h | 14 +- + lib/url.c | 4 +- + lib/urldata.h | 5 + + tests/unit/unit1304.c | 48 ++++- + 6 files changed, 292 insertions(+), 188 deletions(-) + +diff --git a/lib/multi.c b/lib/multi.c +index 223c2339c..0f9fedaff 100644 +--- a/lib/multi.c ++++ b/lib/multi.c +@@ -757,6 +757,8 @@ static CURLcode multi_done(struct Curl_easy *data, + data->state.lastconnect_id = -1; + } + ++ /* flush the netrc cache */ ++ Curl_netrc_cleanup(&data->state.netrc); + Curl_safefree(data->state.buffer); + return result; + } +diff --git a/lib/netrc.c b/lib/netrc.c +index 3c0651dcc..c23f927ce 100644 +--- a/lib/netrc.c ++++ b/lib/netrc.c +@@ -31,7 +31,6 @@ + + #include + #include "netrc.h" +-#include "strtok.h" + #include "strcase.h" + #include "curl_get_line.h" + +@@ -49,21 +48,56 @@ enum host_lookup_state { + MACDEF + }; + ++enum found_state { ++ NONE, ++ LOGIN, ++ PASSWORD ++}; ++ + #define NETRC_FILE_MISSING 1 + #define NETRC_FAILED -1 + #define NETRC_SUCCESS 0 + + #define MAX_NETRC_LINE 4096 ++#define MAX_NETRC_FILE (64*1024) ++#define MAX_NETRC_TOKEN 128 ++ ++static CURLcode file2memory(const char *filename, struct dynbuf *filebuf) ++{ ++ CURLcode result = CURLE_OK; ++ FILE *file = fopen(filename, FOPEN_READTEXT); ++ struct dynbuf linebuf; ++ Curl_dyn_init(&linebuf, MAX_NETRC_LINE); ++ ++ if(file) { ++ while(Curl_get_line(&linebuf, file)) { ++ const char *line = Curl_dyn_ptr(&linebuf); ++ /* skip comments on load */ ++ while(ISBLANK(*line)) ++ line++; ++ if(*line == '#') ++ continue; ++ result = Curl_dyn_add(filebuf, line); ++ if(result) ++ goto done; ++ } ++ } ++done: ++ Curl_dyn_free(&linebuf); ++ if(file) ++ fclose(file); ++ return result; ++} + + /* + * Returns zero on success. + */ +-static int parsenetrc(const char *host, ++static int parsenetrc(struct store_netrc *store, ++ const char *host, + char **loginp, + char **passwordp, +- char *netrcfile) ++ const char *netrcfile) + { +- FILE *file; + int retcode = NETRC_FILE_MISSING; + char *login = *loginp; + char *password = *passwordp; +@@ -71,204 +105,212 @@ static int parsenetrc(const char *host, + bool login_alloc = FALSE; + bool password_alloc = FALSE; + enum host_lookup_state state = NOTHING; ++ enum found_state found = NONE; ++ bool our_login = TRUE; /* With specific_login, found *our* login name (or ++ login-less line) */ ++ bool done = FALSE; ++ char *netrcbuffer; ++ struct dynbuf token; ++ struct dynbuf *filebuf = &store->filebuf; ++ Curl_dyn_init(&token, MAX_NETRC_TOKEN); + +- char state_login = 0; /* Found a login keyword */ +- char state_password = 0; /* Found a password keyword */ +- int state_our_login = TRUE; /* With specific_login, found *our* login +- name (or login-less line) */ +- +- DEBUGASSERT(netrcfile); ++ if(!store->loaded) { ++ if(file2memory(netrcfile, filebuf)) ++ return NETRC_FAILED; ++ store->loaded = TRUE; ++ } + +- file = fopen(netrcfile, FOPEN_READTEXT); +- if(file) { +- bool done = FALSE; +- struct dynbuf buf; +- Curl_dyn_init(&buf, MAX_NETRC_LINE); ++ netrcbuffer = Curl_dyn_ptr(filebuf); + +- while(!done && Curl_get_line(&buf, file)) { +- char *tok; ++ while(!done) { ++ char *tok = netrcbuffer; ++ while(tok) { + char *tok_end; + bool quoted; +- char *netrcbuffer = Curl_dyn_ptr(&buf); ++ Curl_dyn_reset(&token); ++ while(ISBLANK(*tok)) ++ tok++; ++ /* tok is first non-space letter */ + if(state == MACDEF) { +- if((netrcbuffer[0] == '\n') || (netrcbuffer[0] == '\r')) +- state = NOTHING; +- else +- continue; ++ if((*tok == '\n') || (*tok == '\r')) ++ state = NOTHING; /* end of macro definition */ + } +- tok = netrcbuffer; +- while(tok) { +- while(ISBLANK(*tok)) +- tok++; +- /* tok is first non-space letter */ +- if(!*tok || (*tok == '#')) +- /* end of line or the rest is a comment */ +- break; + +- /* leading double-quote means quoted string */ +- quoted = (*tok == '\"'); ++ if(!*tok || (*tok == '\n')) ++ /* end of line */ ++ break; + +- tok_end = tok; +- if(!quoted) { +- while(!ISSPACE(*tok_end)) +- tok_end++; +- *tok_end = 0; ++ /* leading double-quote means quoted string */ ++ quoted = (*tok == '\"'); ++ ++ tok_end = tok; ++ if(!quoted) { ++ size_t len = 0; ++ while(!ISSPACE(*tok_end)) { ++ tok_end++; ++ len++; + } +- else { +- bool escape = FALSE; +- bool endquote = FALSE; +- char *store = tok; +- tok_end++; /* pass the leading quote */ +- while(*tok_end) { +- char s = *tok_end; +- if(escape) { +- escape = FALSE; +- switch(s) { +- case 'n': +- s = '\n'; +- break; +- case 'r': +- s = '\r'; +- break; +- case 't': +- s = '\t'; +- break; +- } +- } +- else if(s == '\\') { +- escape = TRUE; +- tok_end++; +- continue; +- } +- else if(s == '\"') { +- tok_end++; /* pass the ending quote */ +- endquote = TRUE; ++ if(!len || Curl_dyn_addn(&token, tok, len)) { ++ retcode = NETRC_FAILED; ++ goto out; ++ } ++ } ++ else { ++ bool escape = FALSE; ++ bool endquote = FALSE; ++ tok_end++; /* pass the leading quote */ ++ while(*tok_end) { ++ char s = *tok_end; ++ if(escape) { ++ escape = FALSE; ++ switch(s) { ++ case 'n': ++ s = '\n'; ++ break; ++ case 'r': ++ s = '\r'; ++ break; ++ case 't': ++ s = '\t'; + break; + } +- *store++ = s; ++ } ++ else if(s == '\\') { ++ escape = TRUE; + tok_end++; ++ continue; ++ } ++ else if(s == '\"') { ++ tok_end++; /* pass the ending quote */ ++ endquote = TRUE; ++ break; + } +- *store = 0; +- if(escape || !endquote) { +- /* bad syntax, get out */ ++ if(Curl_dyn_addn(&token, &s, 1)) { + retcode = NETRC_FAILED; + goto out; + } ++ tok_end++; + } +- +- if((login && *login) && (password && *password)) { +- done = TRUE; +- break; ++ if(escape || !endquote) { ++ /* bad syntax, get out */ ++ retcode = NETRC_FAILED; ++ goto out; + } ++ } + +- switch(state) { +- case NOTHING: +- if(strcasecompare("macdef", tok)) { +- /* Define a macro. A macro is defined with the specified name; its +- contents begin with the next .netrc line and continue until a +- null line (consecutive new-line characters) is encountered. */ +- state = MACDEF; +- } +- else if(strcasecompare("machine", tok)) { +- /* the next tok is the machine name, this is in itself the +- delimiter that starts the stuff entered for this machine, +- after this we need to search for 'login' and +- 'password'. */ +- state = HOSTFOUND; +- } +- else if(strcasecompare("default", tok)) { +- state = HOSTVALID; +- retcode = NETRC_SUCCESS; /* we did find our host */ +- } +- break; +- case MACDEF: +- if(!strlen(tok)) { +- state = NOTHING; +- } +- break; +- case HOSTFOUND: +- if(strcasecompare(host, tok)) { +- /* and yes, this is our host! */ +- state = HOSTVALID; +- retcode = NETRC_SUCCESS; /* we did find our host */ ++ if((login && *login) && (password && *password)) { ++ done = TRUE; ++ break; ++ } ++ ++ tok = Curl_dyn_ptr(&token); ++ ++ switch(state) { ++ case NOTHING: ++ if(strcasecompare("macdef", tok)) ++ /* Define a macro. A macro is defined with the specified name; its ++ contents begin with the next .netrc line and continue until a ++ null line (consecutive new-line characters) is encountered. */ ++ state = MACDEF; ++ else if(strcasecompare("machine", tok)) ++ /* the next tok is the machine name, this is in itself the delimiter ++ that starts the stuff entered for this machine, after this we ++ need to search for 'login' and 'password'. */ ++ state = HOSTFOUND; ++ else if(strcasecompare("default", tok)) { ++ state = HOSTVALID; ++ retcode = NETRC_SUCCESS; /* we did find our host */ ++ } ++ break; ++ case MACDEF: ++ if(!*tok) ++ state = NOTHING; ++ break; ++ case HOSTFOUND: ++ if(strcasecompare(host, tok)) { ++ /* and yes, this is our host! */ ++ state = HOSTVALID; ++ retcode = NETRC_SUCCESS; /* we did find our host */ ++ } ++ else ++ /* not our host */ ++ state = NOTHING; ++ break; ++ case HOSTVALID: ++ /* we are now parsing sub-keywords concerning "our" host */ ++ if(found == LOGIN) { ++ if(specific_login) { ++ our_login = !Curl_timestrcmp(login, tok); + } +- else +- /* not our host */ +- state = NOTHING; +- break; +- case HOSTVALID: +- /* we are now parsing sub-keywords concerning "our" host */ +- if(state_login) { +- if(specific_login) { +- state_our_login = !Curl_timestrcmp(login, tok); ++ else if(!login || Curl_timestrcmp(login, tok)) { ++ if(login_alloc) ++ free(login); ++ login = strdup(tok); ++ if(!login) { ++ retcode = NETRC_FAILED; /* allocation failed */ ++ goto out; + } +- else if(!login || Curl_timestrcmp(login, tok)) { +- if(login_alloc) { +- free(login); +- login_alloc = FALSE; +- } +- login = strdup(tok); +- if(!login) { +- retcode = NETRC_FAILED; /* allocation failed */ +- goto out; +- } +- login_alloc = TRUE; +- } +- state_login = 0; ++ login_alloc = TRUE; + } +- else if(state_password) { +- if((state_our_login || !specific_login) +- && (!password || Curl_timestrcmp(password, tok))) { +- if(password_alloc) { +- free(password); +- password_alloc = FALSE; +- } +- password = strdup(tok); +- if(!password) { +- retcode = NETRC_FAILED; /* allocation failed */ +- goto out; +- } +- password_alloc = TRUE; ++ found = NONE; ++ } ++ else if(found == PASSWORD) { ++ if((our_login || !specific_login) && ++ (!password || Curl_timestrcmp(password, tok))) { ++ if(password_alloc) ++ free(password); ++ password = strdup(tok); ++ if(!password) { ++ retcode = NETRC_FAILED; /* allocation failed */ ++ goto out; + } +- state_password = 0; +- } +- else if(strcasecompare("login", tok)) +- state_login = 1; +- else if(strcasecompare("password", tok)) +- state_password = 1; +- else if(strcasecompare("machine", tok)) { +- /* ok, there's machine here go => */ +- state = HOSTFOUND; +- state_our_login = FALSE; ++ password_alloc = TRUE; + } +- break; +- } /* switch (state) */ +- tok = ++tok_end; +- } +- } /* while Curl_get_line() */ ++ found = NONE; ++ } ++ else if(strcasecompare("login", tok)) ++ found = LOGIN; ++ else if(strcasecompare("password", tok)) ++ found = PASSWORD; ++ else if(strcasecompare("machine", tok)) { ++ /* ok, there is machine here go => */ ++ state = HOSTFOUND; ++ found = NONE; ++ } ++ break; ++ } /* switch (state) */ ++ tok = ++tok_end; ++ } ++ if(!done) { ++ char *nl = NULL; ++ if(tok) ++ nl = strchr(tok, '\n'); ++ if(!nl) ++ break; ++ /* point to next line */ ++ netrcbuffer = &nl[1]; ++ } ++ } /* while !done */ + + out: +- Curl_dyn_free(&buf); +- if(!retcode) { +- /* success */ +- if(login_alloc) { +- if(*loginp) +- free(*loginp); +- *loginp = login; +- } +- if(password_alloc) { +- if(*passwordp) +- free(*passwordp); +- *passwordp = password; +- } ++ Curl_dyn_free(&token); ++ if(!retcode) { ++ /* success */ ++ if(login_alloc) { ++ free(*loginp); ++ *loginp = login; + } +- else { +- if(login_alloc) +- free(login); +- if(password_alloc) +- free(password); ++ if(password_alloc) { ++ free(*passwordp); ++ *passwordp = password; + } +- fclose(file); ++ } ++ else { ++ Curl_dyn_free(filebuf); ++ if(login_alloc) ++ free(login); ++ if(password_alloc) ++ free(password); + } + + return retcode; +@@ -280,7 +322,8 @@ out: + * *loginp and *passwordp MUST be allocated if they aren't NULL when passed + * in. + */ +-int Curl_parsenetrc(const char *host, char **loginp, char **passwordp, ++int Curl_parsenetrc(struct store_netrc *store, const char *host, ++ char **loginp, char **passwordp, + char *netrcfile) + { + int retcode = 1; +@@ -329,7 +372,7 @@ int Curl_parsenetrc(const char *host, char **loginp, char **passwordp, + free(homea); + return -1; + } +- retcode = parsenetrc(host, loginp, passwordp, filealloc); ++ retcode = parsenetrc(store, host, loginp, passwordp, filealloc); + free(filealloc); + #ifdef WIN32 + if(retcode == NETRC_FILE_MISSING) { +@@ -339,15 +382,25 @@ int Curl_parsenetrc(const char *host, char **loginp, char **passwordp, + free(homea); + return -1; + } +- retcode = parsenetrc(host, loginp, passwordp, filealloc); ++ retcode = parsenetrc(store, host, loginp, passwordp, filealloc); + free(filealloc); + } + #endif + free(homea); + } + else +- retcode = parsenetrc(host, loginp, passwordp, netrcfile); ++ retcode = parsenetrc(store, host, loginp, passwordp, netrcfile); + return retcode; + } + ++void Curl_netrc_init(struct store_netrc *s) ++{ ++ Curl_dyn_init(&s->filebuf, MAX_NETRC_FILE); ++ s->loaded = FALSE; ++} ++void Curl_netrc_cleanup(struct store_netrc *s) ++{ ++ Curl_dyn_free(&s->filebuf); ++ s->loaded = FALSE; ++} + #endif +diff --git a/lib/netrc.h b/lib/netrc.h +index 37c95db5e..0ef9ff78e 100644 +--- a/lib/netrc.h ++++ b/lib/netrc.h +@@ -26,9 +26,19 @@ + + #include "curl_setup.h" + #ifndef CURL_DISABLE_NETRC ++#include "dynbuf.h" ++ ++struct store_netrc { ++ struct dynbuf filebuf; ++ char *filename; ++ BIT(loaded); ++}; ++ ++void Curl_netrc_init(struct store_netrc *s); ++void Curl_netrc_cleanup(struct store_netrc *s); + + /* returns -1 on failure, 0 if the host is found, 1 is the host isn't found */ +-int Curl_parsenetrc(const char *host, char **loginp, ++int Curl_parsenetrc(struct store_netrc *s, const char *host, char **loginp, + char **passwordp, char *filename); + /* Assume: (*passwordp)[0]=0, host[0] != 0. + * If (*loginp)[0] = 0, search for login and password within a machine +@@ -38,6 +48,8 @@ int Curl_parsenetrc(const char *host, char **loginp, + #else + /* disabled */ + #define Curl_parsenetrc(a,b,c,d,e,f) 1 ++#define Curl_netrc_init(x) ++#define Curl_netrc_cleanup(x) + #endif + + #endif /* HEADER_CURL_NETRC_H */ +diff --git a/lib/url.c b/lib/url.c +index a59cb0e34..45745bc60 100644 +--- a/lib/url.c ++++ b/lib/url.c +@@ -338,6 +338,7 @@ CURLcode Curl_close(struct Curl_easy **datap) + Curl_wildcard_dtor(&data->wildcard); + Curl_freeset(data); + Curl_headers_cleanup(data); ++ Curl_netrc_cleanup(&data->state.netrc); + free(data); + return CURLE_OK; + } +@@ -545,6 +546,7 @@ CURLcode Curl_open(struct Curl_easy **curl) + + data->progress.flags |= PGRS_HIDE; + data->state.current_speed = -1; /* init to negative == impossible */ ++ Curl_netrc_init(&data->state.netrc); + } + + if(result) { +@@ -2689,7 +2691,7 @@ static CURLcode override_login(struct Curl_easy *data, + url_provided = TRUE; + } + +- ret = Curl_parsenetrc(conn->host.name, ++ ret = Curl_parsenetrc(&data->state.netrc, conn->host.name, + userp, passwdp, + data->set.str[STRING_NETRC_FILE]); + if(ret > 0) { +diff --git a/lib/urldata.h b/lib/urldata.h +index 4e0d6ef98..6aa26237d 100644 +--- a/lib/urldata.h ++++ b/lib/urldata.h +@@ -163,6 +163,7 @@ typedef unsigned int curl_prot_t; + #include "splay.h" + #include "dynbuf.h" + #include "dynhds.h" ++#include "netrc.h" + + /* return the count of bytes sent, or -1 on error */ + typedef ssize_t (Curl_send)(struct Curl_easy *data, /* transfer */ +@@ -1313,6 +1314,10 @@ struct UrlState { + CURLcode hresult; /* used to pass return codes back from hyper callbacks */ + #endif + ++#ifndef CURL_DISABLE_NETRC ++ struct store_netrc netrc; ++#endif ++ + /* Dynamically allocated strings, MUST be freed before this struct is + killed. */ + struct dynamically_allocated_data { +diff --git a/tests/unit/unit1304.c b/tests/unit/unit1304.c +index 2171c0736..238d3c0f7 100644 +--- a/tests/unit/unit1304.c ++++ b/tests/unit/unit1304.c +@@ -49,17 +49,22 @@ static void unit_stop(void) + } + + UNITTEST_START ++{ + int result; ++ struct store_netrc store; + + /* + * Test a non existent host in our netrc file. + */ +- result = Curl_parsenetrc("test.example.com", &login, &password, arg); ++ Curl_netrc_init(&store); ++ result = Curl_parsenetrc(&store, ++ "test.example.com", &login, &password, arg); + fail_unless(result == 1, "Host not found should return 1"); + abort_unless(password != NULL, "returned NULL!"); + fail_unless(password[0] == 0, "password should not have been changed"); + abort_unless(login != NULL, "returned NULL!"); + fail_unless(login[0] == 0, "login should not have been changed"); ++ Curl_netrc_cleanup(&store); + + /* + * Test a non existent login in our netrc file. +@@ -67,13 +72,16 @@ UNITTEST_START + free(login); + login = strdup("me"); + abort_unless(login != NULL, "returned NULL!"); +- result = Curl_parsenetrc("example.com", &login, &password, arg); ++ Curl_netrc_init(&store); ++ result = Curl_parsenetrc(&store, ++ "example.com", &login, &password, arg); + fail_unless(result == 0, "Host should have been found"); + abort_unless(password != NULL, "returned NULL!"); + fail_unless(password[0] == 0, "password should not have been changed"); + abort_unless(login != NULL, "returned NULL!"); + fail_unless(strncmp(login, "me", 2) == 0, + "login should not have been changed"); ++ Curl_netrc_cleanup(&store); + + /* + * Test a non existent login and host in our netrc file. +@@ -81,13 +89,16 @@ UNITTEST_START + free(login); + login = strdup("me"); + abort_unless(login != NULL, "returned NULL!"); +- result = Curl_parsenetrc("test.example.com", &login, &password, arg); ++ Curl_netrc_init(&store); ++ result = Curl_parsenetrc(&store, ++ "test.example.com", &login, &password, arg); + fail_unless(result == 1, "Host not found should return 1"); + abort_unless(password != NULL, "returned NULL!"); + fail_unless(password[0] == 0, "password should not have been changed"); + abort_unless(login != NULL, "returned NULL!"); + fail_unless(strncmp(login, "me", 2) == 0, + "login should not have been changed"); ++ Curl_netrc_cleanup(&store); + + /* + * Test a non existent login (substring of an existing one) in our +@@ -96,13 +107,16 @@ UNITTEST_START + free(login); + login = strdup("admi"); + abort_unless(login != NULL, "returned NULL!"); +- result = Curl_parsenetrc("example.com", &login, &password, arg); ++ Curl_netrc_init(&store); ++ result = Curl_parsenetrc(&store, ++ "example.com", &login, &password, arg); + fail_unless(result == 0, "Host should have been found"); + abort_unless(password != NULL, "returned NULL!"); + fail_unless(password[0] == 0, "password should not have been changed"); + abort_unless(login != NULL, "returned NULL!"); + fail_unless(strncmp(login, "admi", 4) == 0, + "login should not have been changed"); ++ Curl_netrc_cleanup(&store); + + /* + * Test a non existent login (superstring of an existing one) +@@ -111,13 +125,16 @@ UNITTEST_START + free(login); + login = strdup("adminn"); + abort_unless(login != NULL, "returned NULL!"); +- result = Curl_parsenetrc("example.com", &login, &password, arg); ++ Curl_netrc_init(&store); ++ result = Curl_parsenetrc(&store, ++ "example.com", &login, &password, arg); + fail_unless(result == 0, "Host should have been found"); + abort_unless(password != NULL, "returned NULL!"); + fail_unless(password[0] == 0, "password should not have been changed"); + abort_unless(login != NULL, "returned NULL!"); + fail_unless(strncmp(login, "adminn", 6) == 0, + "login should not have been changed"); ++ Curl_netrc_cleanup(&store); + + /* + * Test for the first existing host in our netrc file +@@ -126,13 +143,16 @@ UNITTEST_START + free(login); + login = strdup(""); + abort_unless(login != NULL, "returned NULL!"); +- result = Curl_parsenetrc("example.com", &login, &password, arg); ++ Curl_netrc_init(&store); ++ result = Curl_parsenetrc(&store, ++ "example.com", &login, &password, arg); + fail_unless(result == 0, "Host should have been found"); + abort_unless(password != NULL, "returned NULL!"); + fail_unless(strncmp(password, "passwd", 6) == 0, + "password should be 'passwd'"); + abort_unless(login != NULL, "returned NULL!"); + fail_unless(strncmp(login, "admin", 5) == 0, "login should be 'admin'"); ++ Curl_netrc_cleanup(&store); + + /* + * Test for the first existing host in our netrc file +@@ -141,13 +161,16 @@ UNITTEST_START + free(password); + password = strdup(""); + abort_unless(password != NULL, "returned NULL!"); +- result = Curl_parsenetrc("example.com", &login, &password, arg); ++ Curl_netrc_init(&store); ++ result = Curl_parsenetrc(&store, ++ "example.com", &login, &password, arg); + fail_unless(result == 0, "Host should have been found"); + abort_unless(password != NULL, "returned NULL!"); + fail_unless(strncmp(password, "passwd", 6) == 0, + "password should be 'passwd'"); + abort_unless(login != NULL, "returned NULL!"); + fail_unless(strncmp(login, "admin", 5) == 0, "login should be 'admin'"); ++ Curl_netrc_cleanup(&store); + + /* + * Test for the second existing host in our netrc file +@@ -159,13 +182,16 @@ UNITTEST_START + free(login); + login = strdup(""); + abort_unless(login != NULL, "returned NULL!"); +- result = Curl_parsenetrc("curl.example.com", &login, &password, arg); ++ Curl_netrc_init(&store); ++ result = Curl_parsenetrc(&store, ++ "curl.example.com", &login, &password, arg); + fail_unless(result == 0, "Host should have been found"); + abort_unless(password != NULL, "returned NULL!"); + fail_unless(strncmp(password, "none", 4) == 0, + "password should be 'none'"); + abort_unless(login != NULL, "returned NULL!"); + fail_unless(strncmp(login, "none", 4) == 0, "login should be 'none'"); ++ Curl_netrc_cleanup(&store); + + /* + * Test for the second existing host in our netrc file +@@ -174,14 +200,18 @@ UNITTEST_START + free(password); + password = strdup(""); + abort_unless(password != NULL, "returned NULL!"); +- result = Curl_parsenetrc("curl.example.com", &login, &password, arg); ++ Curl_netrc_init(&store); ++ result = Curl_parsenetrc(&store, ++ "curl.example.com", &login, &password, arg); + fail_unless(result == 0, "Host should have been found"); + abort_unless(password != NULL, "returned NULL!"); + fail_unless(strncmp(password, "none", 4) == 0, + "password should be 'none'"); + abort_unless(login != NULL, "returned NULL!"); + fail_unless(strncmp(login, "none", 4) == 0, "login should be 'none'"); ++ Curl_netrc_cleanup(&store); + ++} + UNITTEST_STOP + + #else +-- +2.33.0 + diff --git a/backport-CVE-2024-11053-pre3.patch b/backport-CVE-2024-11053-pre3.patch new file mode 100644 index 0000000..286a7f4 --- /dev/null +++ b/backport-CVE-2024-11053-pre3.patch @@ -0,0 +1,349 @@ +From 9bee39bfed2c413b4cc4eb306a57ac92a1854907 Mon Sep 17 00:00:00 2001 +From: Daniel Stenberg +Date: Sat, 12 Oct 2024 23:54:39 +0200 +Subject: [PATCH] url: use same credentials on redirect + +Previously it could lose the username and only use the password. + +Added test 998 and 999 to verify. + +Reported-by: Tobias Bora +Fixes #15262 +Closes #15282 + +Conflict:context adapt +Reference:https://github.com/curl/curl/commit/9bee39bfed2c413b4cc4eb306a57ac92a1854907 +--- + lib/transfer.c | 3 + + lib/url.c | 19 ++++--- + lib/urldata.h | 9 ++- + tests/data/Makefile.inc | 14 +++++++------- + tests/data/test998 | 92 ++++++++++++++++++++++++++++++ + tests/data/test999 | 81 ++++++++++++++++++++++++++ + 6 files changed, 195 insertions(+), 11 deletions(-) + create mode 100644 tests/data/test998 + create mode 100644 tests/data/test999 + +diff --git a/lib/transfer.c b/lib/transfer.c +index 79d648cab..3a9239254 100644 +--- a/lib/transfer.c ++++ b/lib/transfer.c +@@ -679,6 +679,9 @@ CURLcode Curl_pretransfer(struct Curl_easy *data) + return CURLE_OUT_OF_MEMORY; + } + ++ if(data->set.str[STRING_USERNAME] || ++ data->set.str[STRING_PASSWORD]) ++ data->state.creds_from = CREDS_OPTION; + if(!result) + result = Curl_setstropt(&data->state.aptr.user, + data->set.str[STRING_USERNAME]); +diff --git a/lib/url.c b/lib/url.c +index 45745bc60..261f61d8d 100644 +--- a/lib/url.c ++++ b/lib/url.c +@@ -1860,10 +1860,10 @@ static CURLcode parseurlandfillconn(struct Curl_easy *data, + return result; + + /* +- * User name and password set with their own options override the +- * credentials possibly set in the URL. ++ * username and password set with their own options override the credentials ++ * possibly set in the URL, but netrc does not. + */ +- if(!data->set.str[STRING_PASSWORD]) { ++ if(!data->state.aptr.passwd || (data->state.creds_from != CREDS_OPTION)) { + uc = curl_url_get(uh, CURLUPART_PASSWORD, &data->state.up.password, 0); + if(!uc) { + char *decoded; +@@ -1876,12 +1876,13 @@ static CURLcode parseurlandfillconn(struct Curl_easy *data, + result = Curl_setstropt(&data->state.aptr.passwd, decoded); + if(result) + return result; ++ data->state.creds_from = CREDS_URL; + } + else if(uc != CURLUE_NO_PASSWORD) + return Curl_uc_to_curlcode(uc); + } + +- if(!data->set.str[STRING_USERNAME]) { ++ if(!data->state.aptr.user || (data->state.creds_from != CREDS_OPTION)) { + /* we don't use the URL API's URL decoder option here since it rejects + control codes and we want to allow them for some schemes in the user + and password fields */ +@@ -1895,13 +1896,10 @@ static CURLcode parseurlandfillconn(struct Curl_easy *data, + return result; + conn->user = decoded; + result = Curl_setstropt(&data->state.aptr.user, decoded); ++ data->state.creds_from = CREDS_URL; + } + else if(uc != CURLUE_NO_USER) + return Curl_uc_to_curlcode(uc); +- else if(data->state.aptr.passwd) { +- /* no user was set but a password, set a blank user */ +- result = Curl_setstropt(&data->state.aptr.user, ""); +- } + if(result) + return result; + } +@@ -2685,7 +2683,8 @@ static CURLcode override_login(struct Curl_easy *data, + int ret; + bool url_provided = FALSE; + +- if(data->state.aptr.user) { ++ if(data->state.aptr.user && ++ (data->state.creds_from != CREDS_NETRC)) { + /* there was a user name in the URL. Use the URL decoded version */ + userp = &data->state.aptr.user; + url_provided = TRUE; +@@ -2733,6 +2732,7 @@ static CURLcode override_login(struct Curl_easy *data, + result = Curl_setstropt(&data->state.aptr.user, *userp); + if(result) + return result; ++ data->state.creds_from = CREDS_NETRC; + } + } + if(data->state.aptr.user) { +@@ -2750,6 +2750,7 @@ static CURLcode override_login(struct Curl_easy *data, + CURLcode result = Curl_setstropt(&data->state.aptr.passwd, *passwdp); + if(result) + return result; ++ data->state.creds_from = CREDS_NETRC; + } + if(data->state.aptr.passwd) { + uc = curl_url_set(data->state.uh, CURLUPART_PASSWORD, +diff --git a/lib/urldata.h b/lib/urldata.h +index 6aa26237d..73f662159 100644 +--- a/lib/urldata.h ++++ b/lib/urldata.h +@@ -1206,6 +1206,11 @@ struct urlpieces { + char *query; + }; + ++#define CREDS_NONE 0 ++#define CREDS_URL 1 /* from URL */ ++#define CREDS_OPTION 2 /* set with a CURLOPT_ */ ++#define CREDS_NETRC 3 /* found in netrc */ ++ + struct UrlState { + /* Points to the connection cache */ + struct conncache *conn_cache; +@@ -1344,7 +1349,6 @@ struct UrlState { + char *proxyuser; + char *proxypasswd; + } aptr; +- + unsigned char httpwant; /* when non-zero, a specific HTTP version requested + to be used in the library's request(s) */ + unsigned char httpversion; /* the lowest HTTP version*10 reported by any +@@ -1354,6 +1358,9 @@ struct UrlState { + unsigned char dselect_bits; /* != 0 -> bitmask of socket events for this + transfer overriding anything the socket may + report */ ++ unsigned int creds_from:2; /* where is the server credentials originating ++ from, see the CREDS_* defines above */ ++ + #ifdef CURLDEBUG + BIT(conncache_lock); + #endif +diff --git a/tests/data/Makefile.inc b/tests/data/Makefile.inc +index 480a88208..02bf2ae25 100644 +--- a/tests/data/Makefile.inc ++++ b/tests/data/Makefile.inc +@@ -133,7 +133,7 @@ test961 test962 test963 test964 test965 test966 test967 test968 test969 \ + test961 test962 test963 test964 test965 test966 test967 test968 test969 \ + test970 test971 test972 test973 test974 test975 test976 test977 test978 \ + test979 test980 test981 test982 test983 test984 test985 test986 test987 \ +-test988 test989 test990 test991 \ ++test988 test989 test990 test991 test998 test999 \ + \ + test1000 test1001 test1002 test1003 test1004 test1005 test1006 test1007 \ + test1008 test1009 test1010 test1011 test1012 test1013 test1014 test1015 \ +diff --git a/tests/data/test998 b/tests/data/test998 +new file mode 100644 +index 000000000..c3a8f5169 +--- /dev/null ++++ b/tests/data/test998 +@@ -0,0 +1,92 @@ ++ ++ ++ ++HTTP ++--location-trusted ++ ++ ++ ++# ++# Server-side ++ ++ ++HTTP/1.1 301 redirect ++Date: Tue, 09 Nov 2010 14:49:00 GMT ++Server: test-server/fake ++Content-Length: 0 ++Connection: close ++Content-Type: text/html ++Location: http://somewhere.else.example/a/path/%TESTNUMBER0002 ++ ++ ++ ++HTTP/1.1 200 OK ++Date: Tue, 09 Nov 2010 14:49:00 GMT ++Content-Length: 6 ++Content-Type: text/html ++Funny-head: yesyes ++ ++-foo- ++ ++ ++ ++HTTP/1.1 301 redirect ++Date: Tue, 09 Nov 2010 14:49:00 GMT ++Server: test-server/fake ++Content-Length: 0 ++Connection: close ++Content-Type: text/html ++Location: http://somewhere.else.example/a/path/%TESTNUMBER0002 ++ ++HTTP/1.1 200 OK ++Date: Tue, 09 Nov 2010 14:49:00 GMT ++Content-Length: 6 ++Content-Type: text/html ++Funny-head: yesyes ++ ++-foo- ++ ++ ++ ++ ++# ++# Client-side ++ ++ ++proxy ++ ++ ++http ++ ++ ++HTTP with auth in URL redirected to another host ++ ++ ++-x %HOSTIP:%HTTPPORT http://alberto:einstein@somwhere.example/%TESTNUMBER --location-trusted ++ ++ ++ ++# ++# Verify data after the test has been "shot" ++ ++ ++QUIT ++ ++ ++GET http://somwhere.example/998 HTTP/1.1 ++Host: somwhere.example ++Authorization: Basic YWxiZXJ0bzplaW5zdGVpbg== ++User-Agent: curl/%VERSION ++Accept: */* ++Proxy-Connection: Keep-Alive ++ ++GET http://somewhere.else.example/a/path/9980002 HTTP/1.1 ++Host: somewhere.else.example ++Authorization: Basic YWxiZXJ0bzplaW5zdGVpbg== ++User-Agent: curl/%VERSION ++Accept: */* ++Proxy-Connection: Keep-Alive ++ ++ ++ ++ +diff --git a/tests/data/test999 b/tests/data/test999 +new file mode 100644 +index 000000000..990a8d09a +--- /dev/null ++++ b/tests/data/test999 +@@ -0,0 +1,81 @@ ++ ++ ++ ++HTTP ++--location-trusted ++ ++ ++ ++# ++# Server-side ++ ++ ++HTTP/1.1 200 OK ++Date: Tue, 09 Nov 2010 14:49:00 GMT ++Content-Length: 6 ++Content-Type: text/html ++Funny-head: yesyes ++ ++-foo- ++ ++ ++ ++HTTP/1.1 301 redirect ++Date: Tue, 09 Nov 2010 14:49:00 GMT ++Server: test-server/fake ++Content-Length: 0 ++Connection: close ++Content-Type: text/html ++Location: http://somewhere.else.example/a/path/%TESTNUMBER0002 ++ ++HTTP/1.1 200 OK ++Date: Tue, 09 Nov 2010 14:49:00 GMT ++Content-Length: 6 ++Content-Type: text/html ++Funny-head: yesyes ++ ++-foo- ++ ++ ++ ++ ++# ++# Client-side ++ ++ ++proxy ++ ++ ++http ++ ++ ++HTTP with auth in first URL but not second ++ ++ ++-x %HOSTIP:%HTTPPORT http://alberto:einstein@somwhere.example/%TESTNUMBER http://somewhere.else.example/%TESTNUMBER ++ ++ ++ ++# ++# Verify data after the test has been "shot" ++ ++ ++QUIT ++ ++ ++GET http://somwhere.example/%TESTNUMBER HTTP/1.1 ++Host: somwhere.example ++Authorization: Basic YWxiZXJ0bzplaW5zdGVpbg== ++User-Agent: curl/%VERSION ++Accept: */* ++Proxy-Connection: Keep-Alive ++ ++GET http://somewhere.else.example/%TESTNUMBER HTTP/1.1 ++Host: somewhere.else.example ++User-Agent: curl/%VERSION ++Accept: */* ++Proxy-Connection: Keep-Alive ++ ++ ++ ++ +-- +2.33.0 + diff --git a/backport-CVE-2024-11053-pre4.patch b/backport-CVE-2024-11053-pre4.patch new file mode 100644 index 0000000..98e3deb --- /dev/null +++ b/backport-CVE-2024-11053-pre4.patch @@ -0,0 +1,223 @@ +From f5c616930b5cf148b1b2632da4f5963ff48bdf88 Mon Sep 17 00:00:00 2001 +From: Daniel Stenberg +Date: Thu, 7 Nov 2024 08:52:38 +0100 +Subject: [PATCH] duphandle: also init netrc + +The netrc init was only done in the Curl_open, meaning that a duplicated +handle would not get inited properly. + +Added test 2309 to verify. It does netrc auth with a duplicated handle. + +Regression from 3b43a05e000aa8f65bda513f733a + +Reported-by: tranzystorekk on github +Fixes #15496 +Closes #15503 + +Conflict:context adapt +Reference:https://github.com/curl/curl/commit/f5c616930b5cf148b1b2632da4f5963ff48bdf88 +--- + lib/easy.c | 1 + + tests/data/Makefile.inc | 2 ++ + tests/data/test2309 | 66 ++++++++++++++++++++++++++++++++++++++ + tests/libtest/Makefile.inc | 5 ++- + tests/libtest/lib2309.c | 66 ++++++++++++++++++++++++++++++++++++++ + 5 files changed, 139 insertions(+), 1 deletions(-) + create mode 100644 tests/data/test2309 + create mode 100644 tests/libtest/lib2309.c + +diff --git a/lib/easy.c b/lib/easy.c +index d16fa8c07..ac8fab342 100644 +--- a/lib/easy.c ++++ b/lib/easy.c +@@ -940,6 +940,7 @@ CURL *curl_easy_duphandle(CURL *d) + goto fail; + + Curl_dyn_init(&outcurl->state.headerb, CURL_MAX_HTTP_HEADER); ++ Curl_netrc_init(&outcurl->state.netrc); + + /* the connection cache is setup on demand */ + outcurl->state.conn_cache = NULL; +diff --git a/tests/data/Makefile.inc b/tests/data/Makefile.inc +index 02bf2ae25..ea5221c00 100644 +--- a/tests/data/Makefile.inc ++++ b/tests/data/Makefile.inc +@@ -255,6 +255,8 @@ test2100 \ + test2200 test2201 test2202 test2203 test2204 test2205 \ + \ + test2300 test2301 test2302 test2303 test2304 test2305 test2306 \ ++\ ++test2309 \ + \ + test2400 test2401 test2402 test2403 test2404 \ + \ +diff --git a/tests/data/test2309 b/tests/data/test2309 +new file mode 100644 +index 000000000..4ba78ee91 +--- /dev/null ++++ b/tests/data/test2309 +@@ -0,0 +1,66 @@ ++ ++ ++ ++netrc ++HTTP ++ ++ ++# ++# Server-side ++ ++ ++HTTP/1.1 200 OK ++Date: Tue, 09 Nov 2010 14:49:00 GMT ++Server: test-server/fake ++Last-Modified: Tue, 13 Jun 2000 12:10:00 GMT ++ETag: "21025-dc7-39462498" ++Accept-Ranges: bytes ++Content-Length: 6 ++Connection: close ++Content-Type: text/html ++Funny-head: yesyes ++ ++-foo- ++ ++ ++ ++# ++# Client-side ++ ++ ++http ++ ++ ++proxy ++ ++ ++# Reproducing issue 15496 ++ ++HTTP with .netrc using duped easy handle ++ ++ ++lib%TESTNUMBER ++ ++ ++http://github.com %LOGDIR/netrc%TESTNUMBER http://%HOSTIP:%HTTPPORT/ ++ ++ ++ ++machine github.com ++ ++login daniel ++password $y$j9T$WUVjiVvDbRAWafDLs6cab1$01NX.oaZKf5lw8MR2Nk9Yaxv4CqbE0IaDF.GpGxPul1 ++ ++ ++ ++ ++ ++GET http://github.com/ HTTP/1.1 ++Host: github.com ++Authorization: Basic %b64[daniel:$y$j9T$WUVjiVvDbRAWafDLs6cab1$01NX.oaZKf5lw8MR2Nk9Yaxv4CqbE0IaDF.GpGxPul1]b64% ++Accept: */* ++Proxy-Connection: Keep-Alive ++ ++ ++ ++ +diff --git a/tests/libtest/Makefile.inc b/tests/libtest/Makefile.inc +index 339a00fc4..8f58fd642 100644 +--- a/tests/libtest/Makefile.inc ++++ b/tests/libtest/Makefile.inc +@@ -77,7 +77,7 @@ LIBTESTPROGS = libauthretry libntlmconnect libprereq \ + lib1945 lib1946 lib1947 lib1948 lib1955 lib1956 lib1957 lib1958 lib1959 \ + lib1960 lib1964 \ + lib1970 lib1971 lib1972 lib1973 lib1974 lib1975 \ +- lib2301 lib2302 lib2304 lib2305 lib2306 \ ++ lib2301 lib2302 lib2304 lib2305 lib2306 lib2309 \ + lib2402 lib2404 \ + lib2502 \ + lib3010 lib3025 lib3026 lib3027 \ +@@ -683,6 +683,9 @@ lib2306_LDADD = $(TESTUTIL_LIBS) + lib2306_SOURCES = lib2306.c $(SUPPORTFILES) + lib2306_LDADD = $(TESTUTIL_LIBS) + ++lib2309_SOURCES = lib2309.c $(SUPPORTFILES) ++lib2309_LDADD = $(TESTUTIL_LIBS) ++ + lib2402_SOURCES = lib2402.c $(SUPPORTFILES) $(TESTUTIL) $(WARNLESS) + lib2402_LDADD = $(TESTUTIL_LIBS) + +diff --git a/tests/libtest/lib2309.c b/tests/libtest/lib2309.c +new file mode 100644 +index 000000000..11f1c1fbd +--- /dev/null ++++ b/tests/libtest/lib2309.c +@@ -0,0 +1,66 @@ ++/*************************************************************************** ++ * _ _ ____ _ ++ * Project ___| | | | _ \| | ++ * / __| | | | |_) | | ++ * | (__| |_| | _ <| |___ ++ * \___|\___/|_| \_\_____| ++ * ++ * Copyright (C) Daniel Stenberg, , et al. ++ * ++ * This software is licensed as described in the file COPYING, which ++ * you should have received as part of this distribution. The terms ++ * are also available at https://curl.se/docs/copyright.html. ++ * ++ * You may opt to use, copy, modify, merge, publish, distribute and/or sell ++ * copies of the Software, and permit persons to whom the Software is ++ * furnished to do so, under the terms of the COPYING file. ++ * ++ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY ++ * KIND, either express or implied. ++ * ++ * SPDX-License-Identifier: curl ++ * ++ ***************************************************************************/ ++ ++#include "test.h" ++#include "testtrace.h" ++ ++#include ++ ++static size_t cb_ignore(char *buffer, size_t size, size_t nmemb, void *userp) ++{ ++ (void)buffer; ++ (void)size; ++ (void)nmemb; ++ (void)userp; ++ return CURL_WRITEFUNC_ERROR; ++} ++ ++int test(char *URL) ++{ ++ CURL *curl; ++ CURL *curldupe; ++ int res = CURLE_OK; ++ ++ global_init(CURL_GLOBAL_ALL); ++ curl = curl_easy_init(); ++ if(curl) { ++ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, cb_ignore); ++ curl_easy_setopt(curl, CURLOPT_URL, URL); ++ curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); ++ curl_easy_setopt(curl, CURLOPT_PROXY, libtest_arg3); ++ curl_easy_setopt(curl, CURLOPT_NETRC, (long)CURL_NETRC_REQUIRED); ++ curl_easy_setopt(curl, CURLOPT_NETRC_FILE, libtest_arg2); ++ ++ curldupe = curl_easy_duphandle(curl); ++ if(curldupe) { ++ res = curl_easy_perform(curldupe); ++ printf("Returned %d, should be %d.\n", res, CURLE_WRITE_ERROR); ++ fflush(stdout); ++ curl_easy_cleanup(curldupe); ++ } ++ curl_easy_cleanup(curl); ++ } ++ curl_global_cleanup(); ++ return 0; ++} +-- +2.33.0 + diff --git a/backport-CVE-2024-11053-pre5.patch b/backport-CVE-2024-11053-pre5.patch new file mode 100644 index 0000000..a50c273 --- /dev/null +++ b/backport-CVE-2024-11053-pre5.patch @@ -0,0 +1,37 @@ +From 0cdde0fdfbeb8c35420f6d03fa4b77ed73497694 Mon Sep 17 00:00:00 2001 +From: Daniel Stenberg +Date: Thu, 7 Nov 2024 17:03:54 +0100 +Subject: [PATCH] netrc: support large file, longer lines, longer tokens + +Regression from 3b43a05e000aa8f6 (shipped in 8.11.0) + +Reported-by: Moritz +Fixes #15513 +Closes #15514 + +Conflict:NA +Reference:https://github.com/curl/curl/commit/0cdde0fdfbeb8c35420f6d03fa4b77ed73497694 +--- + lib/netrc.c | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) + +diff --git a/lib/netrc.c b/lib/netrc.c +index c23f927ce..034c0307a 100644 +--- a/lib/netrc.c ++++ b/lib/netrc.c +@@ -58,9 +58,9 @@ enum found_state { + #define NETRC_FAILED -1 + #define NETRC_SUCCESS 0 + +-#define MAX_NETRC_LINE 4096 +-#define MAX_NETRC_FILE (64*1024) +-#define MAX_NETRC_TOKEN 128 ++#define MAX_NETRC_LINE 16384 ++#define MAX_NETRC_FILE (128*1024) ++#define MAX_NETRC_TOKEN 4096 + + static CURLcode file2memory(const char *filename, struct dynbuf *filebuf) + { +-- +2.33.0 + diff --git a/backport-CVE-2024-11053.patch b/backport-CVE-2024-11053.patch new file mode 100644 index 0000000..b920c42 --- /dev/null +++ b/backport-CVE-2024-11053.patch @@ -0,0 +1,728 @@ +From e9b9bbac22c26cf67316fa8e6c6b9e831af31949 Mon Sep 17 00:00:00 2001 +From: Daniel Stenberg +Date: Fri, 15 Nov 2024 11:06:36 +0100 +Subject: [PATCH] netrc: address several netrc parser flaws + +- make sure that a match that returns a username also returns a + password, that should be blank if no password is found + +- fix handling of multiple logins for same host where the password/login + order might be reversed. + +- reject credentials provided in the .netrc if they contain ASCII control + codes - if the used protocol does not support such (like HTTP and WS do) + +Reported-by: Harry Sintonen + +Add test 478, 479 and 480 to verify. Updated unit 1304. + +Closes #15586 + +Conflict:context adapt +Reference:https://github.com/curl/curl/e9b9bbac22c26cf67316fa8e6c6b9e831af31949 +--- + lib/netrc.c | 113 +++++++++++++++++++++++------------------ + lib/url.c | 60 +++++++++++++++------- + tests/data/Makefile.inc | 1 + + tests/data/test478 | 73 ++++++++++++++++++++++++++ + tests/data/test479 | 107 ++++++++++++++++++++++++++++++++++++++ + tests/data/test480 | 38 ++++++++++++++ + tests/unit/unit1304.c | 75 ++++++++------------------- + 7 files changed, 345 insertions(+), 122 deletions(-) + create mode 100644 tests/data/test478 + create mode 100644 tests/data/test479 + create mode 100644 tests/data/test480 + +diff --git a/lib/netrc.c b/lib/netrc.c +index 034c0307a..e787a6ffc 100644 +--- a/lib/netrc.c ++++ b/lib/netrc.c +@@ -54,6 +54,9 @@ enum found_state { + PASSWORD + }; + ++#define FOUND_LOGIN 1 ++#define FOUND_PASSWORD 2 ++ + #define NETRC_FILE_MISSING 1 + #define NETRC_FAILED -1 + #define NETRC_SUCCESS 0 +@@ -94,24 +97,24 @@ done: + */ + static int parsenetrc(struct store_netrc *store, + const char *host, +- char **loginp, ++ char **loginp, /* might point to a username */ + char **passwordp, + const char *netrcfile) + { + int retcode = NETRC_FILE_MISSING; + char *login = *loginp; +- char *password = *passwordp; +- bool specific_login = (login && *login != 0); +- bool login_alloc = FALSE; +- bool password_alloc = FALSE; ++ char *password = NULL; ++ bool specific_login = login; /* points to something */ + enum host_lookup_state state = NOTHING; +- enum found_state found = NONE; +- bool our_login = TRUE; /* With specific_login, found *our* login name (or +- login-less line) */ ++ enum found_state keyword = NONE; ++ unsigned char found = 0; /* login + password found bits, as they can come in ++ any order */ ++ bool our_login = FALSE; /* found our login name */ + bool done = FALSE; + char *netrcbuffer; + struct dynbuf token; + struct dynbuf *filebuf = &store->filebuf; ++ DEBUGASSERT(!*passwordp); + Curl_dyn_init(&token, MAX_NETRC_TOKEN); + + if(!store->loaded) { +@@ -124,7 +127,7 @@ static int parsenetrc(struct store_netrc *store, + + while(!done) { + char *tok = netrcbuffer; +- while(tok) { ++ while(tok && !done) { + char *tok_end; + bool quoted; + Curl_dyn_reset(&token); +@@ -198,11 +201,6 @@ static int parsenetrc(struct store_netrc *store, + } + } + +- if((login && *login) && (password && *password)) { +- done = TRUE; +- break; +- } +- + tok = Curl_dyn_ptr(&token); + + switch(state) { +@@ -212,11 +210,18 @@ static int parsenetrc(struct store_netrc *store, + contents begin with the next .netrc line and continue until a + null line (consecutive new-line characters) is encountered. */ + state = MACDEF; +- else if(strcasecompare("machine", tok)) ++ else if(strcasecompare("machine", tok)) { + /* the next tok is the machine name, this is in itself the delimiter + that starts the stuff entered for this machine, after this we + need to search for 'login' and 'password'. */ + state = HOSTFOUND; ++ keyword = NONE; ++ found = 0; ++ our_login = FALSE; ++ Curl_safefree(password); ++ if(!specific_login) ++ Curl_safefree(login); ++ } + else if(strcasecompare("default", tok)) { + state = HOSTVALID; + retcode = NETRC_SUCCESS; /* we did find our host */ +@@ -238,44 +243,54 @@ static int parsenetrc(struct store_netrc *store, + break; + case HOSTVALID: + /* we are now parsing sub-keywords concerning "our" host */ +- if(found == LOGIN) { +- if(specific_login) { ++ if(keyword == LOGIN) { ++ if(specific_login) + our_login = !Curl_timestrcmp(login, tok); +- } +- else if(!login || Curl_timestrcmp(login, tok)) { +- if(login_alloc) +- free(login); ++ else { ++ our_login = TRUE; ++ free(login); + login = strdup(tok); + if(!login) { + retcode = NETRC_FAILED; /* allocation failed */ + goto out; + } +- login_alloc = TRUE; + } +- found = NONE; ++ found |= FOUND_LOGIN; ++ keyword = NONE; + } +- else if(found == PASSWORD) { +- if((our_login || !specific_login) && +- (!password || Curl_timestrcmp(password, tok))) { +- if(password_alloc) +- free(password); +- password = strdup(tok); +- if(!password) { +- retcode = NETRC_FAILED; /* allocation failed */ +- goto out; +- } +- password_alloc = TRUE; ++ else if(keyword == PASSWORD) { ++ free(password); ++ password = strdup(tok); ++ if(!password) { ++ retcode = NETRC_FAILED; /* allocation failed */ ++ goto out; + } +- found = NONE; ++ found |= FOUND_PASSWORD; ++ keyword = NONE; + } + else if(strcasecompare("login", tok)) +- found = LOGIN; ++ keyword = LOGIN; + else if(strcasecompare("password", tok)) +- found = PASSWORD; ++ keyword = PASSWORD; + else if(strcasecompare("machine", tok)) { +- /* ok, there is machine here go => */ ++ /* a new machine here */ + state = HOSTFOUND; +- found = NONE; ++ keyword = NONE; ++ found = 0; ++ Curl_safefree(password); ++ if(!specific_login) ++ Curl_safefree(login); ++ } ++ else if(strcasecompare("default", tok)) { ++ state = HOSTVALID; ++ retcode = NETRC_SUCCESS; /* we did find our host */ ++ Curl_safefree(password); ++ if(!specific_login) ++ Curl_safefree(login); ++ } ++ if((found == (FOUND_PASSWORD|FOUND_LOGIN)) && our_login) { ++ done = TRUE; ++ break; + } + break; + } /* switch (state) */ +@@ -294,23 +309,23 @@ static int parsenetrc(struct store_netrc *store, + + out: + Curl_dyn_free(&token); ++ if(!retcode && !password && our_login) { ++ /* success without a password, set a blank one */ ++ password = strdup(""); ++ if(!password) ++ retcode = 1; /* out of memory */ ++ } + if(!retcode) { + /* success */ +- if(login_alloc) { +- free(*loginp); ++ if(!specific_login) + *loginp = login; +- } +- if(password_alloc) { +- free(*passwordp); +- *passwordp = password; +- } ++ *passwordp = password; + } + else { + Curl_dyn_free(filebuf); +- if(login_alloc) ++ if(!specific_login) + free(login); +- if(password_alloc) +- free(password); ++ free(password); + } + + return retcode; +diff --git a/lib/url.c b/lib/url.c +index f9bb05f79..436edd891 100644 +--- a/lib/url.c ++++ b/lib/url.c +@@ -2651,6 +2651,17 @@ static CURLcode parse_remote_port(struct Curl_easy *data, + return CURLE_OK; + } + ++static bool str_has_ctrl(const char *input) ++{ ++ const unsigned char *str = (const unsigned char *)input; ++ while(*str) { ++ if(*str < 0x20) ++ return TRUE; ++ str++; ++ } ++ return FALSE; ++} ++ + /* + * Override the login details from the URL with that in the CURLOPT_USERPWD + * option or a .netrc file, if applicable. +@@ -2682,29 +2693,40 @@ static CURLcode override_login(struct Curl_easy *data, + + if(data->state.aptr.user && + (data->state.creds_from != CREDS_NETRC)) { +- /* there was a user name in the URL. Use the URL decoded version */ ++ /* there was a username with a length in the URL. Use the URL decoded ++ version */ + userp = &data->state.aptr.user; + url_provided = TRUE; + } + +- ret = Curl_parsenetrc(&data->state.netrc, conn->host.name, +- userp, passwdp, +- data->set.str[STRING_NETRC_FILE]); +- if(ret > 0) { +- infof(data, "Couldn't find host %s in the %s file; using defaults", +- conn->host.name, +- (data->set.str[STRING_NETRC_FILE] ? +- data->set.str[STRING_NETRC_FILE] : ".netrc")); +- } +- else if(ret < 0) { +- failf(data, ".netrc parser error"); +- return CURLE_READ_ERROR; +- } +- else { +- /* set bits.netrc TRUE to remember that we got the name from a .netrc +- file, so that it is safe to use even if we followed a Location: to a +- different host or similar. */ +- conn->bits.netrc = TRUE; ++ if(!*passwdp) { ++ ret = Curl_parsenetrc(&data->state.netrc, conn->host.name, ++ userp, passwdp, ++ data->set.str[STRING_NETRC_FILE]); ++ if(ret > 0) { ++ infof(data, "Couldn't find host %s in the %s file; using defaults", ++ conn->host.name, ++ (data->set.str[STRING_NETRC_FILE] ? ++ data->set.str[STRING_NETRC_FILE] : ".netrc")); ++ } ++ else if(ret < 0) { ++ failf(data, ".netrc parser error"); ++ return CURLE_READ_ERROR; ++ } ++ else { ++ if(!(conn->handler->flags&PROTOPT_USERPWDCTRL)) { ++ /* if the protocol can't handle control codes in credentials, make ++ sure there are none */ ++ if(str_has_ctrl(*userp) || str_has_ctrl(*passwdp)) { ++ failf(data, "control code detected in .netrc credentials"); ++ return CURLE_READ_ERROR; ++ } ++ } ++ /* set bits.netrc TRUE to remember that we got the name from a .netrc ++ file, so that it is safe to use even if we followed a Location: to a ++ different host or similar. */ ++ conn->bits.netrc = TRUE; ++ } + } + if(url_provided) { + Curl_safefree(conn->user); +diff --git a/tests/data/Makefile.inc b/tests/data/Makefile.inc +index ea5221c00..53f62c6e2 100644 +--- a/tests/data/Makefile.inc ++++ b/tests/data/Makefile.inc +@@ -77,6 +77,7 @@ test435 test436 test437 test438 test439 test440 test441 test442 test443 \ + test435 test436 test437 test438 test439 test440 test441 test442 test443 \ + test444 test445 test446 test447 test448 test449 test450 test451 test452 \ + test453 test454 test455 test456 test457 test458 \ ++test478 test479 test480 \ + \ + test490 test491 test492 test493 test494 test495 test496 test497 test498 \ + \ +diff --git a/tests/data/test478 b/tests/data/test478 +new file mode 100644 +index 000000000..6558363f5 +--- /dev/null ++++ b/tests/data/test478 +@@ -0,0 +1,73 @@ ++ ++ ++ ++netrc ++HTTP ++ ++ ++# ++# Server-side ++ ++ ++HTTP/1.1 200 OK ++Date: Tue, 09 Nov 2010 14:49:00 GMT ++Server: test-server/fake ++Last-Modified: Tue, 13 Jun 2000 12:10:00 GMT ++ETag: "21025-dc7-39462498" ++Accept-Ranges: bytes ++Content-Length: 6 ++Connection: close ++Content-Type: text/html ++Funny-head: yesyes ++ ++-foo- ++ ++ ++ ++# ++# Client-side ++ ++ ++http ++ ++ ++proxy ++ ++ ++.netrc with multiple accounts for same host ++ ++ ++--netrc --netrc-file %LOGDIR/netrc%TESTNUMBER -x http://%HOSTIP:%HTTPPORT/ http://debbie@github.com/ ++ ++ ++ ++machine github.com ++password weird ++password firstone ++login daniel ++ ++machine github.com ++ ++machine github.com ++login debbie ++ ++machine github.com ++password weird ++password "second\r" ++login debbie ++ ++ ++ ++ ++ ++ ++GET http://github.com/ HTTP/1.1 ++Host: github.com ++Authorization: Basic %b64[debbie:second%0D]b64% ++User-Agent: curl/%VERSION ++Accept: */* ++Proxy-Connection: Keep-Alive ++ ++ ++ ++ +diff --git a/tests/data/test479 b/tests/data/test479 +new file mode 100644 +index 000000000..d7ce4652f +--- /dev/null ++++ b/tests/data/test479 +@@ -0,0 +1,107 @@ ++ ++ ++ ++netrc ++HTTP ++ ++ ++# ++# Server-side ++ ++ ++HTTP/1.1 301 Follow this you fool ++Date: Tue, 09 Nov 2010 14:49:00 GMT ++Server: test-server/fake ++Last-Modified: Tue, 13 Jun 2000 12:10:00 GMT ++ETag: "21025-dc7-39462498" ++Accept-Ranges: bytes ++Content-Length: 6 ++Connection: close ++Location: http://b.com/%TESTNUMBER0002 ++ ++-foo- ++ ++ ++ ++HTTP/1.1 200 OK ++Date: Tue, 09 Nov 2010 14:49:00 GMT ++Server: test-server/fake ++Last-Modified: Tue, 13 Jun 2000 12:10:00 GMT ++ETag: "21025-dc7-39462498" ++Accept-Ranges: bytes ++Content-Length: 7 ++Connection: close ++ ++target ++ ++ ++ ++HTTP/1.1 301 Follow this you fool ++Date: Tue, 09 Nov 2010 14:49:00 GMT ++Server: test-server/fake ++Last-Modified: Tue, 13 Jun 2000 12:10:00 GMT ++ETag: "21025-dc7-39462498" ++Accept-Ranges: bytes ++Content-Length: 6 ++Connection: close ++Location: http://b.com/%TESTNUMBER0002 ++ ++HTTP/1.1 200 OK ++Date: Tue, 09 Nov 2010 14:49:00 GMT ++Server: test-server/fake ++Last-Modified: Tue, 13 Jun 2000 12:10:00 GMT ++ETag: "21025-dc7-39462498" ++Accept-Ranges: bytes ++Content-Length: 7 ++Connection: close ++ ++target ++ ++ ++ ++# ++# Client-side ++ ++ ++http ++ ++ ++proxy ++ ++ ++.netrc with redirect and default without password ++ ++ ++--netrc --netrc-file %LOGDIR/netrc%TESTNUMBER -L -x http://%HOSTIP:%HTTPPORT/ http://a.com/ ++ ++ ++ ++machine a.com ++ login alice ++ password alicespassword ++ ++default ++ login bob ++ ++ ++ ++ ++ ++ ++GET http://a.com/ HTTP/1.1 ++Host: a.com ++Authorization: Basic %b64[alice:alicespassword]b64% ++User-Agent: curl/%VERSION ++Accept: */* ++Proxy-Connection: Keep-Alive ++ ++GET http://b.com/%TESTNUMBER0002 HTTP/1.1 ++Host: b.com ++Authorization: Basic %b64[bob:]b64% ++User-Agent: curl/%VERSION ++Accept: */* ++Proxy-Connection: Keep-Alive ++ ++ ++ ++ +diff --git a/tests/data/test480 b/tests/data/test480 +new file mode 100644 +index 000000000..aab889f47 +--- /dev/null ++++ b/tests/data/test480 +@@ -0,0 +1,38 @@ ++ ++ ++ ++netrc ++pop3 ++ ++ ++# ++# Server-side ++ ++ ++ ++ ++# ++# Client-side ++ ++ ++pop3 ++ ++ ++Reject .netrc with credentials using CRLF for POP3 ++ ++ ++--netrc --netrc-file %LOGDIR/netrc%TESTNUMBER pop3://%HOSTIP:%POP3PORT/%TESTNUMBER ++ ++ ++machine %HOSTIP ++ login alice ++ password "password\r\ncommand" ++ ++ ++ ++ ++ ++26 ++ ++ ++ +diff --git a/tests/unit/unit1304.c b/tests/unit/unit1304.c +index 238d3c0f7..817887b94 100644 +--- a/tests/unit/unit1304.c ++++ b/tests/unit/unit1304.c +@@ -32,13 +32,8 @@ static char *password; + + static CURLcode unit_setup(void) + { +- password = strdup(""); +- login = strdup(""); +- if(!password || !login) { +- Curl_safefree(password); +- Curl_safefree(login); +- return CURLE_OUT_OF_MEMORY; +- } ++ password = NULL; ++ login = NULL; + return CURLE_OK; + } + +@@ -60,89 +55,61 @@ UNITTEST_START + result = Curl_parsenetrc(&store, + "test.example.com", &login, &password, arg); + fail_unless(result == 1, "Host not found should return 1"); +- abort_unless(password != NULL, "returned NULL!"); +- fail_unless(password[0] == 0, "password should not have been changed"); +- abort_unless(login != NULL, "returned NULL!"); +- fail_unless(login[0] == 0, "login should not have been changed"); ++ abort_unless(password == NULL, "password did not return NULL!"); ++ abort_unless(login == NULL, "user did not return NULL!"); + Curl_netrc_cleanup(&store); + + /* + * Test a non existent login in our netrc file. + */ +- free(login); +- login = strdup("me"); +- abort_unless(login != NULL, "returned NULL!"); ++ login = (char *)"me"; + Curl_netrc_init(&store); + result = Curl_parsenetrc(&store, + "example.com", &login, &password, arg); + fail_unless(result == 0, "Host should have been found"); +- abort_unless(password != NULL, "returned NULL!"); +- fail_unless(password[0] == 0, "password should not have been changed"); +- abort_unless(login != NULL, "returned NULL!"); +- fail_unless(strncmp(login, "me", 2) == 0, +- "login should not have been changed"); ++ abort_unless(password == NULL, "password is not NULL!"); + Curl_netrc_cleanup(&store); + + /* + * Test a non existent login and host in our netrc file. + */ +- free(login); +- login = strdup("me"); +- abort_unless(login != NULL, "returned NULL!"); ++ login = (char *)"me"; + Curl_netrc_init(&store); + result = Curl_parsenetrc(&store, + "test.example.com", &login, &password, arg); + fail_unless(result == 1, "Host not found should return 1"); +- abort_unless(password != NULL, "returned NULL!"); +- fail_unless(password[0] == 0, "password should not have been changed"); +- abort_unless(login != NULL, "returned NULL!"); +- fail_unless(strncmp(login, "me", 2) == 0, +- "login should not have been changed"); ++ abort_unless(password == NULL, "password is not NULL!"); + Curl_netrc_cleanup(&store); + + /* + * Test a non existent login (substring of an existing one) in our + * netrc file. + */ +- free(login); +- login = strdup("admi"); +- abort_unless(login != NULL, "returned NULL!"); ++ login = (char *)"admi"; + Curl_netrc_init(&store); + result = Curl_parsenetrc(&store, + "example.com", &login, &password, arg); + fail_unless(result == 0, "Host should have been found"); +- abort_unless(password != NULL, "returned NULL!"); +- fail_unless(password[0] == 0, "password should not have been changed"); +- abort_unless(login != NULL, "returned NULL!"); +- fail_unless(strncmp(login, "admi", 4) == 0, +- "login should not have been changed"); ++ abort_unless(password == NULL, "password is not NULL!"); + Curl_netrc_cleanup(&store); + + /* + * Test a non existent login (superstring of an existing one) + * in our netrc file. + */ +- free(login); +- login = strdup("adminn"); +- abort_unless(login != NULL, "returned NULL!"); ++ login = (char *)"adminn"; + Curl_netrc_init(&store); + result = Curl_parsenetrc(&store, + "example.com", &login, &password, arg); + fail_unless(result == 0, "Host should have been found"); +- abort_unless(password != NULL, "returned NULL!"); +- fail_unless(password[0] == 0, "password should not have been changed"); +- abort_unless(login != NULL, "returned NULL!"); +- fail_unless(strncmp(login, "adminn", 6) == 0, +- "login should not have been changed"); ++ abort_unless(password == NULL, "password is not NULL!"); + Curl_netrc_cleanup(&store); + + /* + * Test for the first existing host in our netrc file + * with login[0] = 0. + */ +- free(login); +- login = strdup(""); +- abort_unless(login != NULL, "returned NULL!"); ++ login = NULL; + Curl_netrc_init(&store); + result = Curl_parsenetrc(&store, + "example.com", &login, &password, arg); +@@ -159,8 +126,9 @@ UNITTEST_START + * with login[0] != 0. + */ + free(password); +- password = strdup(""); +- abort_unless(password != NULL, "returned NULL!"); ++ free(login); ++ password = NULL; ++ login = NULL; + Curl_netrc_init(&store); + result = Curl_parsenetrc(&store, + "example.com", &login, &password, arg); +@@ -177,11 +145,9 @@ UNITTEST_START + * with login[0] = 0. + */ + free(password); +- password = strdup(""); +- abort_unless(password != NULL, "returned NULL!"); ++ password = NULL; + free(login); +- login = strdup(""); +- abort_unless(login != NULL, "returned NULL!"); ++ login = NULL; + Curl_netrc_init(&store); + result = Curl_parsenetrc(&store, + "curl.example.com", &login, &password, arg); +@@ -198,8 +164,9 @@ UNITTEST_START + * with login[0] != 0. + */ + free(password); +- password = strdup(""); +- abort_unless(password != NULL, "returned NULL!"); ++ free(login); ++ password = NULL; ++ login = NULL; + Curl_netrc_init(&store); + result = Curl_parsenetrc(&store, + "curl.example.com", &login, &password, arg); +-- +2.33.0 + diff --git a/curl.spec b/curl.spec index 1b60957..d4012d4 100644 --- a/curl.spec +++ b/curl.spec @@ -7,7 +7,7 @@ Name: curl Version: 8.4.0 -Release: 13 +Release: 14 Summary: Curl is used in command lines or scripts to transfer data License: curl URL: https://curl.se/ @@ -38,6 +38,14 @@ Patch29: backport-pre-CVE-2024-9681.patch Patch30: backport-CVE-2024-9681.patch Patch31: backport-multi-check-that-the-multi-handle-is-valid-in-curl_m.patch Patch32: backport-cookie-treat-cookie-name-case-sensitively.patch +Patch33: backport-CVE-2024-11053-pre1.patch +Patch34: backport-CVE-2024-11053-pre2.patch +Patch35: backport-CVE-2024-11053-pre3.patch +Patch36: backport-CVE-2024-11053-pre4.patch +Patch37: backport-CVE-2024-11053-pre5.patch +Patch38: backport-CVE-2024-11053.patch +Patch39: backport-CVE-2024-11053-post1.patch +Patch40: backport-CVE-2024-11053-post2.patch BuildRequires: automake brotli-devel coreutils gcc groff krb5-devel BuildRequires: libidn2-devel libnghttp2-devel libpsl-devel @@ -223,6 +231,12 @@ rm -rf ${RPM_BUILD_ROOT}%{_libdir}/libcurl.la %{_mandir}/man3/* %changelog +* Tue Jan 07 2025 zhouyihang - 8.4.0-14 +- Type:CVE +- CVE:CVE-2024-11053 +- SUG:NA +- DESC:fix CVE-2024-11053 + * Mon Dec 09 2024 zhouyihang - 8.4.0-13 - Type:bugfix - CVE:NA -- Gitee