Fix CVE-2026-58472: integer+buffer overflow in html_quote_string()
Backport fix for CVE-2026-58472 to wget-1.21.1. The patch addresses an integer and buffer overflow in html_quote_string() by using INT_ADD_OK for safe integer arithmetic when calculating the output buffer size for HTML entity encoding. Also includes the follow-up fix for an off-by-1 error in the size calculation for each entity-encoded character, along with unit tests. The combined fix is added as Patch6: wget-1.21-CVE-2026-58472.patch based on upstream commits dd692d9 and f76978a. CVE: CVE-2026-58472 Upstream patches: -dd692d9cea.patch -f76978a51b.patch Resolves: RHEL-210636 This commit was backported by Ymir, a Red Hat Enterprise Linux software maintenance AI agent. Assisted-by: Ymir
This commit is contained in:
parent
928040f599
commit
a596752c69
296
wget-1.21-CVE-2026-58472.patch
Normal file
296
wget-1.21-CVE-2026-58472.patch
Normal file
@ -0,0 +1,296 @@
|
||||
From 6e351fa40e7d9223b856002d62587c85cf382370 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tim=20R=C3=BChsen?= <tim.ruehsen@gmx.de>
|
||||
Date: Mon, 29 Jun 2026 19:13:15 +0200
|
||||
Subject: [PATCH 1/2] * src/convert.c (html_quote_string): Fix integer+buffer
|
||||
overflow
|
||||
|
||||
Reported-by: TristanInSec@gmail.com
|
||||
---
|
||||
src/convert.c | 31 ++++++++++++++++++++++++-------
|
||||
1 file changed, 24 insertions(+), 7 deletions(-)
|
||||
|
||||
diff --git a/src/convert.c b/src/convert.c
|
||||
index 683f719..741582f 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"
|
||||
@@ -1169,21 +1170,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++)
|
||||
{
|
||||
|
||||
From 8babd2417cee6f7433e6cee4982f7f4cd4787cf9 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tim=20R=C3=BChsen?= <tim.ruehsen@gmx.de>
|
||||
Date: Thu, 2 Jul 2026 13:13:07 +0200
|
||||
Subject: [PATCH 2/2] Regression: Fix buffer overflow in html_quote_string()
|
||||
|
||||
The regression has been introduced in commit dd692d9 and
|
||||
is not part of any release.
|
||||
|
||||
The tests allow the address sanitizer to find the issue.
|
||||
|
||||
* src/convert.c: Fix string size calculation.
|
||||
* tests/unit-tests.c: Added tests including tests for html_quote_string().
|
||||
* tests/unit-tests.h: Add definitions for the test functions.
|
||||
|
||||
Reported-by: Trung Nguyen <trungnh@cystack.net>
|
||||
---
|
||||
src/convert.c | 148 +++++++++++++++++++++++++++++++++++++++++++--
|
||||
tests/unit-tests.c | 4 ++
|
||||
tests/unit-tests.h | 4 ++
|
||||
3 files changed, 152 insertions(+), 4 deletions(-)
|
||||
|
||||
diff --git a/src/convert.c b/src/convert.c
|
||||
index 741582f..24ab33b 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;
|
||||
@@ -1177,13 +1180,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);
|
||||
|
||||
@@ -1242,6 +1245,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&b" },
|
||||
+ { "<tag>", "<tag>" },
|
||||
+ { "\"quote\"", ""quote"" },
|
||||
+ { "space here", "space here" },
|
||||
+ { "&<>\" ", "&<>" " },
|
||||
+ { "no special", "no special" },
|
||||
+ { "&&&&", "&&&&" },
|
||||
+ { "<<>>", "<<>>" },
|
||||
+ { "" , "" },
|
||||
+ };
|
||||
+
|
||||
+ 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 443e628..ac5ce69 100644
|
||||
--- a/tests/unit-tests.c
|
||||
+++ b/tests/unit-tests.c
|
||||
@@ -65,6 +65,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 dd10e54..8e43e96 100644
|
||||
--- a/tests/unit-tests.h
|
||||
+++ b/tests/unit-tests.h
|
||||
@@ -61,6 +61,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 */
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
Summary: A utility for retrieving files using the HTTP or FTP protocols
|
||||
Name: wget
|
||||
Version: 1.21.1
|
||||
Release: 8%{?dist}
|
||||
Release: 9%{?dist}
|
||||
License: GPLv3+
|
||||
Url: http://www.gnu.org/software/wget/
|
||||
Source: ftp://ftp.gnu.org/gnu/wget/wget-%{version}.tar.gz
|
||||
@ -11,6 +11,9 @@ Patch2: wget-1.21-strtol.patch
|
||||
Patch3: wget-1.21-metalink-man.patch
|
||||
Patch4: wget-1.21-segfault.patch
|
||||
Patch5: wget-1.21-CVE-2024-38428.patch
|
||||
# https://gitlab.com/gnuwget/wget/-/commit/dd692d9cea5335b181d877ae917fe6e75587a812
|
||||
# https://gitlab.com/gnuwget/wget/-/commit/f76978a51ba9365e7ecaed96c1cfb73197a38ca2
|
||||
Patch6: wget-1.21-CVE-2026-58472.patch
|
||||
|
||||
Provides: webclient
|
||||
Provides: bundled(gnulib)
|
||||
@ -70,6 +73,9 @@ make check
|
||||
%{_infodir}/*
|
||||
|
||||
%changelog
|
||||
* Wed Jul 15 2026 RHEL Packaging Agent <redhat-ymir-agent@redhat.com> - 1.21.1-9
|
||||
- Resolves: RHEL-210636 - Fix integer+buffer overflow in html_quote_string()
|
||||
|
||||
* Mon Jul 15 2024 Michal Ruprich <mruprich@redhat.com> - 1.21.1-8
|
||||
- Resolves: RHEL-43226 - Misinterpretation of input may lead to improper behavior
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user