[ 
https://issues.apache.org/jira/browse/THRIFT-6181?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Jens Geyer updated THRIFT-6181:
-------------------------------
    Description: 
TAsyncMethodCall sizes the response frame from the four bytes the peer sends 
ahead of it, with no check of any kind 
(lib/java/src/main/java/org/apache/thrift/async/TAsyncMethodCall.java, 
doReadingResponseSize):
{code}
    if (sizeBuffer.remaining() == 0) {
      state = State.READING_RESPONSE_BODY;
      frameBuffer = 
ByteBuffer.allocate(TFramedTransport.decodeFrameSize(sizeBufferArray));
    }
{code}
decodeFrameSize returns a signed int, so the peer chooses any value from 
-2147483648 to 2147483647, and ByteBuffer.allocate is handed it directly. A 
negative one raises IllegalArgumentException, which is not a 
TTransportException and reaches the callback as-is; a large positive one is 
allocated, and ByteBuffer.allocate zeroes the array, so the memory is resident 
rather than reserved.

This is the odd one out among the three places lib/java reads a frame size. 
TFramedTransport.readFrame and TFastFramedTransport.readFrame both refuse a 
negative size and one over getConfiguration().getMaxFrameSize(). 
AbstractNonblockingServer.FrameBuffer refuses a size <= 0, one over 
trans_.getMaxFrameSize(), and additionally defers a frame that would push the 
server past MAX_READ_BUFFER_BYTES. Only the async client takes the number as it 
comes.

The fix applies the two checks the framed transports already apply, with the 
same messages, so that the two ways of reading a framed response agree.

Compatibility, worth a release note: maxFrameSize defaults to 16384000, so an 
async client now refuses a response frame larger than that where it previously 
allocated it. Callers who expect larger responses raise it with 
TNonblockingTransport.setMaxFrameSize, and the bound follows.

Tests are in 
lib/java/src/test/java/org/apache/thrift/async/TestTAsyncMethodCallFrameSize.java.
 They drive a real TAsyncMethodCall against a socket that answers with a frame 
size and no body, and assert the buffer that was allocated rather than only 
that the call failed -- a response body that never arrives ends the call either 
way. Two of the three fail before the change: "allocated 65536 for a frame that 
is over the maximum", and "a negative size reached ByteBuffer.allocate: 
java.lang.IllegalArgumentException: capacity < 0: (-1 < 0)". The third reads a 
frame of exactly the maximum end to end and passes either way.


  was:
TAsyncMethodCall sizes the response frame from the four bytes the peer sends 
ahead of it, with no check of any kind 
(lib/java/src/main/java/org/apache/thrift/async/TAsyncMethodCall.java, 
doReadingResponseSize):

    if (sizeBuffer.remaining() == 0) {
      state = State.READING_RESPONSE_BODY;
      frameBuffer = 
ByteBuffer.allocate(TFramedTransport.decodeFrameSize(sizeBufferArray));
    }

decodeFrameSize returns a signed int, so the peer chooses any value from 
-2147483648 to 2147483647, and ByteBuffer.allocate is handed it directly. A 
negative one raises IllegalArgumentException, which is not a 
TTransportException and reaches the callback as-is; a large positive one is 
allocated, and ByteBuffer.allocate zeroes the array, so the memory is resident 
rather than reserved.

This is the odd one out among the three places lib/java reads a frame size. 
TFramedTransport.readFrame and TFastFramedTransport.readFrame both refuse a 
negative size and one over getConfiguration().getMaxFrameSize(). 
AbstractNonblockingServer.FrameBuffer refuses a size <= 0, one over 
trans_.getMaxFrameSize(), and additionally defers a frame that would push the 
server past MAX_READ_BUFFER_BYTES. Only the async client takes the number as it 
comes.

The fix applies the two checks the framed transports already apply, with the 
same messages, so that the two ways of reading a framed response agree.

Compatibility, worth a release note: maxFrameSize defaults to 16384000, so an 
async client now refuses a response frame larger than that where it previously 
allocated it. Callers who expect larger responses raise it with 
TNonblockingTransport.setMaxFrameSize, and the bound follows.

Tests are in 
lib/java/src/test/java/org/apache/thrift/async/TestTAsyncMethodCallFrameSize.java.
 They drive a real TAsyncMethodCall against a socket that answers with a frame 
size and no body, and assert the buffer that was allocated rather than only 
that the call failed -- a response body that never arrives ends the call either 
way. Two of the three fail before the change: "allocated 65536 for a frame that 
is over the maximum", and "a negative size reached ByteBuffer.allocate: 
java.lang.IllegalArgumentException: capacity < 0: (-1 < 0)". The third reads a 
frame of exactly the maximum end to end and passes either way.



> Bound the response frame size in the Java async client
> ------------------------------------------------------
>
>                 Key: THRIFT-6181
>                 URL: https://issues.apache.org/jira/browse/THRIFT-6181
>             Project: Thrift
>          Issue Type: Bug
>          Components: Java - Library
>            Reporter: Jens Geyer
>            Priority: Major
>             Fix For: 0.25.0
>
>          Time Spent: 40m
>  Remaining Estimate: 0h
>
> TAsyncMethodCall sizes the response frame from the four bytes the peer sends 
> ahead of it, with no check of any kind 
> (lib/java/src/main/java/org/apache/thrift/async/TAsyncMethodCall.java, 
> doReadingResponseSize):
> {code}
>     if (sizeBuffer.remaining() == 0) {
>       state = State.READING_RESPONSE_BODY;
>       frameBuffer = 
> ByteBuffer.allocate(TFramedTransport.decodeFrameSize(sizeBufferArray));
>     }
> {code}
> decodeFrameSize returns a signed int, so the peer chooses any value from 
> -2147483648 to 2147483647, and ByteBuffer.allocate is handed it directly. A 
> negative one raises IllegalArgumentException, which is not a 
> TTransportException and reaches the callback as-is; a large positive one is 
> allocated, and ByteBuffer.allocate zeroes the array, so the memory is 
> resident rather than reserved.
> This is the odd one out among the three places lib/java reads a frame size. 
> TFramedTransport.readFrame and TFastFramedTransport.readFrame both refuse a 
> negative size and one over getConfiguration().getMaxFrameSize(). 
> AbstractNonblockingServer.FrameBuffer refuses a size <= 0, one over 
> trans_.getMaxFrameSize(), and additionally defers a frame that would push the 
> server past MAX_READ_BUFFER_BYTES. Only the async client takes the number as 
> it comes.
> The fix applies the two checks the framed transports already apply, with the 
> same messages, so that the two ways of reading a framed response agree.
> Compatibility, worth a release note: maxFrameSize defaults to 16384000, so an 
> async client now refuses a response frame larger than that where it 
> previously allocated it. Callers who expect larger responses raise it with 
> TNonblockingTransport.setMaxFrameSize, and the bound follows.
> Tests are in 
> lib/java/src/test/java/org/apache/thrift/async/TestTAsyncMethodCallFrameSize.java.
>  They drive a real TAsyncMethodCall against a socket that answers with a 
> frame size and no body, and assert the buffer that was allocated rather than 
> only that the call failed -- a response body that never arrives ends the call 
> either way. Two of the three fail before the change: "allocated 65536 for a 
> frame that is over the maximum", and "a negative size reached 
> ByteBuffer.allocate: java.lang.IllegalArgumentException: capacity < 0: (-1 < 
> 0)". The third reads a frame of exactly the maximum end to end and passes 
> either way.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to