Package: release.debian.org Severity: normal Tags: bullseye X-Debbugs-Cc: fos...@packages.debian.org Control: affects -1 + src:fossil User: release.debian....@packages.debian.org Usertags: pu
this bug was opened by previous arrangement with maintainer. [ Reason ] fossil is affected by a regression due to a security update of apache CVE-2024-24795. Backport was choosen because upstream does not document all commit needed for fixing the regression. [ Impact ] Fossil is broken at least server part [ Tests ] Full upstream test suite [ Risks ] Broken fossil [ Checklist ] [X] *all* changes are documented in the d/changelog [X] I reviewed all changes and I approve them [X] attach debdiff against the package in (old)stable [X] the issue is verified as fixed in unstable [ Changes ] Cherry picked and backport fix [ Other info ] None
diff -Nru fossil-2.15.2/debian/changelog fossil-2.15.2/debian/changelog --- fossil-2.15.2/debian/changelog 2021-06-15 09:55:20.000000000 +0000 +++ fossil-2.15.2/debian/changelog 2024-05-14 21:29:39.000000000 +0000 @@ -1,3 +1,13 @@ +fossil (1:2.15.2-1+deb11u1) bullseye; urgency=medium + + * Non maintainer fix with acknowlegment by maintainer. + * Cherry-pick fix f4ffefe708793b03 for CVE-2024-24795 and add + "Breaks: apache2 (<< 2.4.59-1~)" to stage fix; see + https://bz.apache.org/bugzilla/show_bug.cgi?id=68905 + (closes: #1070069) + + -- Bastien Roucari??s <ro...@debian.org> Tue, 14 May 2024 21:29:39 +0000 + fossil (1:2.15.2-1) unstable; urgency=high * New upstream version, announcement (expurgated) says: diff -Nru fossil-2.15.2/debian/control fossil-2.15.2/debian/control --- fossil-2.15.2/debian/control 2021-04-07 08:12:51.000000000 +0000 +++ fossil-2.15.2/debian/control 2024-05-14 21:29:39.000000000 +0000 @@ -22,6 +22,7 @@ Architecture: any Multi-Arch: foreign Depends: libtcl8.6 | libtcl, ${misc:Depends}, ${shlibs:Depends} +Breaks: apache2 (<< 2.4.59-1~), apache2-bin (<< 2.4.59-1~) Suggests: gnupg | gnupg2 Description: DSCM with built-in wiki, http interface and server, tickets database Fossil is an easy-to-use Distributed Source Control Management system diff -Nru fossil-2.15.2/debian/patches/0002-Deal-with-the-missing-Content-Length-field.patch fossil-2.15.2/debian/patches/0002-Deal-with-the-missing-Content-Length-field.patch --- fossil-2.15.2/debian/patches/0002-Deal-with-the-missing-Content-Length-field.patch 1970-01-01 00:00:00.000000000 +0000 +++ fossil-2.15.2/debian/patches/0002-Deal-with-the-missing-Content-Length-field.patch 2024-05-14 21:29:39.000000000 +0000 @@ -0,0 +1,361 @@ +From: =?utf-8?q?Bastien_Roucari=C3=A8s?= <ro...@debian.org> +Date: Tue, 14 May 2024 21:23:16 +0000 +Subject: Deal with the missing Content-Length field + +fix regression of CVE-2024-24795 + +bug: https://bz.apache.org/bugzilla/show_bug.cgi?id=68905 +origin: https://fossil-scm.org/home/vpatch?from=9c40ddbcd182f264&to=a8e33fb161f45b65 +--- + src/cgi.c | 43 ++++++++++++++++++++++++++++--------- + src/clone.c | 14 +++++++++++- + src/http.c | 71 +++++++++++++++++++++++++++++++++++++++++++++++++++++-------- + src/main.c | 14 ++++++++++-- + src/xfer.c | 1 + + 5 files changed, 121 insertions(+), 22 deletions(-) + +diff --git a/src/cgi.c b/src/cgi.c +index d47575b..aade0fb 100644 +--- a/src/cgi.c ++++ b/src/cgi.c +@@ -1034,7 +1034,7 @@ void cgi_trace(const char *z){ + } + + /* Forward declaration */ +-static NORETURN void malformed_request(const char *zMsg); ++static NORETURN void malformed_request(const char *zMsg, ...); + + /* + ** Initialize the query parameter database. Information is pulled from +@@ -1080,6 +1080,7 @@ void cgi_init(void){ + const char *zRequestUri = cgi_parameter("REQUEST_URI",0); + const char *zScriptName = cgi_parameter("SCRIPT_NAME",0); + const char *zPathInfo = cgi_parameter("PATH_INFO",0); ++ const char *zContentLength = 0; + #ifdef _WIN32 + const char *zServerSoftware = cgi_parameter("SERVER_SOFTWARE",0); + #endif +@@ -1186,7 +1187,15 @@ void cgi_init(void){ + g.zIpAddr = fossil_strdup(z); + } + +- len = atoi(PD("CONTENT_LENGTH", "0")); ++ zContentLength = P("CONTENT_LENGTH"); ++ if( zContentLength==0 ){ ++ len = 0; ++ if( sqlite3_stricmp(PD("REQUEST_METHOD",""),"POST")==0 ){ ++ malformed_request("missing CONTENT_LENGTH on a POST method"); ++ } ++ }else{ ++ len = atoi(zContentLength); ++ } + zType = P("CONTENT_TYPE"); + zSemi = zType ? strchr(zType, ';') : 0; + if( zSemi ){ +@@ -1593,11 +1602,22 @@ void cgi_vprintf(const char *zFormat, va_list ap){ + /* + ** Send a reply indicating that the HTTP request was malformed + */ +-static NORETURN void malformed_request(const char *zMsg){ +- cgi_set_status(501, "Not Implemented"); +- cgi_printf( +- "<html><body><p>Bad Request: %s</p></body></html>\n", zMsg +- ); ++static NORETURN void malformed_request(const char *zMsg, ...){ ++ va_list ap; ++ char *z; ++ va_start(ap, zMsg); ++ z = vmprintf(zMsg, ap); ++ va_end(ap); ++ cgi_set_status(400, "Bad Request"); ++ zContentType = "text/plain"; ++ if( g.zReqType==0 ) g.zReqType = "WWW"; ++ if( g.zReqType[0]=='C' && PD("SERVER_SOFTWARE",0)!=0 ){ ++ const char *zServer = PD("SERVER_SOFTWARE",""); ++ cgi_printf("Bad CGI Request from \"%s\": %s\n",zServer,z); ++ }else{ ++ cgi_printf("Bad %s Request: %s\n", g.zReqType, z); ++ } ++ fossil_free(z); + cgi_reply(); + fossil_exit(0); + } +@@ -1714,8 +1734,9 @@ void cgi_handle_http_request(const char *zIpAddr){ + const char *zScheme = "http"; + char zLine[2000]; /* A single line of input. */ + g.fullHttpReply = 1; ++ g.zReqType = "HTTP"; + if( fgets(zLine, sizeof(zLine),g.httpIn)==0 ){ +- malformed_request("missing HTTP header"); ++ malformed_request("missing header"); + } + blob_append(&g.httpHeader, zLine, -1); + cgi_trace(zLine); +@@ -1725,13 +1746,14 @@ void cgi_handle_http_request(const char *zIpAddr){ + } + if( fossil_strcmp(zToken,"GET")!=0 && fossil_strcmp(zToken,"POST")!=0 + && fossil_strcmp(zToken,"HEAD")!=0 ){ +- malformed_request("unsupported HTTP method"); ++ malformed_request("unsupported HTTP method: \"%s\" - Fossil only supports" ++ "GET, POST, and HEAD", zToken); + } + cgi_setenv("GATEWAY_INTERFACE","CGI/1.0"); + cgi_setenv("REQUEST_METHOD",zToken); + zToken = extract_token(z, &z); + if( zToken==0 ){ +- malformed_request("malformed URL in HTTP header"); ++ malformed_request("malformed URI in the HTTP header"); + } + cgi_setenv("REQUEST_URI", zToken); + cgi_setenv("SCRIPT_NAME", ""); +@@ -1840,6 +1862,7 @@ void cgi_handle_ssh_http_request(const char *zIpAddr){ + }else{ + fossil_panic("missing SSH IP address"); + } ++ g.zReqType = "HTTP"; + if( fgets(zLine, sizeof(zLine),g.httpIn)==0 ){ + malformed_request("missing HTTP header"); + } +diff --git a/src/clone.c b/src/clone.c +index 38c2b69..05d5b00 100644 +--- a/src/clone.c ++++ b/src/clone.c +@@ -137,6 +137,7 @@ void delete_private_content(void){ + ** -u|--unversioned Also sync unversioned content + ** -v|--verbose Show more statistics in output + ** --workdir DIR Also open a checkout in DIR ++** --xverbose Extra debugging output + ** + ** See also: [[init]], [[open]] + */ +@@ -162,6 +163,7 @@ void clone_cmd(void){ + urlFlags |= URL_REMEMBER_PW; + } + if( find_option("verbose","v",0)!=0) syncFlags |= SYNC_VERBOSE; ++ if( find_option("xverbose",0,0)!=0) syncFlags |= SYNC_XVERBOSE; + if( find_option("unversioned","u",0)!=0 ) syncFlags |= SYNC_UNVERSIONED; + zHttpAuth = find_option("httpauth","B",1); + zDefaultUser = find_option("admin-user","A",1); +@@ -262,7 +264,17 @@ void clone_cmd(void){ + db_close(1); + if( nErr ){ + file_delete(zRepo); +- fossil_fatal("server returned an error - clone aborted"); ++ if( g.fHttpTrace ){ ++ fossil_fatal( ++ "server returned an error - clone aborted\n\n%s", ++ http_last_trace_reply() ++ ); ++ }else{ ++ fossil_fatal( ++ "server returned an error - clone aborted\n" ++ "Rerun using --httptrace for more detail" ++ ); ++ } + } + db_open_repository(zRepo); + } +diff --git a/src/http.c b/src/http.c +index 2bc4984..2d1f02a 100644 +--- a/src/http.c ++++ b/src/http.c +@@ -49,6 +49,11 @@ + /* Keep track of HTTP Basic Authorization failures */ + static int fSeenHttpAuth = 0; + ++/* The N value for most recent http-request-N.txt and http-reply-N.txt ++** trace files. ++*/ ++static int traceCnt = 0; ++ + /* + ** Construct the "login" card with the client credentials. + ** +@@ -208,6 +213,25 @@ char *prompt_for_httpauth_creds(void){ + return zHttpAuth; + } + ++/* ++** Return the complete text of the last HTTP reply as saved in the ++** http-reply-N.txt file. This only works if run using --httptrace. ++** Without the --httptrace option, this routine returns a NULL pointer. ++** It still might return a NULL pointer if for some reason it cannot ++** find and open the last http-reply-N.txt file. ++*/ ++char *http_last_trace_reply(void){ ++ Blob x; ++ int n; ++ char *zFilename; ++ if( g.fHttpTrace==0 ) return 0; ++ zFilename = mprintf("http-reply-%d.txt", traceCnt); ++ n = blob_read_from_file(&x, zFilename, ExtFILE); ++ fossil_free(zFilename); ++ if( n<=0 ) return 0; ++ return blob_str(&x); ++} ++ + /* + ** Sign the content in pSend, compress it, and send it to the server + ** via HTTP or HTTPS. Get a reply, uncompress the reply, and store the reply +@@ -230,7 +254,6 @@ int http_exchange( + Blob hdr; /* The HTTP request header */ + int closeConnection; /* True to close the connection when done */ + int iLength; /* Expected length of the reply payload */ +- int iRecvLen; /* Received length of the reply payload */ + int rc = 0; /* Result code */ + int iHttpVersion; /* Which version of HTTP protocol server uses */ + char *zLine; /* A single line of the reply header */ +@@ -268,7 +291,6 @@ int http_exchange( + ** ./fossil test-http <http-request-1.txt + */ + if( g.fHttpTrace ){ +- static int traceCnt = 0; + char *zOutFile; + FILE *out; + traceCnt++; +@@ -305,6 +327,7 @@ int http_exchange( + */ + closeConnection = 1; + iLength = -1; ++ iHttpVersion = -1; + while( (zLine = transport_receive_line(&g.url))!=0 && zLine[0]!=0 ){ + if( mHttpFlags & HTTP_VERBOSE ){ + fossil_print("Read: [%s]\n", zLine); +@@ -343,6 +366,7 @@ int http_exchange( + fossil_warning("server says: %s", &zLine[ii]); + goto write_err; + } ++ if( iHttpVersion<0 ) iHttpVersion = 1; + closeConnection = 0; + }else if( fossil_strnicmp(zLine, "content-length:", 15)==0 ){ + for(i=15; fossil_isspace(zLine[i]); i++){} +@@ -416,7 +440,12 @@ int http_exchange( + } + } + } +- if( iLength<0 ){ ++ if( iHttpVersion<0 ){ ++ /* We got nothing back from the server. If using the ssh: protocol, ++ ** this might mean we need to add or remove the PATH=... argument ++ ** to the SSH command being sent. If that is the case, retry the ++ ** request after adding or removing the PATH= argument. ++ */ + fossil_warning("server did not reply"); + goto write_err; + } +@@ -429,13 +458,37 @@ int http_exchange( + ** Extract the reply payload that follows the header + */ + blob_zero(pReply); +- blob_resize(pReply, iLength); +- iRecvLen = transport_receive(&g.url, blob_buffer(pReply), iLength); +- if( iRecvLen != iLength ){ +- fossil_warning("response truncated: got %d bytes of %d", iRecvLen, iLength); +- goto write_err; ++ if( iLength==0 ){ ++ /* No content to read */ ++ }else if( iLength>0 ){ ++ /* Read content of a known length */ ++ int iRecvLen; /* Received length of the reply payload */ ++ blob_resize(pReply, iLength); ++ iRecvLen = transport_receive(&g.url, blob_buffer(pReply), iLength); ++ if( mHttpFlags & HTTP_VERBOSE ){ ++ fossil_print("Reply received: %d of %d bytes\n", iRecvLen, iLength); ++ } ++ if( iRecvLen != iLength ){ ++ fossil_warning("response truncated: got %d bytes of %d", ++ iRecvLen, iLength); ++ goto write_err; ++ } ++ }else{ ++ /* Read content until end-of-file */ ++ int iRecvLen; /* Received length of the reply payload */ ++ unsigned int nReq = 1000; ++ unsigned int nPrior = 0; ++ do{ ++ nReq *= 2; ++ blob_resize(pReply, nPrior+nReq); ++ iRecvLen = transport_receive(&g.url, &pReply->aData[nPrior], (int)nReq); ++ nPrior += iRecvLen; ++ pReply->nUsed = nPrior; ++ }while( iRecvLen==nReq && nReq<0x20000000 ); ++ if( mHttpFlags & HTTP_VERBOSE ){ ++ fossil_print("Reply received: %u bytes (w/o content-length)\n", nPrior); ++ } + } +- blob_resize(pReply, iLength); + if( isError ){ + char *z; + int i, j; +diff --git a/src/main.c b/src/main.c +index 5f5e277..2effa14 100644 +--- a/src/main.c ++++ b/src/main.c +@@ -216,7 +216,9 @@ struct Global { + const char *zMainMenuFile; /* --mainmenu FILE from server/ui/cgi */ + const char *zSSLIdentity; /* Value of --ssl-identity option, filename of + ** SSL client identity */ +-#if defined(_WIN32) && USE_SEE ++ ++ const char *zReqType; /* Type of request: "HTTP", "CGI", "SCGI" */ ++#if USE_SEE + const char *zPidKey; /* Saved value of the --usepidkey option. Only + * applicable when using SEE on Windows. */ + #endif +@@ -2218,6 +2220,7 @@ void cmd_cgi(void){ + fossil_binary_mode(g.httpOut); + fossil_binary_mode(g.httpIn); + g.cgiOutput = 1; ++ g.zReqType = "CGI"; + fossil_set_timeout(FOSSIL_DEFAULT_TIMEOUT); + /* Find the name of the CGI control file */ + if( g.argc==3 && fossil_strcmp(g.argv[1],"cgi")==0 ){ +@@ -2653,6 +2656,7 @@ void cmd_http(void){ + g.fNoHttpCompress = find_option("nocompress",0,0)!=0; + g.zExtRoot = find_option("extroot",0,1); + g.zCkoutAlias = find_option("ckout-alias",0,1); ++ g.zReqType = "HTTP"; + zInFile = find_option("in",0,1); + if( zInFile ){ + backoffice_disable(); +@@ -2670,6 +2674,7 @@ void cmd_http(void){ + } + zIpAddr = find_option("ipaddr",0,1); + useSCGI = find_option("scgi", 0, 0)!=0; ++ if( useSCGI ) g.zReqType = "SCGI"; + zAltBase = find_option("baseurl", 0, 1); + if( find_option("nodelay",0,0)!=0 ) backoffice_no_delay(); + if( zAltBase ) set_base_url(zAltBase); +@@ -2750,6 +2755,7 @@ void cmd_test_http(void){ + fossil_binary_mode(g.httpIn); + g.zExtRoot = find_option("extroot",0,1); + find_server_repository(2, 0); ++ g.zReqType = "HTTP"; + g.cgiOutput = 1; + g.fNoHttpCompress = 1; + g.fullHttpReply = 1; +@@ -2930,7 +2936,11 @@ void cmd_webserver(void){ + if( find_option("nocompress",0,0)!=0 ) g.fNoHttpCompress = 1; + zAltBase = find_option("baseurl", 0, 1); + fCreate = find_option("create",0,0)!=0; +- if( find_option("scgi", 0, 0)!=0 ) flags |= HTTP_SERVER_SCGI; ++ g.zReqType = "HTTP"; ++ if( find_option("scgi", 0, 0)!=0 ){ ++ g.zReqType = "SCGI"; ++ flags |= HTTP_SERVER_SCGI; ++ } + if( zAltBase ){ + set_base_url(zAltBase); + } +diff --git a/src/xfer.c b/src/xfer.c +index f1e5399..7f9153d 100644 +--- a/src/xfer.c ++++ b/src/xfer.c +@@ -1822,6 +1822,7 @@ static const char zBriefFormat[] = + #define SYNC_UV_DRYRUN 0x0400 /* Do not actually exchange files */ + #define SYNC_IFABLE 0x0800 /* Inability to sync is not fatal */ + #define SYNC_CKIN_LOCK 0x1000 /* Lock the current check-in */ ++#define SYNC_XVERBOSE 0x20000 /* Extra verbose. Network traffic */ + #endif + + /* diff -Nru fossil-2.15.2/debian/patches/series fossil-2.15.2/debian/patches/series --- fossil-2.15.2/debian/patches/series 2021-06-15 09:55:20.000000000 +0000 +++ fossil-2.15.2/debian/patches/series 2024-05-14 21:29:39.000000000 +0000 @@ -1 +1,2 @@ debian-changes +0002-Deal-with-the-missing-Content-Length-field.patch
signature.asc
Description: This is a digitally signed message part.