File hg-CVE-2025-2361.patch of Package mercurial.38124

exporting patch:
# HG changeset patch
# User Raphaël Gomès <rgomes@octobus.net>
# Date 1742340720 -3600
#      Wed Mar 19 00:32:00 2025 +0100
# Branch stable
# Node ID a5c72ed2929341d97b11968211c880854803f003
# Parent  74439d1cbebaa9ff8f8300e37e93b42e6d381be4
hgweb: fix XSS vulnerability in hgweb (CVE-2025-2361)

818598f5bc8b91 is the change that introduced the vulnerability (in 2006!)
that was disclosed to us, but I found a similar pattern in other places
in the code.

Since XSS escaping is actually hard and that would mean vendoring some
better sanitation tool, I decided to simply remove user input from any
HTML output in hgweb, hopefully in all places.

---
 mercurial/hgweb/hgweb_mod.py   |    5 ++++-
 mercurial/hgweb/webcommands.py |   21 ++++++++++++++-------
 tests/test-archive.t           |   26 +++++++++++++-------------
 tests/test-hgweb.t             |   10 +++++-----
 4 files changed, 36 insertions(+), 26 deletions(-)

Index: mercurial-4.5.2/mercurial/hgweb/hgweb_mod.py
===================================================================
--- mercurial-4.5.2.orig/mercurial/hgweb/hgweb_mod.py	2018-03-06 20:19:51.000000000 +0100
+++ mercurial-4.5.2/mercurial/hgweb/hgweb_mod.py	2025-03-20 19:43:52.534289516 +0100
@@ -437,7 +437,10 @@
             if rctx.configbool('web', 'cache') and not rctx.nonce:
                 caching(self, req) # sets ETag header or raises NOT_MODIFIED
             if cmd not in webcommands.__all__:
-                msg = 'no such method: %s' % cmd
+                msg = 'method not found'
+                # /!\ Do not print `cmd` here unless you do *extensive*
+                # escaping.
+                # Because XSS escaping is hard, we just don't risk it.
                 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
             elif cmd == 'file' and r'raw' in req.form.get(r'style', []):
                 rctx.ctype = ctype
Index: mercurial-4.5.2/mercurial/hgweb/webcommands.py
===================================================================
--- mercurial-4.5.2.orig/mercurial/hgweb/webcommands.py	2018-03-06 20:19:51.000000000 +0100
+++ mercurial-4.5.2/mercurial/hgweb/webcommands.py	2025-03-20 19:46:05.728244255 +0100
@@ -522,7 +522,9 @@
             h[None] = None # denotes files present
 
     if mf and not files and not dirs:
-        raise ErrorResponse(HTTP_NOT_FOUND, 'path not found: ' + path)
+        # /!\ Do not print `path` here unless you do *extensive* escaping.
+        # Because XSS escaping is hard, we just don't risk it.
+        raise ErrorResponse(HTTP_NOT_FOUND, b'path not found')
 
     def filelist(**map):
         for f in sorted(files):
@@ -1108,12 +1110,15 @@
     key = req.form['node'][0]
 
     if type_ not in web.archivespecs:
-        msg = 'Unsupported archive type: %s' % type_
+        # /!\ Do not print `type_` here unless you do *extensive* escaping.
+        # Because XSS escaping is hard, we just don't risk it.
+        msg = b'Unsupported archive type'
         raise ErrorResponse(HTTP_NOT_FOUND, msg)
 
-    if not ((type_ in allowed or
-             web.configbool("web", "allow" + type_))):
-        msg = 'Archive type not allowed: %s' % type_
+    if not (type_ in allowed or web.configbool(b"web", b"allow" + type_)):
+        # /!\ Do not print `type_` here unless you do *extensive* escaping.
+        # Because XSS escaping is hard, we just don't risk it.
+        msg = b'Archive type not allowed'
         raise ErrorResponse(HTTP_FORBIDDEN, msg)
 
     reponame = re.sub(r"\W+", "-", os.path.basename(web.reponame))
@@ -1133,8 +1138,10 @@
         if pats:
             files = [f for f in ctx.manifest().keys() if match(f)]
             if not files:
-                raise ErrorResponse(HTTP_NOT_FOUND,
-                    'file(s) not found: %s' % file[0])
+                # /!\ Do not print `files` here unless you do *extensive*
+                # escaping.
+                # Because XSS escaping is hard, we just don't risk it.
+                raise ErrorResponse(HTTP_NOT_FOUND, b'file(s) not found')
 
     mimetype, artype, extension, encoding = web.archivespecs[type_]
     headers = [
Index: mercurial-4.5.2/tests/test-archive.t
===================================================================
--- mercurial-4.5.2.orig/tests/test-archive.t	2018-03-06 20:19:51.000000000 +0100
+++ mercurial-4.5.2/tests/test-archive.t	2025-03-20 19:42:22.316324005 +0100
@@ -122,20 +122,20 @@
   % gz allowed should give 200
   200 Script output follows
   % tar.bz2 and zip disallowed should both give 403
-  403 Archive type not allowed: bz2
-  403 Archive type not allowed: zip
+  403 Archive type not allowed
+  403 Archive type not allowed
   $ test_archtype bz2 tar.bz2 zip tar.gz
   % bz2 allowed should give 200
   200 Script output follows
   % zip and tar.gz disallowed should both give 403
-  403 Archive type not allowed: zip
-  403 Archive type not allowed: gz
+  403 Archive type not allowed
+  403 Archive type not allowed
   $ test_archtype zip zip tar.gz tar.bz2
   % zip allowed should give 200
   200 Script output follows
   % tar.gz and tar.bz2 disallowed should both give 403
-  403 Archive type not allowed: gz
-  403 Archive type not allowed: bz2
+  403 Archive type not allowed
+  403 Archive type not allowed
 
 check http return codes (with deprecated option)
 
@@ -143,20 +143,20 @@
   % gz allowed should give 200
   200 Script output follows
   % tar.bz2 and zip disallowed should both give 403
-  403 Archive type not allowed: bz2
-  403 Archive type not allowed: zip
+  403 Archive type not allowed
+  403 Archive type not allowed
   $ test_archtype_deprecated bz2 tar.bz2 zip tar.gz
   % bz2 allowed should give 200
   200 Script output follows
   % zip and tar.gz disallowed should both give 403
-  403 Archive type not allowed: zip
-  403 Archive type not allowed: gz
+  403 Archive type not allowed
+  403 Archive type not allowed
   $ test_archtype_deprecated zip zip tar.gz tar.bz2
   % zip allowed should give 200
   200 Script output follows
   % tar.gz and tar.bz2 disallowed should both give 403
-  403 Archive type not allowed: gz
-  403 Archive type not allowed: bz2
+  403 Archive type not allowed
+  403 Archive type not allowed
 
   $ echo "allow_archive = gz bz2 zip" >> .hg/hgrc
   $ hg serve -p $HGPORT -d --pid-file=hg.pid -E errors.log
@@ -172,7 +172,7 @@
 invalid arch type should give 404
 
   $ get-with-headers.py localhost:$HGPORT "archive/tip.invalid" | head -n 1
-  404 Unsupported archive type: None
+  404 Unsupported archive type
 
   $ TIP=`hg id -v | cut -f1 -d' '`
   $ QTIP=`hg id -q`
Index: mercurial-4.5.2/tests/test-hgweb.t
===================================================================
--- mercurial-4.5.2.orig/tests/test-hgweb.t	2018-03-06 20:19:51.000000000 +0100
+++ mercurial-4.5.2/tests/test-hgweb.t	2025-03-20 19:39:27.623009421 +0100
@@ -122,25 +122,25 @@
   400* (glob)
   
   
-  error: no such method: spam
+  error: method not found
   [1]
 
   $ get-with-headers.py --headeronly localhost:$HGPORT '?cmd=spam'
-  400 no such method: spam
+  400 method not found
   [1]
 
 should give a 400 - bad command as a part of url path (issue4071)
 
   $ get-with-headers.py --headeronly localhost:$HGPORT 'spam'
-  400 no such method: spam
+  400 method not found
   [1]
 
   $ get-with-headers.py --headeronly localhost:$HGPORT 'raw-spam'
-  400 no such method: spam
+  400 method not found
   [1]
 
   $ get-with-headers.py --headeronly localhost:$HGPORT 'spam/tip/foo'
-  400 no such method: spam
+  400 method not found
   [1]
 
 should give a 404 - file does not exist
openSUSE Build Service is sponsored by