--- Begin Message ---
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.
--- End Message ---