diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1b9f3b0ae..230a9ee7e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -65,6 +65,11 @@ jobs: clang-cl * subrange-policy: | msvc: one-per-minor + containers: | + # ubuntu:25.04 (plucky), the v1.9.3 default for GCC 16, is EOL + # and never gained gcc-16 in apt or toolchain-r/ppa. GCC 16 ships + # natively on ubuntu:26.04 so target that instead. + gcc 16: ubuntu:26.04 standards: '>=11' latest-factors: | gcc ASan diff --git a/include/boost/url/impl/decode_view.hpp b/include/boost/url/impl/decode_view.hpp index 4de0e29f0..e9eb0f7dd 100644 --- a/include/boost/url/impl/decode_view.hpp +++ b/include/boost/url/impl/decode_view.hpp @@ -178,8 +178,9 @@ decoded_strcmp(decode_view s0, T s1) auto n = (std::min)(n0, n1); auto it0 = s0.begin(); auto it1 = s1.begin(); - while (n--) + while (n != 0) { + --n; const char c0 = *it0++; const char c1 = *it1++; if (c0 == c1) diff --git a/include/boost/url/rfc/impl/ipv6_address_rule.hpp b/include/boost/url/rfc/impl/ipv6_address_rule.hpp index d2c9f1aa6..c0bbb79f8 100644 --- a/include/boost/url/rfc/impl/ipv6_address_rule.hpp +++ b/include/boost/url/rfc/impl/ipv6_address_rule.hpp @@ -133,6 +133,12 @@ parse( BOOST_URL_CONSTEXPR_RETURN_EC( grammar::error::invalid); } + if(! c) + { + // missing h16 before "." + BOOST_URL_CONSTEXPR_RETURN_EC( + grammar::error::invalid); + } if(! detail::maybe_octet( &bytes[2*(7-n)])) { diff --git a/src/detail/format_args.cpp b/src/detail/format_args.cpp index 1c62d1089..c2adaae3a 100644 --- a/src/detail/format_args.cpp +++ b/src/detail/format_args.cpp @@ -370,9 +370,11 @@ measure( dn += measure_one(sign, cs); ++n; } - // Use unsigned to avoid UB when v == LLONG_MIN + // Use bitwise two's-complement negation to obtain |v| without + // tripping signed-overflow (v == LLONG_MIN) or + // unsigned-overflow (0ull - x) sanitizers. unsigned long long int uv = v < 0 - ? 0ull - static_cast(v) + ? ~static_cast(v) + 1ull : static_cast(v); do { @@ -448,10 +450,12 @@ format( grammar::lut_chars const& cs) const { // get n digits - // Use unsigned to avoid UB when v == LLONG_MIN + // Bitwise two's-complement negation to obtain |v| without + // tripping signed-overflow (v == LLONG_MIN) or + // unsigned-overflow (0ull - x) sanitizers. bool const neg = v < 0; unsigned long long int uv = neg - ? 0ull - static_cast(v) + ? ~static_cast(v) + 1ull : static_cast(v); unsigned long long int uv0 = uv; unsigned long long int p = 1; diff --git a/src/grammar/ci_string.cpp b/src/grammar/ci_string.cpp index a3d5c8238..fa53e4e6f 100644 --- a/src/grammar/ci_string.cpp +++ b/src/grammar/ci_string.cpp @@ -11,6 +11,22 @@ #include #include +// FNV-1a (in ci_digest below) relies on modular multiplication +// of unsigned values, which Clang's -fsanitize=integer flags as +// overflow. Suppress the check for that function only. GCC has +// no equivalent sanitizer (it does not flag unsigned overflow), +// so the annotation is Clang-only — applying it on GCC produces +// "attribute directive ignored" under -Werror=attributes. +#if defined(__clang__) && defined(__has_attribute) +# if __has_attribute(no_sanitize) +# define BOOST_URL_NO_SANITIZE_INT_OVERFLOW \ + __attribute__((no_sanitize("unsigned-integer-overflow"))) +# endif +#endif +#ifndef BOOST_URL_NO_SANITIZE_INT_OVERFLOW +# define BOOST_URL_NO_SANITIZE_INT_OVERFLOW +#endif + namespace boost { namespace urls { namespace grammar { @@ -32,15 +48,16 @@ ci_is_equal( auto p2 = s1.data(); char a, b; // fast loop - while(n--) + while(n != 0) { + --n; a = *p1++; b = *p2++; if(a != b) goto slow; } return true; - do + for(;;) { a = *p1++; b = *p2++; @@ -48,8 +65,10 @@ ci_is_equal( if( to_lower(a) != to_lower(b)) return false; + if(n == 0) + break; + --n; } - while(n--); return true; } @@ -64,8 +83,9 @@ ci_is_less( auto p2 = s1.data(); auto n = s0.size() < s1.size() ? s0.size() : s1.size(); - while(n--) + while(n != 0) { + --n; auto c1 = to_lower(*p1++); auto c2 = to_lower(*p2++); if(c1 != c2) @@ -102,8 +122,9 @@ ci_compare( } auto it0 = s0.data(); auto it1 = s1.data(); - while(n--) + while(n != 0) { + --n; auto c0 = to_lower(*it0++); auto c1 = @@ -119,6 +140,7 @@ ci_compare( //------------------------------------------------ +BOOST_URL_NO_SANITIZE_INT_OVERFLOW std::size_t ci_digest( core::string_view s) noexcept @@ -138,7 +160,7 @@ ci_digest( auto hash = hash0; auto p = s.data(); auto n = s.size(); - for(;n--;++p) + for(; n != 0; --n, ++p) { // VFALCO NOTE Consider using a lossy // to_lower which works 4 or 8 chars at a time. diff --git a/test/unit/ipv6_address.cpp b/test/unit/ipv6_address.cpp index dc2e3aa3d..855b12d06 100644 --- a/test/unit/ipv6_address.cpp +++ b/test/unit/ipv6_address.cpp @@ -251,6 +251,11 @@ class ipv6_address_test bad("0:0:0:0:0:0:0:1.2.3.4"); bad("0:0:0:0:0:0:0::1.2.3.4"); + // missing h16 before "." (issue #993) + bad("::."); + bad("1::."); + bad("1:2::."); + bad("."); bad("::1."); bad("::1.2"); bad("::1.2"); diff --git a/test/unit/url_view.cpp b/test/unit/url_view.cpp index 5eb9751c0..c9fe81059 100644 --- a/test/unit/url_view.cpp +++ b/test/unit/url_view.cpp @@ -1231,6 +1231,12 @@ class url_view_test } } + // Malformed IPv6 host must not read uninitialized memory (issue #993) + { + BOOST_URL_CXX20_CONSTEXPR auto r = parse_uri("https://[::."); + BOOST_TEST(! r.has_value()); + } + // Detailed test { BOOST_URL_CXX20_CONSTEXPR auto r = parse_uri_reference("http://example.com:8080/path?query=1#frag");