Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions src/main/java/org/jruby/ext/openssl/SSLSocket.java
Original file line number Diff line number Diff line change
Expand Up @@ -863,9 +863,9 @@ public int read(final ByteBuffer dst, final boolean blocking) throws IOException

private int read(final ByteBuffer dst, final boolean blocking, final boolean exception) throws IOException {
if ( initialHandshake ) return 0;
if ( engine.isInboundDone() ) return -1;

if ( ! appReadData.hasRemaining() ) {
if ( engine.isInboundDone() ) return -1;
final int appBytesProduced = readAndUnwrap(blocking, exception);
if (appBytesProduced == -1 || appBytesProduced == 0 || isWouldBlockResult(appBytesProduced)) {
return appBytesProduced;
Expand All @@ -880,7 +880,7 @@ private int read(final ByteBuffer dst, final boolean blocking, final boolean exc
private int readAndUnwrap(final boolean blocking, final boolean exception) throws IOException {
final int bytesRead = socketChannelImpl().read(netReadData);
if ( bytesRead == -1 ) {
if ( ! netReadData.hasRemaining() ||
if ( netReadData.position() == 0 ||
( status == SSLEngineResult.Status.BUFFER_UNDERFLOW ) ) {
closeInbound();
return -1;
Expand Down Expand Up @@ -916,16 +916,16 @@ private int readAndUnwrap(final boolean blocking, final boolean exception) throw
status = result.getStatus();
handshakeStatus = result.getHandshakeStatus();

if ( bytesRead == -1 && ! netReadData.hasRemaining() ) {
// now it's safe to call closeInbound().
if ( bytesRead == -1 && ! netReadData.hasRemaining() && appReadData.position() == 0 ) {
// Deliver decrypted data first; report an unclean EOF on the next read.
closeInbound();
}
if ( status == SSLEngineResult.Status.CLOSED ) {
return -1;
}

netReadData.compact();
appReadData.flip();
if ( status == SSLEngineResult.Status.CLOSED && ! appReadData.hasRemaining() ) {
return -1;
}
if ( ! initialHandshake && (
handshakeStatus == SSLEngineResult.HandshakeStatus.NEED_TASK ||
handshakeStatus == SSLEngineResult.HandshakeStatus.NEED_WRAP ||
Expand Down
42 changes: 42 additions & 0 deletions test/ssl/test_ssl.rb
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,48 @@ def test_unexpected_eof_is_not_accepted_by_default
tcp_server&.close rescue nil
end

def test_unexpected_eof_delivers_buffered_data_first
[:sysread, :read_nonblock].each do |read_method|
server_ctx = OpenSSL::SSL::SSLContext.new
server_ctx.cert = @svr_cert
server_ctx.key = @svr_key
server_ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION
tcp_server = TCPServer.new("127.0.0.1", 0)
ssl_server = OpenSSL::SSL::SSLServer.new(tcp_server, server_ctx)
records = ["first record", "second record"]

server_thread = Thread.new do
peer = ssl_server.accept
records.each { |record| peer.write(record) }
peer.flush
peer.to_io.close
end

begin
client_ctx = OpenSSL::SSL::SSLContext.new
client_ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE
client_ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION
sock = TCPSocket.new("127.0.0.1", tcp_server.addr[1])
ssl = OpenSSL::SSL::SSLSocket.new(sock, client_ctx)
ssl.connect
# Queue both TLS records and the transport EOF before reading any plaintext.
server_thread.value

payload = records.join
received = +""
received << ssl.public_send(read_method, 1) until received.bytesize == payload.bytesize
assert_equal payload, received
assert_raise(OpenSSL::SSL::SSLError) { ssl.public_send(read_method, 1) }
ensure
ssl&.close rescue nil
sock&.close rescue nil
server_thread&.join rescue nil
ssl_server&.close rescue nil
tcp_server&.close rescue nil
end
end
end

def test_unexpected_eof_can_be_ignored_with_option
server_ctx = OpenSSL::SSL::SSLContext.new
server_ctx.cert = @svr_cert
Expand Down
Loading