Skip to content

Commit 3166183

Browse files
committed
Bug 39116277 - [38668667->25.09] Extend TCP Proxy TcpProcessor thread Spinning on 0-Byte Read on Closed Sockets causing High CPU
Bug 38668667 - Extend TCP Proxy TcpProcessor thread Spinning on 0-Byte Read on Closed Sockets causing High CPU. Issue: - When a client sends a partial TLS record and closes the connection, the server receives incomplete encrypted data. - SSLEngine.unwrap() cannot decrypt the record and returns BUFFER_UNDERFLOW because more bytes are required. - Since the socket has already reached EOS, no additional data can arrive to complete the TLS record. - The remaining encrypted bytes may keep the channel marked readable, causing the connection to be processed repeatedly. Fix: - Updated the read logic to return -1 when the socket has reached EOS, no bytes were produced for the caller buffer (cbFill == 0), and no decrypted bytes remain in the clear buffer. - The code also checks that fillClearBuffer() returns 0, ensuring that no additional bytes can be decrypted from the remaining encrypted data. - This indicates that the remaining data if present in Encyrpted Buffer is likely a partial TLS record that cannot be completed. - Returning -1 signals end-of-stream and allows the TcpProcessor to close the connection, preventing repeated processing of the same socket. [git-p4: depot-paths = "//dev/coherence-ce/main/": change = 119310]
1 parent 3728bff commit 3166183

File tree

2 files changed

+101
-13
lines changed

2 files changed

+101
-13
lines changed

prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/ssl/SSLSocketChannel.java

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2000, 2024, Oracle and/or its affiliates.
2+
* Copyright (c) 2000, 2026, Oracle and/or its affiliates.
33
*
44
* Licensed under the Universal Permissive License v 1.0 as shown at
55
* https://oss.oracle.com/licenses/upl.
@@ -436,15 +436,13 @@ protected long readInternal(ByteBuffer[] aBuffDst, int ofBuff, int cBuff)
436436
// COH-21678 - check both clear and encrypted as the SSL engine may consume the decrypt resulting in an empty clear buffer
437437
boolean fMore = f_buffClearIn.hasRemaining() || fillClearBuffer() > 0 || f_buffEncIn.hasRemaining();
438438
markKeysReadable(fMore);
439-
440-
if (cbFill == 0 && cbRead == -1 && !fMore)
441-
{
442-
return -1;
443-
}
444-
else
445-
{
446-
return cbFill;
447-
}
439+
// COH-33073 - Prevent TcpProcessor spinning on closed sockets.
440+
// If no bytes were written to the destination buffer and the socket has reached EOS,
441+
// check fillClearBuffer() to ensure any remaining encrypted data cannot still be decrypted.
442+
// If it produces no bytes and the clear buffer is empty, the remaining data is likely a
443+
// partial TLS record that cannot be completed, so return -1 and allow TcpProcessor to close
444+
// the connection.
445+
return (cbFill == 0 && cbRead == -1 && !(f_buffClearIn.hasRemaining()) && fillClearBuffer() == 0) ? -1 : cbFill;
448446
}
449447

450448

prj/test/functional/extend/src/main/java/extend/DistExtendDirectSSLTests.java

Lines changed: 93 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,28 @@
11
/*
2-
* Copyright (c) 2000, 2022, Oracle and/or its affiliates.
2+
* Copyright (c) 2000, 2026, Oracle and/or its affiliates.
33
*
44
* Licensed under the Universal Permissive License v 1.0 as shown at
55
* https://oss.oracle.com/licenses/upl.
66
*/
77

88
package extend;
99

10+
import com.oracle.bedrock.runtime.coherence.CoherenceClusterMember;
11+
import com.oracle.bedrock.testsupport.deferred.Eventually;
12+
13+
import java.io.InputStream;
14+
import java.io.OutputStream;
15+
import java.net.Socket;
16+
import java.net.SocketException;
17+
import java.net.SocketTimeoutException;
18+
import java.util.Properties;
1019

1120
import org.junit.AfterClass;
1221
import org.junit.BeforeClass;
22+
import org.junit.Test;
1323

24+
import static org.hamcrest.CoreMatchers.is;
25+
import static org.junit.Assert.assertTrue;
1426

1527
/**
1628
* A collection of functional tests for Coherence*Extend that use the
@@ -38,9 +50,10 @@ public DistExtendDirectSSLTests()
3850
* Initialize the test class.
3951
*/
4052
@BeforeClass
41-
public static void startup()
53+
public static void _startup()
4254
{
43-
startCacheServerWithProxy("DistExtendDirectSSLTests", "extend", "server-cache-config-ssl.xml");
55+
setupProps();
56+
s_member = startCacheServerWithProxy("DistExtendDirectSSLTests", "extend", "server-cache-config-ssl.xml");
4457
}
4558

4659
/**
@@ -51,4 +64,81 @@ public static void shutdown()
5164
{
5265
stopCacheServer("DistExtendDirectSSLTests");
5366
}
67+
68+
/**
69+
* COH-33073 - ensure SSL-configured ExtendTcpProxy (TcpProcessor path)
70+
* cleanly closes a connection when it receives malformed TLS bytes
71+
* that cause BUFFER_UNDERFLOW / SSL decode failure.
72+
*/
73+
@Test
74+
public void testProxyClosesConnectionOnIncompleteTlsRecord()
75+
throws Exception
76+
{
77+
Eventually.assertDeferred(() -> s_member.isServiceRunning("ExtendTcpProxyService"), is(true));
78+
79+
Properties props = System.getProperties();
80+
String sHost = props.getProperty("test.extend.address.remote", "127.0.0.1");
81+
int nPort = Integer.parseInt(props.getProperty("test.extend.port"));
82+
83+
byte[] abPayload = new byte[]
84+
{
85+
(byte) 0x16, (byte) 0x03, (byte) 0x03, (byte) 0x00,
86+
(byte) 0x50, (byte) 0x01, (byte) 0x02, (byte) 0x03
87+
};
88+
89+
boolean fClosed = false;
90+
91+
try (Socket socket = new Socket(sHost, nPort))
92+
{
93+
socket.setSoTimeout(2000);
94+
95+
OutputStream out = socket.getOutputStream();
96+
out.write(abPayload);
97+
out.flush();
98+
99+
// half close client -> server
100+
socket.shutdownOutput();
101+
102+
InputStream in = socket.getInputStream();
103+
byte[] buffer = new byte[32];
104+
105+
int attempts = 3;
106+
107+
while (attempts-- > 0)
108+
{
109+
try
110+
{
111+
int n = in.read(buffer);
112+
if (n == -1)
113+
{
114+
// graceful close (FIN)
115+
System.out.println("Server closed socket (FIN) after malformed TLS payload");
116+
fClosed = true;
117+
break;
118+
}
119+
continue;
120+
}
121+
catch (SocketException e)
122+
{
123+
// connection reset (RST) also means server closed the connection
124+
System.out.println("Server reset socket (RST) after malformed TLS payload: " + e.getMessage());
125+
fClosed = true;
126+
break;
127+
}
128+
catch (SocketTimeoutException e)
129+
{
130+
// server kept connection open
131+
continue;
132+
}
133+
}
134+
}
135+
136+
assertTrue("ExtendTcpProxyService did not close the connection for malformed TLS input", fClosed);
137+
138+
Eventually.assertDeferred(() -> s_member.isServiceRunning("ExtendTcpProxyService"), is(true));
139+
}
140+
141+
// ----- data members ---------------------------------------------------
142+
143+
private static CoherenceClusterMember s_member;
54144
}

0 commit comments

Comments
 (0)