Dmytro Shteflyuk created THRIFT-6148:
----------------------------------------

             Summary: 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


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)

Reply via email to