Open
Conversation
|
An automated preview of the documentation is available at https://988.url.prtest2.cppalliance.org/index.html If more commits are pushed to the pull request, the docs will rebuild at the same URL. 2026-04-15 15:11:19 UTC |
|
GCOVR code coverage report https://988.url.prtest2.cppalliance.org/gcovr/index.html Build time: 2026-04-15 15:18:35 UTC |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
parse_pattern/replacement_field_rulevalidates{/}matching and format specs before detail functions run), atomic types behind#ifdefguards, or preconditions guaranteed by callers.Summary
v3 (Apr 2, 2026)
All Fixes (4 commits)
7cd6702b55336c7c0665d003696dFinding Details
Per-finding triage with a verdict and a rationale for each individual finding.
HIGH (4 findings)
H1. Data race on reference count decrement in release()
include/boost/url/grammar/impl/recycled.hpp:78refsis a plainstd::size_twith no synchronization.refsisstd::atomic<std::size_t>whenBOOST_URL_DISABLE_THREADSis not defined (the default). The plainstd::size_tbranch only applies when threads are explicitly disabled. Same finding was triaged as false positive in v2 (PR Security review fixes #982).H2. Non-atomic reference count increment in copy constructor
include/boost/url/grammar/impl/recycled.hpp:149++p_->refs.H3. Integer overflow in encoded_size leads to heap buffer overflow
include/boost/url/impl/encode.hpp:60encoded_size()can overflowstd::size_tfor very large inputs.encoded_size()overflowsstd::size_t, the encoded result would exceed the maximum representable size and could not be stored in any string or buffer. The overflow means the input already violates the precondition that the encoded output fits instd::size_t.H4. Out-of-bounds read when format string ends with '{'
src/detail/pct_format.cpp:54++it1, code dereferences*it1with onlyBOOST_ASSERT(it1 != end)as guard.parse_pattern, which usesreplacement_field_ruleto require every{to have a matching}. A format string ending with an unmatched{fails validation beforepct_vmeasure/pct_vformatare ever called. TheBOOST_ASSERTcorrectly documents this precondition.MEDIUM (7 findings)
M1. Out-of-bounds read after align/fill in format_args
src/detail/format_args.cpp:127*itwithout checkingit != end.parse_pattern/replacement_field_rule, which requires a closing}. None of the format spec parsing steps (fill/align, width, type) can consume}, soitalways reaches}beforeend. Verified by reverting the fix and confirming the test cases still throw (fromparse_pattern, not fromformat_args).M2. Out-of-bounds read in integer_formatter_impl::parse
src/detail/format_args.cpp:265}beforeendbecauseparse_patternvalidates the format string structure. Verified by reverting the fix: tests still pass.M3. Unchecked .value() call on port parse result
src/detail/pattern.cpp:232.value()on parse result without checking for error.port_part_rule_t::parse()never returns an error. It returns success withhas_port = falsewhen there is no port, and success with the parsed port otherwise. The string being parsed (":digits") was just written by the formatting code above, so it is always valid.M4. Out-of-bounds read in ci_string with different-size arguments
src/grammar/ci_string.cpp:30detail::ci_is_equalloops usings0.size()as bound, which would read pasts1if sizes differ.ci_is_equal(string_view, string_view)inci_string.hppcheckss0.size() != s1.size()and returns false before calling the detail function. The detail function is never reached with mismatched sizes.M5. Incorrect decoded-length calculation in segments_iter_impl decrement()
include/boost/url/detail/impl/segments_iter_impl.hpp:209/a%20b/c, the iterator tracks how long each segment is when decoded (dn).%20is a space, soa%20bdecoded isa b(3 characters). When going backwards to the first segment, the code did not recalculatedn. It still held the value from whatever segment we were on before (c, which hasdn=1). The segment itself displayed correctly (becauses_recalculates independently via thepct_string_viewconverting constructor), butdnwas wrong. So the next time going forward, the iterator added the wrong number todecoded_prefix(the running total of decoded characters before the current segment).7cd6702, "fix: segments_iter_impl decoded-length in decrement()"): Replaced both the manualstring_viewconstruction (index==0) and the backwards inline scan (index>0) with calls toupdate(), which properly setsdn,next, ands_. Also added missingtestObservers()call in test suite.M6. Potential size_t overflow in range_rule element counter
include/boost/url/grammar/impl/range_rule.hpp:780ncould overflow if parsing an extremely large number of elements.string_viewcannot holdSIZE_MAXbytes on any real system, soncan never reach the overflow threshold. The overflow is physically impossible.M7. Out-of-bounds read in pct_format after format handler returns
src/detail/pct_format.cpp:107pctx.begin()is dereferenced withBOOST_ASSERT(*it1 == '}')as the only guard.parse_pattern/replacement_field_rule, which requires every{to have a matching}. By the timepct_vmeasure/pct_vformatrun, the closing}is guaranteed to exist. TheBOOST_ASSERTcorrectly documents this precondition.LOW (4 findings)
L1. noexcept constructor performs potentially-throwing std::string allocations
include/boost/url/param.hpp:359paramstruct hasstd::stringmembers (keyandvalue). Its constructor takesstring_viewarguments and copies them into those members. Copying into astd::stringallocates memory, and allocation can fail withbad_alloc. But the constructor was markednoexcept, which means if the allocation fails, instead of throwing an exception that the caller could catch, the program callsstd::terminate()and crashes with no recovery possible.b55336c, "fix: param noexcept on throwing constructor"): Removed incorrectnoexcept. Verified withstatic_assert(!std::is_nothrow_constructible<...>)which fails to compile if the fix is reverted.L2. No validation that minimum count N <= maximum count M in range_rule
include/boost/url/grammar/range_rule.hpp:524N > Mis a caller error, but the parse logic handles it gracefully (always returnserror::mismatch). No UB, no crash, no incorrect results. The missingBOOST_ASSERTis a quality-of-life improvement for debugging, not a functional defect.L3. noexcept on operator std::string() that can throw
include/boost/url/grammar/string_view_base.hpp:162string_view_basehasoperator std::string()which creates astd::stringfrom the view's contents. Same problem as L1: constructing astd::stringallocates memory, can throwbad_alloc, but the function was markednoexcept. Same consequence: unrecoverable crash instead of catchable exception.7c0665d, "fix: string_view_base noexcept on throwing operator std::string()"): Removed incorrectnoexcept.L4. std::memcpy with potentially null source pointer when size is zero
include/boost/url/impl/url_view.hpp:70url_view::persist()copies the URL's character buffer usingstd::memcpy(dest, data(), size()). If theurl_viewis default-constructed (empty),data()isnullptrandsize()is 0. Copying zero bytes from null seems harmless, but the C and C++ standards say passingnullptrtomemcpyis undefined behavior even when the size is zero. Sanitizers (UBSan) flag this.003696d, "fix: url_view memcpy with null source when size is zero"): Addedif(size())guard.