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

Attachment: signature.asc
Description: This is a digitally signed message part.

Reply via email to