Fix CVE-2026-58472: integer+buffer overflow in html_quote_string()

Backport upstream fix for CVE-2026-58472 which addresses an integer
and buffer overflow vulnerability in the html_quote_string() function
in src/convert.c. The patch is based on upstream commits dd692d9 and
f76978a, adapted to use INT_ADD_WRAPV instead of INT_ADD_OK since
the gnulib version bundled with wget 1.19.5 does not provide the
latter. The fix adds overflow-safe integer arithmetic to the string
size calculation and aborts on overflow. Unit tests for
construct_relative, match_except_index, find_fragment, and
html_quote_string are included.

CVE: CVE-2026-58472
Upstream patches:
 - dd692d9cea.patch
 - f76978a51b.patch
Resolves: RHEL-210627

This commit was backported by Ymir, a Red Hat Enterprise Linux software maintenance AI agent.

Assisted-by: Ymir
This commit is contained in:
RHEL Packaging Agent 2026-07-15 10:44:14 +00:00
parent 25ab8bae45
commit 055c68cdfe
2 changed files with 273 additions and 1 deletions

View File

@ -0,0 +1,264 @@
From 2133f09782cefec5f174218d8d984744277fa891 Mon Sep 17 00:00:00 2001
From: RHEL Packaging Agent <redhat-ymir-agent@redhat.com>
Date: Wed, 15 Jul 2026 10:38:03 +0000
Subject: [PATCH] Fix CVE-2026-58472: integer+buffer overflow in
html_quote_string()
Backport of upstream commits dd692d9 and f76978a, adapted to use
INT_ADD_WRAPV (available in wget 1.19.5 gnulib) instead of INT_ADD_OK
(not available in this gnulib version).
* src/convert.c (html_quote_string): Fix integer+buffer overflow using
overflow-safe integer arithmetic via INT_ADD_WRAPV.
Fix string size calculation regression (+1 per special character).
Add unit tests for construct_relative, match_except_index,
find_fragment, and html_quote_string.
* tests/unit-tests.c: Register new test functions.
* tests/unit-tests.h: Declare new test functions.
---
src/convert.c | 171 +++++++++++++++++++++++++++++++++++++++++++--
tests/unit-tests.c | 4 ++
tests/unit-tests.h | 4 ++
3 files changed, 172 insertions(+), 7 deletions(-)
diff --git a/src/convert.c b/src/convert.c
index e6ca8fbb..1284d991 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"
@@ -47,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;
@@ -1131,21 +1135,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_WRAPV (i, 4 + 1, &i); /* `amp;' */
else if (*s == '<' || *s == '>')
- i += 3; /* `lt;' and `gt;' */
+ ok = !INT_ADD_WRAPV (i, 3 + 1, &i); /* `lt;' and `gt;' */
else if (*s == '\"')
- i += 5; /* `quot;' */
+ ok = !INT_ADD_WRAPV (i, 5 + 1, &i); /* `quot;' */
else if (*s == ' ')
- i += 4; /* #32; */
+ ok = !INT_ADD_WRAPV (i, 4 + 1, &i); /* #32; */
+ else
+ ok = !INT_ADD_WRAPV (i, 1, &i);
+
+ if (!ok)
+ {
+ DEBUGP (("Overflow detected in html_quote_string().\n"));
+ abort();
+ }
+ }
+
+ if (INT_ADD_WRAPV (i, 1, &i))
+ {
+ DEBUGP (("Overflow detected in html_quote_string().\n"));
+ abort();
}
- res = xmalloc (i + 1);
+
+ res = xmalloc (i);
s = b;
for (p = res; *s; s++)
{
@@ -1187,6 +1207,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 0db434be..f3149343 100644
--- a/tests/unit-tests.c
+++ b/tests/unit-tests.c
@@ -64,6 +64,10 @@ all_tests(void)
mu_run_test (test_hsts_url_rewrite_congruent);
mu_run_test (test_hsts_read_database);
#endif
+ 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 74552120..002a785f 100644
--- a/tests/unit-tests.h
+++ b/tests/unit-tests.h
@@ -60,6 +60,10 @@ const char *test_hsts_new_entry(void);
const char *test_hsts_url_rewrite_superdomain(void);
const char *test_hsts_url_rewrite_congruent(void);
const char *test_hsts_read_database(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 */

View File

@ -1,7 +1,7 @@
Summary: A utility for retrieving files using the HTTP or FTP protocols
Name: wget
Version: 1.19.5
Release: 12%{?dist}
Release: 13%{?dist}
License: GPLv3+
Group: Applications/Internet
Url: http://www.gnu.org/software/wget/
@ -24,6 +24,9 @@ Patch10: wget-1.19.5-no_proxy-tests.patch
Patch11: wget-1.19.5-ca-cert-too-verbose.patch
Patch12: wget-1.19.5-no-log-when-quiet.patch
Patch13: wget-1.19.5-CVE-2024-38428.patch
# https://gitlab.com/gnuwget/wget/-/commit/dd692d9cea5335b181d877ae917fe6e75587a812
# https://gitlab.com/gnuwget/wget/-/commit/f76978a51ba9365e7ecaed96c1cfb73197a38ca2
Patch14: wget-1.19.5-CVE-2026-58472.patch
Provides: webclient
Provides: bundled(gnulib)
@ -62,6 +65,7 @@ grep "PACKAGE_STRING='wget .* (Red Hat modified)'" configure || exit 1
%patch11 -p1 -b .too_verbose
%patch12 -p1 -b .no-log-quiet
%patch13 -p1 -b .CVE-2024-38428
%patch14 -p1 -b .CVE-2026-58472
%build
%configure \
@ -108,6 +112,10 @@ rm -rf $RPM_BUILD_ROOT
%{_infodir}/*
%changelog
* Wed Jul 15 2026 RHEL Packaging Agent <redhat-ymir-agent@redhat.com> - 1.19.5-13
- Fix CVE-2026-58472: integer and buffer overflow in html_quote_string()
- Resolves: RHEL-210627
* Wed Jul 10 2024 Michal Ruprich <mruprich@redhat.com> - 1.19.5-12
- Resolves: RHEL-43559 - Misinterpretation of input may lead to improper behavior