[ 
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)

Reply via email to