[BUG] OnResponse() can call std::terminate() when the response body fails to parse as JSON/protobuf
メンテナーはふだん 1 日以内に返信
評価
- 難易度
- 2/5
- 見積もり時間
- 1〜3時間
- 初心者へのやさしさ
- 78/100
- issue の種類
- バグ
- 明瞭さ
- 明確に書かれている
- 活発さ
- 活発
- 技術スタック
- cpp
調査の方向性
exporters/otlp/src/otlp_http_client.cc の ResponseHandler::OnResponse から始め、レスポンスボディのコピーとパースに関する既存の結果および終了フローを確認してください。WITH_OTLP_HTTP=ON でビルドし、形式が不正またはサイズが大きすぎる JSON/protobuf レスポンスがプロセスを終了させることなく ExportResult::kFailure になることを確認してください。その間も、既存の stopping_ および Unbind のフローが完了することを確認してください。
索引モデルが issue の本文から書いたものです。
説明
Describe your environment
Reproduced by source read + build on main at 11fa0db0 (also present in v1.28.0, the latest release -- not a regression). exporters/otlp/src/otlp_http_client.cc, ResponseHandler::OnResponse.
Steps to reproduce
OnResponse(http_client::Response &response) noexcept is declared noexcept, but its body has no try/catch anywhere:
void OnResponse(http_client::Response &response) noexcept override
{
sdk::common::ExportResult result = sdk::common::ExportResult::kSuccess;
std::string log_message;
{
std::unique_lock<std::mutex> lk(mutex_);
body_ = std::string(response.GetBody().begin(), response.GetBody().end());
...
}
if (response_ != nullptr && result == sdk::common::ExportResult::kSuccess && !body_.empty())
{
if (content_type_ == HttpRequestContentType::kJson)
{
if (!google::protobuf::util::JsonStringToMessage(body_, response_).ok())
{ ... }
}
else if (!response_->ParseFromString(body_))
{ ... }
}
...
}
Both google::protobuf::util::JsonStringToMessage and Message::ParseFromString can throw (protobuf's own message-building path allocates, and an oversized or maliciously-crafted body can trigger std::bad_alloc or other exceptions depending on build/allocator configuration). The body_ = std::string(...) copy a few lines above can throw for the same reason on an oversized body.
An unhandled exception escaping a function marked noexcept invokes std::terminate() per the C++ standard, regardless of what the exception actually was.
To trigger: point an OTLP HTTP exporter at a collector (or a MITM/misbehaving intermediary proxy) that returns a 2xx response with an extremely large or adversarially crafted JSON/protobuf body.
What is the expected behavior?
A malformed or oversized response body from the configured collector should cause that one export to be reported as a failure (ExportResult::kFailure) -- it shouldn't be able to take down the exporting process.
What is the actual behavior?
The exception escapes noexcept and the process calls std::terminate(), killing the entire application -- not just the telemetry pipeline. Any host application exporting spans/logs/metrics via OTLP/HTTP is exposed to this if its collector (or anything between it and the collector) can be made to return an oversized or malformed response body.
Additional context
Suggested fix -- wrap the body in try/catch, converting any exception into kFailure, and keep the existing termination logic (stopping_.compare_exchange_strong(...) / Unbind(result)) outside the try block so a caught exception can't also leave waitForResponse()-style callers waiting forever (the same class of problem as the lost-wakeup bug fixed in #4298 for the Elasticsearch exporter):
sdk::common::ExportResult result = sdk::common::ExportResult::kSuccess;
std::string log_message;
- // Lock the private members so they can't be read while being modified
+ try
{
- std::unique_lock<std::mutex> lk(mutex_);
-
- // Store the body of the request
- body_ = std::string(response.GetBody().begin(), response.GetBody().end());
-
- if (!(response.GetStatusCode() >= 200 && response.GetStatusCode() <= 299))
+ // Lock the private members so they can't be read while being modified
{
- log_message = BuildResponseLogMessage(response, body_);
+ std::unique_lock<std::mutex> lk(mutex_);
- OTEL_INTERNAL_LOG_ERROR("[OTLP HTTP Client] Export failed, " << log_message);
- result = sdk::common::ExportResult::kFailure;
- }
- else if (console_debug_)
- {
- if (log_message.empty())
+ // Store the body of the request
+ body_ = std::string(response.GetBody().begin(), response.GetBody().end());
+
+ if (!(response.GetStatusCode() >= 200 && response.GetStatusCode() <= 299))
{
log_message = BuildResponseLogMessage(response, body_);
+
+ OTEL_INTERNAL_LOG_ERROR("[OTLP HTTP Client] Export failed, " << log_message);
+ result = sdk::common::ExportResult::kFailure;
+ }
+ else if (console_debug_)
+ {
+ if (log_message.empty())
+ {
+ log_message = BuildResponseLogMessage(response, body_);
+ }
}
}
- }
- // On 2xx with a non-empty body, parse it into the caller-provided typed response
- if (response_ != nullptr && result == sdk::common::ExportResult::kSuccess && !body_.empty())
- {
- if (content_type_ == HttpRequestContentType::kJson)
+ // On 2xx with a non-empty body, parse it into the caller-provided typed response
+ if (response_ != nullptr && result == sdk::common::ExportResult::kSuccess && !body_.empty())
{
- if (!google::protobuf::util::JsonStringToMessage(body_, response_).ok())
+ if (content_type_ == HttpRequestContentType::kJson)
+ {
+ if (!google::protobuf::util::JsonStringToMessage(body_, response_).ok())
+ {
+ OTEL_INTERNAL_LOG_ERROR("[OTLP HTTP Client] Failed to parse JSON response body");
+ result = sdk::common::ExportResult::kFailure;
+ }
+ }
+ else if (!response_->ParseFromString(body_))
{
- OTEL_INTERNAL_LOG_ERROR("[OTLP HTTP Client] Failed to parse JSON response body");
+ OTEL_INTERNAL_LOG_ERROR("[OTLP HTTP Client] Failed to parse response body");
result = sdk::common::ExportResult::kFailure;
}
}
- else if (!response_->ParseFromString(body_))
+
+ if (console_debug_ && result == sdk::common::ExportResult::kSuccess)
{
- OTEL_INTERNAL_LOG_ERROR("[OTLP HTTP Client] Failed to parse response body");
- result = sdk::common::ExportResult::kFailure;
+ OTEL_INTERNAL_LOG_DEBUG("[OTLP HTTP Client] Export success, " << log_message);
}
}
-
- if (console_debug_ && result == sdk::common::ExportResult::kSuccess)
+ catch (const std::exception &ex)
+ {
+ OTEL_INTERNAL_LOG_ERROR(
+ "[OTLP HTTP Client] Exception while processing response: " << ex.what());
+ result = sdk::common::ExportResult::kFailure;
+ }
+ catch (...)
{
- OTEL_INTERNAL_LOG_DEBUG("[OTLP HTTP Client] Export success, " << log_message);
+ OTEL_INTERNAL_LOG_ERROR("[OTLP HTTP Client] Unknown exception while processing response");
+ result = sdk::common::ExportResult::kFailure;
}
{
Compile-checked against a clean build of this file (WITH_OTLP_HTTP=ON) -- no warnings or errors. Happy to open a PR with this if useful.
- 主要言語
- C++
- スター
- 1.4k
- フォーク
- 660
- 平均マージ
- 1日 12時間
- マージ済み PR(30日)
- 47
環境構築
このプロジェクトの開発コンテナを、あなたの GitHub アカウントでブラウザ上に起動します。
- Dockerfile・Docker Compose ファイルなし
- プルリクエストのテンプレートあり
- コントリビューションガイドを読む
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
open-telemetry/opentelemetry-cpp のほかの issue
-
Baggage::ToHeader/FromHeader use form-style `+` for space, which other W3C Baggage implementations read as a literal plus対応中かも このイシューにリンクされたプルリクエストがオープン中、またはマージ済みです。 オープンbug triage/accepted
難易度 2/5 1〜3時間 初心者へのやさしさ 88/100
open-telemetry/opentelemetry-cpp#4745 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
needs-triage
難易度 2/5 1〜3時間 初心者へのやさしさ 68/100
open-telemetry/opentelemetry-cpp#4697 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
TracerContext construction might result in a terminate再び着手できるかも @akshat-lakhera が 25 日前に担当しましたが、オープン中のプルリクエストはありません。 オープンtriage/accepted
難易度 2/5 1〜3時間 初心者へのやさしさ 62/100
open-telemetry/opentelemetry-cpp#4684 · コメント 2 件 · リアクション 2 件 ·
メンテナーはふだん 1 日以内に返信
-
[CI] Add Ubuntu 26.04 runners to the CI workflow再び着手できるかも このイシューのプルリクエストはマージされずにクローズされました。 オープンtriage/accepted
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
open-telemetry/opentelemetry-cpp#4596 · コメント 2 件 · リアクション 1 件 ·
メンテナーはふだん 1 日以内に返信
-
[BUG] Resource::Create() throws bad_variant_access if process.executable.name isn't a string対応中かも @ryux1 が 59 日前に担当しました。 オープンbug help wanted triage/accepted
難易度 2/5 1〜3時間 初心者へのやさしさ 84/100
open-telemetry/opentelemetry-cpp#4535 · コメント 1 件 · リアクション 2 件 ·
メンテナーはふだん 1 日以内に返信
open-telemetry/opentelemetry-cpp の issue をすべて見る
似ている issue
-
bug javascript
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
メンテナーはふだん 1 日以内に返信
-
enhancement third-party
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
pocoproject/poco#5656 ·
メンテナーはふだん 2 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 76/100
-
Type: Bug
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 73/100
Qiskit/qiskit-aer#2525 ·
メンテナーはふだん 1 日以内に返信