Skip to content

Commit d8567c8

Browse files
authored
Check if input is shut down before writing (#5257)
This commit adds a wrapper socket that ensures the read end of the socket is still open before performing a {@code write()}. In TLS 1.3, it is permitted for the connection to be in a half-closed state, which is dangerous for the Apache client because it can get stuck in a state where it continues to write to the socket and potentially end up a blocked state writing to the socket indefinitely.
1 parent a3ce0df commit d8567c8

File tree

2 files changed

+83
-1
lines changed

2 files changed

+83
-1
lines changed

http-clients/apache-client/src/main/java/software/amazon/awssdk/http/apache/internal/conn/SdkTlsSocketFactory.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
2727
import org.apache.http.protocol.HttpContext;
2828
import software.amazon.awssdk.annotations.SdkInternalApi;
29+
import software.amazon.awssdk.http.apache.internal.net.InputShutdownCheckingSslSocket;
2930
import software.amazon.awssdk.http.apache.internal.net.SdkSocket;
3031
import software.amazon.awssdk.http.apache.internal.net.SdkSslSocket;
3132
import software.amazon.awssdk.utils.Logger;
@@ -65,7 +66,7 @@ public Socket connectSocket(
6566
Socket connectedSocket = super.connectSocket(connectTimeout, socket, host, remoteAddress, localAddress, context);
6667

6768
if (connectedSocket instanceof SSLSocket) {
68-
return new SdkSslSocket((SSLSocket) connectedSocket);
69+
return new InputShutdownCheckingSslSocket(new SdkSslSocket((SSLSocket) connectedSocket));
6970
}
7071

7172
return new SdkSocket(connectedSocket);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.http.apache.internal.net;
17+
18+
import java.io.FilterOutputStream;
19+
import java.io.IOException;
20+
import java.io.OutputStream;
21+
import javax.net.ssl.SSLSocket;
22+
import software.amazon.awssdk.annotations.SdkInternalApi;
23+
24+
/**
25+
* Wrapper socket that ensures the read end of the socket is still open before performing a {@code write()}. In TLS 1.3, it is
26+
* permitted for the connection to be in a half-closed state, which is dangerous for the Apache client because it can get stuck in
27+
* a state where it continues to write to the socket and potentially end up a blocked state writing to the socket indefinitely.
28+
*/
29+
@SdkInternalApi
30+
public final class InputShutdownCheckingSslSocket extends DelegateSslSocket {
31+
32+
public InputShutdownCheckingSslSocket(SSLSocket sock) {
33+
super(sock);
34+
}
35+
36+
@Override
37+
public OutputStream getOutputStream() throws IOException {
38+
return new InputShutdownCheckingOutputStream(sock.getOutputStream(), sock);
39+
}
40+
41+
private static class InputShutdownCheckingOutputStream extends FilterOutputStream {
42+
private final SSLSocket sock;
43+
44+
InputShutdownCheckingOutputStream(OutputStream out, SSLSocket sock) {
45+
super(out);
46+
this.sock = sock;
47+
}
48+
49+
@Override
50+
public void write(int b) throws IOException {
51+
checkInputShutdown();
52+
super.write(b);
53+
}
54+
55+
@Override
56+
public void write(byte[] b) throws IOException {
57+
checkInputShutdown();
58+
super.write(b);
59+
}
60+
61+
@Override
62+
public void write(byte[] b, int off, int len) throws IOException {
63+
checkInputShutdown();
64+
super.write(b, off, len);
65+
}
66+
67+
private void checkInputShutdown() throws IOException {
68+
if (sock.isInputShutdown()) {
69+
throw new IOException("Remote end is closed.");
70+
}
71+
72+
try {
73+
sock.getInputStream();
74+
} catch (IOException inputStreamException) {
75+
IOException e = new IOException("Remote end is closed.");
76+
e.addSuppressed(inputStreamException);
77+
throw e;
78+
}
79+
}
80+
}
81+
}

0 commit comments

Comments
 (0)