Control: tags -1 + patch

Hi,

Attached is a debdiff against wget 1.25.0-3 (as an NMU-style 1.25.0-3.1)
that fixes CVE-2026-58470, CVE-2026-58471 and CVE-2026-58472 by backporting
the upstream commits. Some notes that may save time:

1. CVE-2026-58472: the commit referenced in the CVE (dd692d9c) is not
sufficient on its own. It introduced a new heap buffer overflow in
html_quote_string(): the per-character size counts lost the "+1" that the
old loop header provided, so the allocation is too small for any string
containing &, <, >, " or a space. Upstream fixed this in f76978a5
("Regression: Fix buffer overflow in html_quote_string()"), which also adds
unit tests. With dd692d9c alone, the new test_html_quote_string unit test
aborts under ASan:
AddressSanitizer: heap-buffer-overflow src/convert.c:1253 in
html_quote_string
The attached patch includes both commits.

2. CVE-2026-58470: includes the upstream follow-ups 01ff771c (strtol ->
strtoll) and a6d20d8f (regression test in test_parse_range_header).

3. CVE-2026-58471: upstream c2640fe5, applies as-is.

4. CVE-2026-58469: I believe Debian is not affected. The bug is in
src/metalink.c, and Debian builds wget without metalink support (no
libmetalink-dev in Build-Depends; "wget --version" shows -metalink), so the
code is not compiled. If you agree, the tracker could mark it
<not-affected> (built without metalink support). Note for anyone who
enables metalink later: 37a40fcb itself has an inverted trailing-whitespace
check, fixed upstream in 7b1cdecc, and needs 82d945ff (#include <ctype.h>).

Testing (sid chroot, amd64):
- dpkg-buildpackage: full build passes; the test suite reports 0 FAIL / 0
ERROR across all groups, and unit-tests passes (including the new range and
html_quote_string tests).
- ASan/UBSan builds of unpatched 1.25.0-3 vs patched, against a local HTTP
server:
* CVE-2026-58470: 206 response with "Content-Range: bytes
0-9/99999999999999999999". Unpatched: "runtime error: signed integer
overflow". Patched: header rejected, no error.
* CVE-2026-58471: wget --remote-encoding=ISO-8859-1
--local-encoding=UTF-32LE
https://www.google.com/url?q=http://127.0.0.1/&source=gmail&ust=1793490607448000&sa=E<40
x 'A'>. Unpatched: "AddressSanitizer: heap-buffer-overflow" in
convert_fname. Patched: clean.
* CVE-2026-58472: patched build passes all 21 unit tests under ASan. The
dd692d9c-only build overflows as described above.
- lintian shows no new tags (the custom-library-search-path RUNPATH error
already exists in 1.25.0-3).

Thanks for maintaining wget.

Regards
diff -Nru wget-1.25.0/debian/changelog wget-1.25.0/debian/changelog
--- wget-1.25.0/debian/changelog	2026-07-24 13:53:14.000000000 +0000
+++ wget-1.25.0/debian/changelog	2026-10-30 23:40:03.000000000 +0000
@@ -1,3 +1,17 @@
+wget (1.25.0-3.1) unstable; urgency=medium
+
+  * Non-maintainer upload.
+  * Backport upstream fixes for security issues:
+    - CVE-2026-58470: integer overflow in Content-Range parsing
+      (upstream 43d3ba93, follow-up 01ff771c, regression test a6d20d8f).
+    - CVE-2026-58471: heap buffer overflow in convert_fname()
+      (upstream c2640fe5).
+    - CVE-2026-58472: integer and heap buffer overflow in html_quote_string()
+      (upstream dd692d9c, plus f76978a5 which fixes an overflow introduced
+      by dd692d9c).
+
+ -- automationcanadian <[email protected]>  Fri, 30 Oct 2026 23:40:03 +0000
+
 wget (1.25.0-3) unstable; urgency=medium
 
   * patch from upstream git to fix CVE-2026-15146 a problem with IP validation in FTP PASV. closes: Bug#1142284
diff -Nru wget-1.25.0/debian/patches/CVE-2026-58470 wget-1.25.0/debian/patches/CVE-2026-58470
--- wget-1.25.0/debian/patches/CVE-2026-58470	1970-01-01 00:00:00.000000000 +0000
+++ wget-1.25.0/debian/patches/CVE-2026-58470	2026-10-30 23:39:36.000000000 +0000
@@ -0,0 +1,121 @@
+Description: Fix CVE-2026-58470
+ Backport of upstream commits 43d3ba9336bc94937e6fae2365c6ffd30c34ffcf 01ff771c a6d20d8f.
+Origin: upstream, https://gitlab.com/gnuwget/wget/-/commit/43d3ba9336bc94937e6fae2365c6ffd30c34ffcf https://gitlab.com/gnuwget/wget/-/commit/01ff771c https://gitlab.com/gnuwget/wget/-/commit/a6d20d8f 
+Bug-Debian-Security: https://security-tracker.debian.org/tracker/CVE-2026-58470
+Forwarded: not-needed
+Last-Update: 2026-10-30
+---
+diff --git a/src/http.c b/src/http.c
+index 61d83df1..f447c7f7 100644
+--- a/src/http.c
++++ b/src/http.c
+@@ -914,6 +914,7 @@ parse_content_range (const char *hdr, wgint *first_byte_ptr,
+                      wgint *last_byte_ptr, wgint *entity_length_ptr)
+ {
+   wgint num;
++  char *end;
+ 
+   /* Ancient versions of Netscape proxy server, presumably predating
+      rfc2068, sent out `Content-Range' without the "bytes"
+@@ -932,27 +933,39 @@ parse_content_range (const char *hdr, wgint *first_byte_ptr,
+     }
+   if (!c_isdigit (*hdr))
+     return false;
+-  for (num = 0; c_isdigit (*hdr); hdr++)
+-    num = 10 * num + (*hdr - '0');
+-  if (*hdr != '-' || !c_isdigit (*(hdr + 1)))
++
++  errno = 0;
++  num = strtol(hdr, &end, 10);
++  if (errno == ERANGE)
++    return false;
++  hdr = end;
++
++  if (*hdr++ != '-' || !c_isdigit (*hdr))
+     return false;
+   *first_byte_ptr = num;
+-  ++hdr;
+-  for (num = 0; c_isdigit (*hdr); hdr++)
+-    num = 10 * num + (*hdr - '0');
+-  if (*hdr != '/')
++
++  errno = 0;
++  num = strtol(hdr, &end, 10);
++  if (errno == ERANGE)
++    return false;
++  hdr = end;
++
++  if (*hdr++ != '/')
+     return false;
+   *last_byte_ptr = num;
+-  if (!(c_isdigit (*(hdr + 1)) || *(hdr + 1) == '*'))
++  if (!(c_isdigit (*hdr) || *hdr == '*'))
+     return false;
+   if (*last_byte_ptr < *first_byte_ptr)
+     return false;
+-  ++hdr;
+   if (*hdr == '*')
+     num = -1;
+   else
+-    for (num = 0; c_isdigit (*hdr); hdr++)
+-      num = 10 * num + (*hdr - '0');
++    {
++      errno = 0;
++      num = strtol(hdr, NULL, 10);
++      if (errno == ERANGE)
++        return false;
++    }
+   *entity_length_ptr = num;
+   if ((*entity_length_ptr <= *last_byte_ptr) && *entity_length_ptr != -1)
+     return false;
+diff --git a/src/http.c b/src/http.c
+index e5e75ee6..8170a43c 100644
+--- a/src/http.c
++++ b/src/http.c
+@@ -949,7 +949,7 @@ parse_content_range (const char *hdr, wgint *first_byte_ptr,
+     return false;
+ 
+   errno = 0;
+-  num = strtol(hdr, &end, 10);
++  num = strtoll(hdr, &end, 10);
+   if (errno == ERANGE)
+     return false;
+   hdr = end;
+@@ -959,7 +959,7 @@ parse_content_range (const char *hdr, wgint *first_byte_ptr,
+   *first_byte_ptr = num;
+ 
+   errno = 0;
+-  num = strtol(hdr, &end, 10);
++  num = strtoll(hdr, &end, 10);
+   if (errno == ERANGE)
+     return false;
+   hdr = end;
+@@ -976,7 +976,7 @@ parse_content_range (const char *hdr, wgint *first_byte_ptr,
+   else
+     {
+       errno = 0;
+-      num = strtol(hdr, NULL, 10);
++      num = strtoll(hdr, NULL, 10);
+       if (errno == ERANGE)
+         return false;
+     }
+diff --git a/src/http.c b/src/http.c
+index 8170a43c..36d01d90 100644
+--- a/src/http.c
++++ b/src/http.c
+@@ -5467,6 +5467,15 @@ test_parse_range_header (void)
+       { "bytes 42-1233/*", 42, 1233, -1, true },
+       { "bytes 0-2147483648/2147483649", 0, 2147483648U, 2147483649U, true },
+       { "bytes 2147483648-4294967296/4294967297", 2147483648U, 4294967296ULL, 4294967297ULL, true },
++      /* An entity-length that overflows wgint must be rejected rather
++         than silently wrapped: parse_content_range() parses it with
++         strtoll() and returns false on ERANGE.  Before that fix the
++         value "99999999999999999999" wrapped to a bogus positive
++         number and the function returned true (CVE-2026-58470).  The
++         expected first/last/length match the values the function leaves
++         in the output pointers, which this harness reuses across
++         iterations without resetting. */
++      { "bytes 0-1000/99999999999999999999", 0, 1000, 4294967297ULL, false },
+   };
+ 
+   wgint firstbyteptr[sizeof(wgint)];
diff -Nru wget-1.25.0/debian/patches/CVE-2026-58471 wget-1.25.0/debian/patches/CVE-2026-58471
--- wget-1.25.0/debian/patches/CVE-2026-58471	1970-01-01 00:00:00.000000000 +0000
+++ wget-1.25.0/debian/patches/CVE-2026-58471	2026-10-30 23:39:47.000000000 +0000
@@ -0,0 +1,59 @@
+Description: Fix CVE-2026-58471
+ Backport of upstream commits c2640fe5171c59f87c58dc9fcb195b2d18b010ee.
+Origin: upstream, https://gitlab.com/gnuwget/wget/-/commit/c2640fe5171c59f87c58dc9fcb195b2d18b010ee 
+Bug-Debian-Security: https://security-tracker.debian.org/tracker/CVE-2026-58471
+Forwarded: not-needed
+Last-Update: 2026-10-30
+---
+diff --git a/src/url.c b/src/url.c
+index 7540e90f..f334456c 100644
+--- a/src/url.c
++++ b/src/url.c
+@@ -1614,7 +1614,7 @@ convert_fname (char *fname)
+   const char *from_encoding = opt.encoding_remote;
+   const char *to_encoding = opt.locale;
+   iconv_t cd;
+-  size_t len, done, inlen, outlen;
++  size_t len, inlen, outlen;
+   char *s;
+   const char *orig_fname;
+ 
+@@ -1636,7 +1636,6 @@ convert_fname (char *fname)
+   inlen = strlen (fname);
+   len = outlen = inlen * 2;
+   converted_fname = s = xmalloc (outlen + 1);
+-  done = 0;
+ 
+   for (;;)
+     {
+@@ -1644,7 +1643,7 @@ convert_fname (char *fname)
+       if (iconv (cd, (ICONV_CONST char **) &fname, &inlen, &s, &outlen) == 0
+           && iconv (cd, NULL, NULL, &s, &outlen) == 0)
+         {
+-          *(converted_fname + len - outlen - done) = '\0';
++          *s = '\0';
+           iconv_close (cd);
+           DEBUGP (("Converted file name '%s' (%s) -> '%s' (%s)\n",
+                    orig_fname, from_encoding, converted_fname, to_encoding));
+@@ -1667,10 +1666,17 @@ convert_fname (char *fname)
+         }
+       else if (errno == E2BIG) /* Output buffer full */
+         {
+-          done = len;
+-          len = outlen = done + inlen * 2;
+-          converted_fname = xrealloc (converted_fname, outlen + 1);
+-          s = converted_fname + done;
++          size_t used = s - converted_fname;
++          size_t newlen = used + inlen * 2 + 1;
++
++          /* Ensure we actually grow the buffer */
++          if (newlen <= len)
++            newlen = len * 2;
++
++          converted_fname = xrealloc (converted_fname, newlen + 1);
++          len = newlen;
++          s = converted_fname + used;
++          outlen = len - used;
+         }
+       else /* Weird, we got an unspecified error */
+         {
diff -Nru wget-1.25.0/debian/patches/CVE-2026-58472 wget-1.25.0/debian/patches/CVE-2026-58472
--- wget-1.25.0/debian/patches/CVE-2026-58472	1970-01-01 00:00:00.000000000 +0000
+++ wget-1.25.0/debian/patches/CVE-2026-58472	2026-10-30 23:39:47.000000000 +0000
@@ -0,0 +1,270 @@
+Description: Fix CVE-2026-58472
+ Backport of upstream commits dd692d9cea5335b181d877ae917fe6e75587a812 f76978a5.
+Origin: upstream, https://gitlab.com/gnuwget/wget/-/commit/dd692d9cea5335b181d877ae917fe6e75587a812 https://gitlab.com/gnuwget/wget/-/commit/f76978a5 
+Bug-Debian-Security: https://security-tracker.debian.org/tracker/CVE-2026-58472
+Forwarded: not-needed
+Last-Update: 2026-10-30
+---
+diff --git a/src/convert.c b/src/convert.c
+index feb90a35..3baec850 100644
+--- a/src/convert.c
++++ b/src/convert.c
+@@ -36,6 +36,7 @@ as that of the covered work.  */
+ #include <unistd.h>
+ #include <errno.h>
+ #include <assert.h>
++#include <intprops.h>
+ #include "convert.h"
+ #include "url.h"
+ #include "recur.h"
+@@ -1178,21 +1179,37 @@ html_quote_string (const char *s)
+ {
+   const char *b = s;
+   char *p, *res;
+-  int i;
++  size_t i;
++  int ok;
+ 
+   /* Pass through the string, and count the new size.  */
+-  for (i = 0; *s; s++, i++)
++  for (i = 0; *s; s++)
+     {
+       if (*s == '&')
+-        i += 4;                 /* `amp;' */
++        ok = INT_ADD_OK (i, 4, &i);     /* `amp;' */
+       else if (*s == '<' || *s == '>')
+-        i += 3;                 /* `lt;' and `gt;' */
++        ok = INT_ADD_OK (i, 3, &i);     /* `lt;' and `gt;' */
+       else if (*s == '\"')
+-        i += 5;                 /* `quot;' */
++        ok = INT_ADD_OK (i, 5, &i);     /* `quot;' */
+       else if (*s == ' ')
+-        i += 4;                 /* #32; */
++        ok = INT_ADD_OK (i, 4, &i);     /* #32; */
++      else
++        ok = INT_ADD_OK (i, 1, &i);
++
++      if (!ok)
++        {
++          DEBUGP (("Overflow detected in html_quote_string().\n"));
++          abort();
++        }
+     }
+-  res = xmalloc (i + 1);
++
++  if (!INT_ADD_OK (i, 1, &i))
++    {
++      DEBUGP (("Overflow detected in html_quote_string().\n"));
++      abort();
++    }
++
++  res = xmalloc (i);
+   s = b;
+   for (p = res; *s; s++)
+     {
+diff --git a/src/convert.c b/src/convert.c
+index 3baec850..482676d9 100644
+--- a/src/convert.c
++++ b/src/convert.c
+@@ -48,6 +48,9 @@ as that of the covered work.  */
+ #include "css-url.h"
+ #include "iri.h"
+ #include "xstrndup.h"
++#ifdef TESTING
++#include "../tests/unit-tests.h"
++#endif
+ 
+ static struct hash_table *dl_file_url_map;
+ struct hash_table *dl_url_file_map;
+@@ -1186,13 +1189,13 @@ html_quote_string (const char *s)
+   for (i = 0; *s; s++)
+     {
+       if (*s == '&')
+-        ok = INT_ADD_OK (i, 4, &i);     /* `amp;' */
++        ok = INT_ADD_OK (i, 4 + 1, &i);     /* `amp;' */
+       else if (*s == '<' || *s == '>')
+-        ok = INT_ADD_OK (i, 3, &i);     /* `lt;' and `gt;' */
++        ok = INT_ADD_OK (i, 3 + 1, &i);     /* `lt;' and `gt;' */
+       else if (*s == '\"')
+-        ok = INT_ADD_OK (i, 5, &i);     /* `quot;' */
++        ok = INT_ADD_OK (i, 5 + 1, &i);     /* `quot;' */
+       else if (*s == ' ')
+-        ok = INT_ADD_OK (i, 4, &i);     /* #32; */
++        ok = INT_ADD_OK (i, 4 + 1, &i);     /* #32; */
+       else
+         ok = INT_ADD_OK (i, 1, &i);
+ 
+@@ -1251,6 +1254,143 @@ html_quote_string (const char *s)
+   return res;
+ }
+ 
++#ifdef TESTING
++
++const char *
++test_construct_relative (void)
++{
++  static const struct {
++    const char *basefile;
++    const char *linkfile;
++    const char *expected;
++  } test_array[] = {
++    { "foo", "bar", "bar" },
++    { "A/foo", "A/bar", "bar" },
++    { "A/foo", "A/B/bar", "B/bar" },
++    { "A/X/foo", "A/Y/bar", "../Y/bar" },
++    { "X/", "Y/bar", "../Y/bar" },
++    { "/foo", "/bar", "bar" },
++    { "/a/b/c", "/a/b/d", "d" },
++    { "/a/b/c", "/a/b/c/d", "c/d" },
++    { "/a/b/c", "/a/b/c/d/e", "c/d/e" },
++    { "/a/b/c", "/x/y/z", "../../x/y/z" },
++    { "a/b", "c/d", "../c/d" },
++    { "./foo", "./bar", "bar" },
++  };
++
++  for (unsigned i = 0; i < countof (test_array); ++i)
++    {
++      char *result = construct_relative (test_array[i].basefile,
++                                         test_array[i].linkfile);
++      mu_assert ("test_construct_relative: wrong result",
++                 strcmp (result, test_array[i].expected) == 0);
++      xfree (result);
++    }
++
++  return NULL;
++}
++
++const char *
++test_match_except_index (void)
++{
++  static const struct {
++    const char *s1;
++    const char *s2;
++    bool expected;
++  } test_array[] = {
++    { "foo/index.html", "foo/", true },
++    { "foo/", "foo/index.html", true },
++    { "foo", "foo/index.html", true },
++    { "foo", "foo/", true },
++    { "foo", "foo", true },
++    { "/foo/index.html", "/foo/", true },
++    { "/foo/", "/foo/index.html", true },
++    { "/foo", "/foo/index.html", true },
++    { "/foo", "/foo/", true },
++    { "foo/bar", "foo/qux", false },
++    { "foo/bar", "bar/foo", false },
++  };
++
++  for (unsigned i = 0; i < countof (test_array); ++i)
++    {
++      bool result = match_except_index (test_array[i].s1, test_array[i].s2);
++      mu_assert ("test_match_except_index: wrong result",
++                 result == test_array[i].expected);
++    }
++
++  return NULL;
++}
++
++const char *
++test_find_fragment (void)
++{
++  static const struct {
++    const char *input;
++    int size;
++    bool has_fragment;
++    const char *fragment;
++  } test_array[] = {
++    { "http://example.com#section";, 26, true, "#section" },
++    { "http://example.com";, 18, false, NULL },
++    { "http://example.com?a=1#frag";, 24, true, "#frag" },
++    { "http://example.com?a=1%26#frag";, 28, true, "#frag" },
++    { "http://example.com?a=1&b=2#frag";, 30, true, "#frag" },
++    { "a#b", 3, true, "#b" },
++    { "a", 1, false, NULL },
++  };
++  const char *bp, *ep;
++
++  for (unsigned i = 0; i < countof (test_array); ++i)
++    {
++      bool result = find_fragment (test_array[i].input,
++                                   test_array[i].size, &bp, &ep);
++      mu_assert ("test_find_fragment: wrong result",
++                 result == test_array[i].has_fragment);
++      if (test_array[i].has_fragment)
++        {
++          mu_assert ("test_find_fragment: wrong fragment", bp != NULL);
++          mu_assert ("test_find_fragment: fragment mismatch",
++                     strncmp (bp, test_array[i].fragment,
++                              strlen (test_array[i].fragment)) == 0 &&
++                     ep == test_array[i].input + test_array[i].size);
++        }
++    }
++
++  return NULL;
++}
++
++const char *
++test_html_quote_string (void)
++{
++  static const struct {
++    const char *input;
++    const char *expected;
++  } test_array[] = {
++    { "hello", "hello" },
++    { "a&b", "a&amp;b" },
++    { "<tag>", "&lt;tag&gt;" },
++    { "\"quote\"", "&quot;quote&quot;" },
++    { "space here", "space&#32;here" },
++    { "&<>\" ", "&amp;&lt;&gt;&quot;&#32;" },
++    { "no special", "no&#32;special" },
++    { "&&&&", "&amp;&amp;&amp;&amp;" },
++    { "<<>>", "&lt;&lt;&gt;&gt;" },
++    { "" , "" },
++  };
++
++  for (unsigned i = 0; i < countof (test_array); ++i)
++    {
++      char *result = html_quote_string (test_array[i].input);
++      mu_assert ("test_html_quote_string: wrong result",
++                 strcmp (result, test_array[i].expected) == 0);
++      xfree (result);
++    }
++
++  return NULL;
++}
++
++#endif /* TESTING */
++
+ /*
+  * vim: et ts=2 sw=2
+  */
+diff --git a/tests/unit-tests.c b/tests/unit-tests.c
+index e9695e98..0a820ae6 100644
+--- a/tests/unit-tests.c
++++ b/tests/unit-tests.c
+@@ -68,6 +68,10 @@ all_tests(void)
+ #endif
+   mu_run_test (test_parse_netrc);
+   mu_run_test (test_retr_rate);
++  mu_run_test (test_construct_relative);
++  mu_run_test (test_match_except_index);
++  mu_run_test (test_find_fragment);
++  mu_run_test (test_html_quote_string);
+ 
+   return NULL;
+ }
+diff --git a/tests/unit-tests.h b/tests/unit-tests.h
+index 4402c017..9ad1957e 100644
+--- a/tests/unit-tests.h
++++ b/tests/unit-tests.h
+@@ -64,6 +64,10 @@ const char *test_hsts_url_rewrite_congruent(void);
+ const char *test_hsts_read_database(void);
+ const char *test_parse_netrc(void);
+ const char *test_retr_rate(void);
++const char *test_construct_relative(void);
++const char *test_match_except_index(void);
++const char *test_find_fragment(void);
++const char *test_html_quote_string(void);
+ 
+ #endif /* TEST_H */
+ 
diff -Nru wget-1.25.0/debian/patches/series wget-1.25.0/debian/patches/series
--- wget-1.25.0/debian/patches/series	2026-07-24 13:34:11.000000000 +0000
+++ wget-1.25.0/debian/patches/series	2026-10-30 23:40:03.000000000 +0000
@@ -2,3 +2,6 @@
 wget-doc-remove-usr-local-in-wget.texi
 wget-passive_ftp-default
 CVE-2026-15146
+CVE-2026-58470
+CVE-2026-58471
+CVE-2026-58472

Reply via email to