From f76978a51ba9365e7ecaed96c1cfb73197a38ca2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20R=C3=BChsen?= Date: Thu, 2 Jul 2026 13:13:07 +0200 Subject: [PATCH] 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 Upstream commit (GitLab mirror): https://gitlab.com/gnuwget/wget/-/commit/f76978a51ba9365e7ecaed96c1cfb73197a38ca2 --- 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 3baec850..482676d9 100644 --- wget-1.25.0/src/convert.c +++ wget-1.25.0/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&b" }, + { "", "<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 e9695e98..0a820ae6 100644 --- wget-1.25.0/tests/unit-tests.c +++ wget-1.25.0/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 --- wget-1.25.0/tests/unit-tests.h +++ wget-1.25.0/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 */