8239798: SSLSocket closes socket both socket endpoints on a SocketTimeoutException

Reviewed-by: xuelei
This commit is contained in:
Alexey Bakhtin 2020-03-11 19:14:08 +03:00 committed by Andrew Brygin
parent 6275aee690
commit 14e37ba3df
5 changed files with 128 additions and 113 deletions

View File

@ -436,6 +436,8 @@ public final class SSLSocketImpl
if (!conContext.isNegotiated) {
readHandshakeRecord();
}
} catch (InterruptedIOException iioe) {
handleException(iioe);
} catch (IOException ioe) {
throw conContext.fatal(Alert.HANDSHAKE_FAILURE,
"Couldn't kickstart handshaking", ioe);
@ -1374,12 +1376,11 @@ public final class SSLSocketImpl
}
} catch (SSLException ssle) {
throw ssle;
} catch (InterruptedIOException iioe) {
// don't change exception in case of timeouts or interrupts
throw iioe;
} catch (IOException ioe) {
if (!(ioe instanceof SSLException)) {
throw new SSLException("readHandshakeRecord", ioe);
} else {
throw ioe;
}
throw new SSLException("readHandshakeRecord", ioe);
}
}
@ -1440,6 +1441,9 @@ public final class SSLSocketImpl
}
} catch (SSLException ssle) {
throw ssle;
} catch (InterruptedIOException iioe) {
// don't change exception in case of timeouts or interrupts
throw iioe;
} catch (IOException ioe) {
if (!(ioe instanceof SSLException)) {
throw new SSLException("readApplicationRecord", ioe);

View File

@ -1,5 +1,6 @@
/*
* Copyright (c) 1996, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2020, Azul Systems, Inc. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -26,6 +27,7 @@
package sun.security.ssl;
import java.io.EOFException;
import java.io.InterruptedIOException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@ -47,37 +49,31 @@ import sun.security.ssl.SSLCipher.SSLReadCipher;
final class SSLSocketInputRecord extends InputRecord implements SSLRecord {
private InputStream is = null;
private OutputStream os = null;
private final byte[] temporary = new byte[1024];
private final byte[] header = new byte[headerSize];
private int headerOff = 0;
// Cache for incomplete record body.
private ByteBuffer recordBody = ByteBuffer.allocate(1024);
private boolean formatVerified = false; // SSLv2 ruled out?
// Cache for incomplete handshake messages.
private ByteBuffer handshakeBuffer = null;
private boolean hasHeader = false; // Had read the record header
SSLSocketInputRecord(HandshakeHash handshakeHash) {
super(handshakeHash, SSLReadCipher.nullTlsReadCipher());
}
@Override
int bytesInCompletePacket() throws IOException {
if (!hasHeader) {
// read exactly one record
try {
int really = read(is, temporary, 0, headerSize);
if (really < 0) {
// EOF: peer shut down incorrectly
return -1;
}
} catch (EOFException eofe) {
// The caller will handle EOF.
return -1;
}
hasHeader = true;
// read header
try {
readHeader();
} catch (EOFException eofe) {
// The caller will handle EOF.
return -1;
}
byte byteZero = temporary[0];
byte byteZero = header[0];
int len = 0;
/*
@ -93,9 +89,9 @@ final class SSLSocketInputRecord extends InputRecord implements SSLRecord {
* Last sanity check that it's not a wild record
*/
if (!ProtocolVersion.isNegotiable(
temporary[1], temporary[2], false, false)) {
header[1], header[2], false, false)) {
throw new SSLException("Unrecognized record version " +
ProtocolVersion.nameOf(temporary[1], temporary[2]) +
ProtocolVersion.nameOf(header[1], header[2]) +
" , plaintext connection?");
}
@ -109,8 +105,8 @@ final class SSLSocketInputRecord extends InputRecord implements SSLRecord {
/*
* One of the SSLv3/TLS message types.
*/
len = ((temporary[3] & 0xFF) << 8) +
(temporary[4] & 0xFF) + headerSize;
len = ((header[3] & 0xFF) << 8) +
(header[4] & 0xFF) + headerSize;
} else {
/*
* Must be SSLv2 or something unknown.
@ -121,11 +117,11 @@ final class SSLSocketInputRecord extends InputRecord implements SSLRecord {
*/
boolean isShort = ((byteZero & 0x80) != 0);
if (isShort && ((temporary[2] == 1) || (temporary[2] == 4))) {
if (isShort && ((header[2] == 1) || (header[2] == 4))) {
if (!ProtocolVersion.isNegotiable(
temporary[3], temporary[4], false, false)) {
header[3], header[4], false, false)) {
throw new SSLException("Unrecognized record version " +
ProtocolVersion.nameOf(temporary[3], temporary[4]) +
ProtocolVersion.nameOf(header[3], header[4]) +
" , plaintext connection?");
}
@ -138,9 +134,9 @@ final class SSLSocketInputRecord extends InputRecord implements SSLRecord {
//
// int mask = (isShort ? 0x7F : 0x3F);
// len = ((byteZero & mask) << 8) +
// (temporary[1] & 0xFF) + (isShort ? 2 : 3);
// (header[1] & 0xFF) + (isShort ? 2 : 3);
//
len = ((byteZero & 0x7F) << 8) + (temporary[1] & 0xFF) + 2;
len = ((byteZero & 0x7F) << 8) + (header[1] & 0xFF) + 2;
} else {
// Gobblygook!
throw new SSLException(
@ -160,34 +156,41 @@ final class SSLSocketInputRecord extends InputRecord implements SSLRecord {
return null;
}
if (!hasHeader) {
// read exactly one record
int really = read(is, temporary, 0, headerSize);
if (really < 0) {
throw new EOFException("SSL peer shut down incorrectly");
// read header
readHeader();
Plaintext[] plaintext = null;
boolean cleanInBuffer = true;
try {
if (!formatVerified) {
formatVerified = true;
/*
* The first record must either be a handshake record or an
* alert message. If it's not, it is either invalid or an
* SSLv2 message.
*/
if ((header[0] != ContentType.HANDSHAKE.id) &&
(header[0] != ContentType.ALERT.id)) {
plaintext = handleUnknownRecord();
}
}
hasHeader = true;
}
Plaintext plaintext = null;
if (!formatVerified) {
formatVerified = true;
/*
* The first record must either be a handshake record or an
* alert message. If it's not, it is either invalid or an
* SSLv2 message.
*/
if ((temporary[0] != ContentType.HANDSHAKE.id) &&
(temporary[0] != ContentType.ALERT.id)) {
hasHeader = false;
return handleUnknownRecord(temporary);
// The record header should has consumed.
if (plaintext == null) {
plaintext = decodeInputRecord();
}
} catch(InterruptedIOException e) {
// do not clean header and recordBody in case of Socket Timeout
cleanInBuffer = false;
throw e;
} finally {
if (cleanInBuffer) {
headerOff = 0;
recordBody.clear();
}
}
// The record header should has consumed.
hasHeader = false;
return decodeInputRecord(temporary);
return plaintext;
}
@Override
@ -200,9 +203,7 @@ final class SSLSocketInputRecord extends InputRecord implements SSLRecord {
this.os = outputStream;
}
// Note that destination may be null
private Plaintext[] decodeInputRecord(
byte[] header) throws IOException, BadPaddingException {
private Plaintext[] decodeInputRecord() throws IOException, BadPaddingException {
byte contentType = header[0]; // pos: 0
byte majorVersion = header[1]; // pos: 1
byte minorVersion = header[2]; // pos: 2
@ -227,30 +228,27 @@ final class SSLSocketInputRecord extends InputRecord implements SSLRecord {
}
//
// Read a complete record.
// Read a complete record and store in the recordBody
// recordBody is used to cache incoming record and restore in case of
// read operation timedout
//
ByteBuffer destination = ByteBuffer.allocate(headerSize + contentLen);
int dstPos = destination.position();
destination.put(temporary, 0, headerSize);
while (contentLen > 0) {
int howmuch = Math.min(temporary.length, contentLen);
int really = read(is, temporary, 0, howmuch);
if (really < 0) {
throw new EOFException("SSL peer shut down incorrectly");
if (recordBody.position() == 0) {
if (recordBody.capacity() < contentLen) {
recordBody = ByteBuffer.allocate(contentLen);
}
destination.put(temporary, 0, howmuch);
contentLen -= howmuch;
recordBody.limit(contentLen);
} else {
contentLen = recordBody.remaining();
}
destination.flip();
destination.position(dstPos + headerSize);
readFully(contentLen);
recordBody.flip();
if (SSLLogger.isOn && SSLLogger.isOn("record")) {
SSLLogger.fine(
"READ: " +
ProtocolVersion.nameOf(majorVersion, minorVersion) +
" " + ContentType.nameOf(contentType) + ", length = " +
destination.remaining());
recordBody.remaining());
}
//
@ -259,7 +257,7 @@ final class SSLSocketInputRecord extends InputRecord implements SSLRecord {
ByteBuffer fragment;
try {
Plaintext plaintext =
readCipher.decrypt(contentType, destination, null);
readCipher.decrypt(contentType, recordBody, null);
fragment = plaintext.fragment;
contentType = plaintext.contentType;
} catch (BadPaddingException bpe) {
@ -361,8 +359,7 @@ final class SSLSocketInputRecord extends InputRecord implements SSLRecord {
};
}
private Plaintext[] handleUnknownRecord(
byte[] header) throws IOException, BadPaddingException {
private Plaintext[] handleUnknownRecord() throws IOException, BadPaddingException {
byte firstByte = header[0];
byte thirdByte = header[2];
@ -404,32 +401,29 @@ final class SSLSocketInputRecord extends InputRecord implements SSLRecord {
}
int msgLen = ((header[0] & 0x7F) << 8) | (header[1] & 0xFF);
ByteBuffer destination = ByteBuffer.allocate(headerSize + msgLen);
destination.put(temporary, 0, headerSize);
msgLen -= 3; // had read 3 bytes of content as header
while (msgLen > 0) {
int howmuch = Math.min(temporary.length, msgLen);
int really = read(is, temporary, 0, howmuch);
if (really < 0) {
throw new EOFException("SSL peer shut down incorrectly");
if (recordBody.position() == 0) {
if (recordBody.capacity() < (headerSize + msgLen)) {
recordBody = ByteBuffer.allocate(headerSize + msgLen);
}
destination.put(temporary, 0, howmuch);
msgLen -= howmuch;
recordBody.limit(headerSize + msgLen);
recordBody.put(header, 0, headerSize);
} else {
msgLen = recordBody.remaining();
}
destination.flip();
msgLen -= 3; // had read 3 bytes of content as header
readFully(msgLen);
recordBody.flip();
/*
* If we can map this into a V3 ClientHello, read and
* hash the rest of the V2 handshake, turn it into a
* V3 ClientHello message, and pass it up.
*/
destination.position(2); // exclude the header
handshakeHash.receive(destination);
destination.position(0);
recordBody.position(2); // exclude the header
handshakeHash.receive(recordBody);
recordBody.position(0);
ByteBuffer converted = convertToClientHello(destination);
ByteBuffer converted = convertToClientHello(recordBody);
if (SSLLogger.isOn && SSLLogger.isOn("packet")) {
SSLLogger.fine(
@ -449,28 +443,42 @@ final class SSLSocketInputRecord extends InputRecord implements SSLRecord {
}
}
// Read the exact bytes of data, otherwise, return -1.
private static int read(InputStream is,
byte[] buffer, int offset, int len) throws IOException {
int n = 0;
while (n < len) {
int readLen = is.read(buffer, offset + n, len - n);
if (readLen < 0) {
if (SSLLogger.isOn && SSLLogger.isOn("packet")) {
SSLLogger.fine("Raw read: EOF");
}
return -1;
// Read the exact bytes of data, otherwise, throw IOException.
private int readFully(int len) throws IOException {
int end = len + recordBody.position();
int off = recordBody.position();
try {
while (off < end) {
off += read(is, recordBody.array(), off, end - off);
}
} finally {
recordBody.position(off);
}
return len;
}
// Read SSE record header, otherwise, throw IOException.
private int readHeader() throws IOException {
while (headerOff < headerSize) {
headerOff += read(is, header, headerOff, headerSize - headerOff);
}
return headerSize;
}
private static int read(InputStream is, byte[] buf, int off, int len) throws IOException {
int readLen = is.read(buf, off, len);
if (readLen < 0) {
if (SSLLogger.isOn && SSLLogger.isOn("packet")) {
ByteBuffer bb = ByteBuffer.wrap(buffer, offset + n, readLen);
SSLLogger.fine("Raw read", bb);
SSLLogger.fine("Raw read: EOF");
}
n += readLen;
throw new EOFException("SSL peer shut down incorrectly");
}
return n;
if (SSLLogger.isOn && SSLLogger.isOn("packet")) {
ByteBuffer bb = ByteBuffer.wrap(buf, off, readLen);
SSLLogger.fine("Raw read", bb);
}
return readLen;
}
// Try to use up the input stream without impact the performance too much.

View File

@ -27,6 +27,7 @@ package sun.security.ssl;
import java.io.EOFException;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.nio.ByteBuffer;
import javax.crypto.AEADBadTagException;
import javax.crypto.BadPaddingException;
@ -136,6 +137,9 @@ interface SSLTransport {
} catch (EOFException eofe) {
// rethrow EOFException, the call will handle it if neede.
throw eofe;
} catch (InterruptedIOException iioe) {
// don't close the Socket in case of timeouts or interrupts.
throw iioe;
} catch (IOException ioe) {
throw context.fatal(Alert.UNEXPECTED_MESSAGE, ioe);
}

View File

@ -26,8 +26,7 @@
/*
* @test
* @bug 4836493
* @ignore need further evaluation
* @bug 4836493 8239798
* @summary Socket timeouts for SSLSockets causes data corruption.
* @run main/othervm ClientTimeout
*/

View File

@ -36,7 +36,7 @@
import javax.net.ssl.*;
import java.io.*;
import java.net.InetAddress;
import java.net.*;
public class SSLExceptionForIOIssue implements SSLContextTemplate {
@ -139,7 +139,7 @@ public class SSLExceptionForIOIssue implements SSLContextTemplate {
} catch (SSLProtocolException | SSLHandshakeException sslhe) {
clientException = sslhe;
System.err.println("unexpected client exception: " + sslhe);
} catch (SSLException ssle) {
} catch (SSLException | SocketTimeoutException ssle) {
// the expected exception, ignore it
System.err.println("expected client exception: " + ssle);
} catch (Exception e) {