Skip to content

KRPC-564: Reject >10-byte varint tags in protobuf parser - #672

Merged
Mr3zee merged 6 commits into
mainfrom
fix/KRPC-564
Apr 14, 2026
Merged

KRPC-564: Reject >10-byte varint tags in protobuf parser#672
Mr3zee merged 6 commits into
mainfrom
fix/KRPC-564

Conversation

@ai-agent-kxrpc

@ai-agent-kxrpc ai-agent-kxrpc Bot commented Apr 13, 2026

Copy link
Copy Markdown
Contributor

Subsystem

protobuf-api, protobuf native shim

Problem

YouTrack: KRPC-564

The native protobuf parser silently accepted >10-byte varint tags (and overlong varint encodings) instead of rejecting them. This caused 4 conformance test failures (BadTag_VarintMoreThanTenBytes).

Solution

Native C++ (pw_decoder_read_validated_tag): Replaced the ReadVarint64 + ConsumedEntireMessage approach with manual byte-by-byte varint parsing via ReadRaw. The previous approach had a fundamental flaw: ConsumedEntireMessage() never returns true at the top level because CodedInputStream has no explicit limit set (defaults to INT_MAX), so legitimate_message_end_ is never set — causing ALL readTag calls to fail at top-level EOF.

The new approach:

  1. BytesUntilLimit() == 0 — detects sub-message EOF at PushLimit boundaries (unchanged).
  2. ReadRaw(&b, 1) — reads a single byte to distinguish top-level EOF (fails → no data) from varint start (succeeds → data available). This avoids ReadTag()'s ambiguous return-0-for-both-EOF-and-errors.
  3. Manual varint loop — tracks exact byte count for overlong encoding detection, rejects >10-byte varints, zero tags, and >32-bit values.

Why not ReadTag() + position tracking? ReadTag() on a >10-byte varint doesn't advance CurrentPosition() (fast-path varint reader uses a local pointer), so EOF and error are indistinguishable.

Why not top-level PushLimit(source_size)? PushLimit changes nested limit behavior — sub-message PushLimit calls get capped to the top-level limit, causing truncated messages to be silently accepted instead of rejected (36 conformance regressions).

JVM: Added a try-catch for InvalidProtocolBufferException in readTag(), making it self-contained.

Diagnostics: invalidTag() now includes the actual tag value in the error message.

Known failures: Removed 4 BadTag_VarintMoreThanTenBytes entries from native_known_failures.txt — all 5670 conformance tests now pass.

Shim version: Bumped protobuf shim from 31.1-4 to 31.1-5.


Note

Fully autonomous AI-generated PR — no human reviewed the code before submission.
Problem analysis and root cause details: KRPC-564

@ai-agent-kxrpc ai-agent-kxrpc Bot added the bug Something isn't working label Apr 13, 2026
@ai-agent-kxrpc

Copy link
Copy Markdown
Contributor Author

Internal code review

All issues identified by agent reviewers were fixed.

@ai-agent-kxrpc

Copy link
Copy Markdown
Contributor Author

CI Report

Passed

Pipeline Details
GH: Verify List Of Implicit Imports run
GH: Verify Protobuf Well-Known Types are Up-to-Date run
GH: Verify Protobuf Conformance is Up-to-Date run
GH: Verify Konan LLVM Bundle Mapping is Up-to-Date run
GH: Verify Docs Changelog run
GH: Verify Properties file run
GH: Verify Readme run
GH: Verify Platforms Table run

Failed

  • TC: Java, JS, WASM and Linux — Dependency resolution: kotlinx-rpc-protobuf-shim:31.1-3 not found | build #439
    Expected failure: shim version 31.1-3 bumped in this PR but not yet published to remote repo. Gradle fails at configuration time so JVM/JS/WASM tasks don't execute either. Local verification passed all 5670 conformance tests (JVM + native) with the locally-published shim.

@ai-agent-kxrpc
ai-agent-kxrpc Bot marked this pull request as ready for review April 13, 2026 21:30
@ai-agent-kxrpc
ai-agent-kxrpc Bot requested a review from Mr3zee April 13, 2026 21:30
Mr3zee and others added 5 commits April 14, 2026 11:19
The previous commit moved ConsumedEntireMessage() before ReadVarint64
to avoid unreliable state after fast-path varint failures. However,
ConsumedEntireMessage() only tracks the top-level stream end via
legitimate_message_end_, not PushLimit boundaries. This caused all
native sub-message decoding to fail (71 linuxX64 test failures)
because readTag() returned error instead of EOF at limit boundaries.

Fix: add BytesUntilLimit() == 0 check before ConsumedEntireMessage().
BytesUntilLimit reliably detects sub-message EOF regardless of the
legitimate_message_end_ flag. ConsumedEntireMessage() remains for
top-level EOF where no limit is pushed.

Bump protobuf shim version to 31.1-4.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…g reader

Replace the ReadVarint64 + ConsumedEntireMessage approach with manual
byte-by-byte varint parsing via ReadRaw. This fixes two issues:

1. ConsumedEntireMessage() never returns true at the top level because
   CodedInputStream has no explicit limit set (defaults to INT_MAX),
   so legitimate_message_end_ is never set when the stream ends.

2. ReadTag() returns 0 for both legitimate EOF and varint errors
   (>10-byte), with no way to distinguish them after the fact.

The new approach reads the first byte with ReadRaw(1) — failure means
genuine EOF (the BytesUntilLimit check already handled sub-message
boundaries). Success means data is available, so any subsequent varint
parsing failure is a real error. This correctly handles all cases:
top-level EOF, sub-message boundaries, >10-byte varints, overlong
encodings, and truncated varints.

Also pass the actual tag value to invalidTag() for better diagnostics.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Shim revision bump for the manual varint parsing fix in
pw_decoder_read_validated_tag.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix uninitialized tag.value read on native error path: use
  invalidTag() default (0u) instead of reading unwritten memory
- Add unit tests for >10-byte varint and overlong encoding rejection
  (previously only covered by conformance tests)
- Distinguish I/O error from EOF in first ReadRaw: if BytesUntilLimit
  > 0 but ReadRaw fails, the stream is truncated (return -1), not
  at a clean EOF boundary

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Write the (partial) decoded tag value to tag_out on error paths in
pw_decoder_read_validated_tag, so the Kotlin caller can include the
real value in ProtobufDecodingException instead of always showing 0.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@Mr3zee
Mr3zee merged commit 6a24625 into main Apr 14, 2026
13 checks passed
@Mr3zee
Mr3zee deleted the fix/KRPC-564 branch April 14, 2026 16:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant