[
https://issues.apache.org/jira/browse/THRIFT-6148?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Dmytro Shteflyuk resolved THRIFT-6148.
--------------------------------------
Fix Version/s: 0.25.0
Resolution: Fixed
> Ruby HeaderTransport should enforce limits against complete frames
> ------------------------------------------------------------------
>
> Key: THRIFT-6148
> URL: https://issues.apache.org/jira/browse/THRIFT-6148
> Project: Thrift
> Issue Type: Bug
> Components: Ruby - Library
> Reporter: Dmytro Shteflyuk
> Assignee: Dmytro Shteflyuk
> Priority: Major
> Fix For: 0.25.0
>
> Time Spent: 20m
> Remaining Estimate: 0h
>
> h3. Problem
> Ruby {{HeaderTransport#flush}} checks {{max_frame_size}} against the
> serialized payload before constructing the Header frame. The completed frame
> also contains the fixed Header envelope, transformed payload, protocol and
> transform identifiers, key/value metadata, and alignment padding.
> The read path applies the same configured limit to the frame size declared on
> the wire. That declared size includes the complete Header frame but excludes
> its four-byte length prefix. A writer can therefore emit a frame that a
> reader configured with the same limit rejects.
> This is not double counting: each Header field and payload byte belongs to
> the declared frame exactly once, while the outer four-byte length prefix is
> excluded on both write and read.
> h3. Client impact
> Applications using the same {{max_frame_size}} policy on both peers cannot
> rely on locally written Header frames being accepted by the receiving peer.
> The mismatch is more likely when messages include metadata or transforms
> because those bytes are added after the current writer-side check.
> The writer reports successful flush even though a reader using the identical
> limit rejects the resulting frame.
> h3. Reproduction
> {code:ruby}
> require "thrift"
> limit = 20
> buffer = Thrift::MemoryBufferTransport.new
> writer = Thrift::HeaderTransport.new(buffer)
> writer.set_max_frame_size(limit)
> writer.write("x" * 10)
> writer.flush
> frame = buffer.read(buffer.available)
> puts "declared=#{frame.unpack1("N")} emitted=#{frame.bytesize}"
> reader = Thrift::HeaderTransport.new(
> Thrift::MemoryBufferTransport.new(frame)
> )
> reader.set_max_frame_size(limit)
> begin
> reader.read(10)
> rescue => error
> puts "#{error.class}: #{error.message}"
> end
> {code}
> Testing on master commit {{4d0faf31a8c06e84fe48f35556b3ccdce569dbd9}}
> produces:
> {code}
> declared=24 emitted=28
> Thrift::TransportException: Frame size 24 exceeds maximum 20
> {code}
> The declared size is 24 bytes and excludes the four-byte length prefix. The
> complete emitted buffer is therefore 28 bytes.
> h3. Expected behavior
> The writer should apply {{max_frame_size}} to the completed declared Header
> frame after transforms, metadata, and padding have been constructed. A frame
> exactly at the configured limit should be emitted, while a larger frame
> should raise {{TransportException}} before writing any bytes.
> Framed Binary/Compact compatibility modes should continue comparing their
> payload size because their four-byte length prefix is excluded from the
> declared frame. Unframed Binary/Compact modes should continue comparing the
> complete protocol message because they have no frame prefix.
> If local validation rejects a Header frame before any underlying write,
> pending one-shot headers should remain available for a subsequent write.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)