chore(deps): update dependency protobuf to ~> 0.16 [security] - #39
Conversation
|
Renovate's artifact update failed on this branch, so mix.lock stayed pinned to protobuf 0.15.0 while mix.exs was bumped to ~> 0.16. The lock was both inconsistent with mix.exs and still on a version covered by GHSA-rv48-qqj5-crxg (>= 0.8.0, < 0.16.1).
There was a problem hiding this comment.
セキュリティ修正(CVE-2026-54451)に対応する依存更新として妥当です。1点、バージョン整合性について確認をお願いします。
| # Required dependencies | ||
| {:gen_stage, "~> 1.2"}, | ||
| {:protobuf, "~> 0.15"}, | ||
| {:protobuf, "~> 0.16"}, |
There was a problem hiding this comment.
mix.exsでは ~> 0.16 を指定していますが、mix.lockでは実際に 0.17.0 が解決されています。~> 0.16 は >= 0.16.0 and < 1.0.0 を許容するためロック自体は矛盾しませんが、このPRの目的(CVE-2026-54451対策で必要な最小バージョンは 0.16.1)に対して 0.17.0 が入っている点を意図通りか確認してください。もしメジャー更新(0.17系)に破壊的変更が含まれる場合は、テストが通ることを確認のうえマージすることを推奨します。
There was a problem hiding this comment.
ご指摘のとおり mix.exs は ~> 0.16 のまま、mix.lock は 0.17.0 です。意図通りなので、根拠を残して据え置きます。
なぜ 0.17.0 なのか
そもそもこのブランチは Renovate の artifact 更新が失敗しており(本 PR の Renovate コメント参照。原因は Command failed: install-tool elixir v1.20.3)、mix.lock が protobuf 0.15.0 のままでした。つまり merge しても advisory (>= 0.8.0, < 0.16.1) の範囲内に留まり、脆弱性が解消されない状態でした。そこで mix deps.update protobuf で lock を再生成したところ、~> 0.16 を満たす最新版として 0.17.0 が解決されました。
0.16.1 に固定する選択肢もありますが、それには制約を ~> 0.16.1(= >= 0.16.1 and < 0.17.0)に狭める必要があり、以後の 0.17 系更新を塞ぐことになるため採りませんでした。
破壊的変更の確認
ご指摘の「テストが通ることを確認のうえマージ」は実施済みです。
mix test: 120 passed (3 doctests / 117 tests)、Encoder は行カバレッジ 100%- CI: OTP 27.3.4.4 / Elixir 1.17.3 と OTP 29.0.2 / Elixir 1.20.1 の両マトリクスで pass
mix compile --force --warnings-as-errors: 本体に警告なしmix credo --strict: no issuesmix hex.audit: 警告なし
生成コード lib/dnstap.pb.ex も再生成なしでそのまま通っており、本ライブラリが使う範囲での API 互換性は確認できています。
advisory は CVE-2026-54451 / GHSA-rv48-qqj5-crxg(CVSS 8.2 High)で、ご指摘の CVE 番号で正しいです。修正内容は embedded message のネスト深さを 100 で打ち切るもので、0.17.0 にも含まれています。
This PR contains the following updates:
~> 0.15→~> 0.16Protobuf: Unbounded recursion depth in embedded-message decoding
CVE-2026-54451 / GHSA-rv48-qqj5-crxg
More information
Details
Summary
Unbounded recursion depth in
Protobuf.Decoder(Hex packageprotobuf, versions>= 0.8.0, < 0.16.1) lets an unauthenticated attacker crash any service that decodes untrusted protobuf messages whose schema contains a self-referential or cyclic message type. A small request body (a few KB to a few MB) that nests an embedded field hundreds of thousands to millions of levels deep forces the BEAM to recurse once per level, exhausting memory and pinning a scheduler. A handful of such requests can take the node offline (a request-amplification denial of service).Details
Protobuf.Decoder.value_for_field/3handles embedded message fields in itsembedded?: truebranch atlib/protobuf/decoder.ex:218-243. For an embedded field it callsdecode(bin, type)recursively, which re-entersbuild_message → handle_value → value_for_field. The recursive call is not in tail position (its result is consumed by the surrounding decode after it returns), so every nesting level retains a live frame on the process stack and heap.There is no recursion-depth counter anywhere in the decoder. For any schema with a self-referential message type (e.g.
message Tree { Tree child = 1; }, a common shape for comment threads, org charts, file trees, and ASTs) or any cycle of message types, the attacker controls the nesting depth entirely through the input bytes. Each additional level costs only a 1-byte field tag plus a varint length prefix, so depth grows roughly inversely with payload size: a tiny body buys an enormous recursion depth.Reference protobuf implementations (Google's C++, Java, etc.) cap recursion at 100 specifically to prevent this. The Elixir decoder enforces no comparable bound, so the recursion continues until the process exhausts memory, blows the stack, or starves the scheduler doing GC over the deep structure.
The fix threads a depth counter through
decode / build_message / handle_value / value_for_field(or holds it in the process dictionary for the duration of the top-leveldecode) and raisesProtobuf.DecodeErroronce it exceeds a configurable limit, defaulting to 100 to match the reference implementations.PoC
defmodule Tree do use Protobuf, syntax: :proto3; field(:child, 1, type: Tree) end.depthlevels prepend<<0x0A, length_varint(inner_size), inner>>(tag0x0A= field 1, wire type 2). Use an iolist with a running byte-size to keep generation O(depth).depth = 1_000_000) asapplication/x-protobufto any endpoint that callsTree.decode/1.decode(bin, type)invalue_for_field/3re-enters once per nesting level, accumulating a frame per level. The decode burns seconds of CPU and hundreds of MB on the victim node; a few concurrent requests exhaust it.Impact
Unauthenticated, network-reachable request-amplification denial of service against any service that decodes attacker-influenced protobuf bytes into a self-referential or cyclic message type. A single small request can consume seconds of CPU and hundreds of MB of memory on the victim; a few concurrent requests can take the node offline.
Resources
Severity
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:NReferences
This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).
Release Notes
elixir-protobuf/protobuf (protobuf)
v0.16.1Compare Source
Security
(GHSA-rv48-qqj5-crxg).
Decoding now caps embedded-message nesting depth at
100(matching the reference Protobufimplementations), raising
Protobuf.DecodeErrorwhen the limit is exceeded. The limit can beoverridden per call via the
:max_nesting_depthoption toProtobuf.decode/3.v0.16.0Compare Source
Enhancements
Add
full_name/0callback to Protobuf messages for retrieving the full Protobuf name.Add support for string arguments in the
value/1callback of enums.Add
Protobuf.Any.pack/1.Add
Protobuf.field_presence/2.Add deprecation warning when casting a non-struct enumerable to a Protobuf struct.
Configuration
📅 Schedule: (in timezone Asia/Tokyo)
🚦 Automerge: Enabled.
♻ Rebasing: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.
🔕 Ignore: Close this PR and you won't be reminded about this update again.
This PR was generated by Mend Renovate. View the repository job log.