[BUG] Stop reading past a string_view that is not NUL terminated - #4346
Conversation
772fe15 to
eeead20
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #4346 +/- ##
==========================================
- Coverage 80.86% 80.86% -0.00%
==========================================
Files 450 450
Lines 19215 19216 +1
==========================================
Hits 15537 15537
- Misses 3678 3679 +1
🚀 New features to boost your workflow:
|
| result.GetResponse().ForEachHeader( | ||
| [&span](nostd::string_view header_name, nostd::string_view header_value) { | ||
| span->SetAttribute("http.header." + std::string(header_name.data()), header_value); | ||
| span->SetAttribute("http.header." + static_cast<std::string>(header_name), header_value); |
There was a problem hiding this comment.
Casting to a std::string seems wrong.
Did you intent to build a temporary std::string instead, as in:
span->SetAttribute("http.header." + std::string(header_name), header_value);
There was a problem hiding this comment.
You are right, and your form is the one I should have written. Changed in 59b31d0.
nostd::string_view carries an explicit operator std::string, which direct initialisation reaches just as the cast did, so the two compile to the same thing and yours says what it is doing. I checked it builds both ways the type can exist: the fallback nostd::string_view class, and WITH_STL=CXX17 where it becomes std::string_view and the standard library's own string_view constructor takes over instead. Clean with no warnings under maintainer mode in both.
The branch is also rebased onto main, since the CHANGELOG had picked up a conflict.
nostd::string_view::data() points at the first character of a view, not at a C string, so anything that reads until a NUL can run past the end of what the caller offered. HttpSslOptions did that with strncmp() and it changes an answer rather than only reading too far: a view holding "https" over a buffer that continues with a colon is read as a secure scheme, and the connection silently turns on TLS the caller did not ask for. BuildResponseLogMessage in the Elasticsearch and OTLP HTTP exporters streamed header names and values through the const char* overload, which logs whatever follows the view up to the next NUL. The same file already writes seven other views with their length. The HTTP example built a std::string the same way. The bundled curl client hands out views onto std::string, which is NUL terminated, so none of this misbehaves in tree today. It is reachable through the public HTTP client factory.
@marcalff on open-telemetry#4346: a cast to a class type reads as a conversion where the intent is a temporary. `nostd::string_view` carries an explicit `operator std::string`, which direct initialisation reaches just as the cast did, so the two forms compile to the same thing and this one says what it is. Compiles clean in both configurations of the type: the fallback `nostd::string_view` class, and `WITH_STL=CXX17` where it is `std::string_view` and the standard library's own string_view constructor takes over. Signed-off-by: thc1006 <84045975+thc1006@users.noreply.github.com>
eeead20 to
59b31d0
Compare
nostd::string_view::data()points at the first character of a view, not at a C string. Anything that reads until a NUL can therefore run past the end of what the caller offered. Four places do that.The one that changes an answer
HttpSslOptionsdecidesuse_sslfrom the url it is given:strncmpreads up to six bytes whatever the view's length is. A view holding the five charactershttpsover a buffer that continues with a colon matches, and the connection turns on TLS the caller never asked for. Measured, with the view length being the only difference:The replacement compares within the view, and
<cstring>was there for that one call so it goes too.The three that read too far
BuildResponseLogMessagein the Elasticsearch and the OTLP HTTP exporters streams header names and values through theconst char *overload:whatever follows the view, up to the next NUL, lands in the log.
otlp_http_client.ccalready writes seven other views with their length a few lines below, so these two were the outliers. Dropping.data()picks upnostd::operator<<, which isos.write(s.data(), s.length()).examples/http/client.ccbuilt a span attribute name the same way, withstd::string(header_name.data()).Reachability
The bundled curl client hands
ForEachHeaderviews ontostd::string, andstd::string::data()is NUL terminated, so none of this misbehaves in tree today.HttpClientFactoryis public API andResponse::ForEachHeadertakesstring_view, so a client supplied through the factory can hand out views into its own receive buffer, which is where the log paths become reachable. TheHttpSslOptionsconstructor is public and takes astring_viewurl directly.Tests
HttpSslOptionsTests.UsesOnlyTheGivenUrlToDecideTheSchemecovers the scheme decision, including the truncated view. It fails on the old code:and passes on this one. It lives in
url_parser_test, which linksopentelemetry_extwithout curl.The two
BuildResponseLogMessagemethods are private members of file-local classes with no seam, so they have no direct test here. Their fix routes throughnostd::operator<<, which writes the view's length. Say the word if you would rather I opened a seam for them.Verification
Built with
WITH_ELASTICSEARCH=ON WITH_OTLP_HTTP=ON WITH_ZIPKIN=ON WITH_EXAMPLES_HTTP=ONunderOTELCPP_MAINTAINER_MODE=ON, clean../ci/do_ci.sh formatreports no diff.Not in this change
Properties::to_vector(span<string_view>)in the ETW exporter has the samestd::string(item.data())shape, and on the same lines it pre-sizes the result and then appends, so it returns twice as many entries as it was given with the first half empty. That is a second, unrelated defect in a component no CI job builds, so it is not folded in here.