Skip to content

Commit 42187b8

Browse files
committed
JSSE: add unit test for instance when multiple records are queued and partial drain of CopyOutPacket() is necesssary
1 parent 5cf3d75 commit 42187b8

1 file changed

Lines changed: 126 additions & 0 deletions

File tree

src/test/com/wolfssl/provider/jsse/test/WolfSSLEngineTest.java

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import com.wolfssl.provider.jsse.WolfSSLProvider;
2727
import java.io.EOFException;
2828
import java.io.IOException;
29+
import java.lang.reflect.Field;
2930
import java.net.InetAddress;
3031
import java.nio.ByteBuffer;
3132
import java.nio.channels.ServerSocketChannel;
@@ -3508,4 +3509,129 @@ public void testBufferOverflowSmallOutput()
35083509

35093510
pass("\t\t... passed");
35103511
}
3512+
3513+
@Test
3514+
public void testWrapPartialDrainOffsetUpdate()
3515+
throws NoSuchProviderException, NoSuchAlgorithmException,
3516+
KeyManagementException, KeyStoreException,
3517+
CertificateException, IOException,
3518+
UnrecoverableKeyException,
3519+
NoSuchFieldException, IllegalAccessException {
3520+
3521+
/* Regression test for issue 3241: CopyOutPacket() must decrement
3522+
* internalIOSendBufOffset after a partial drain, otherwise stale
3523+
* ciphertext beyond the shifted region gets re-emitted on the
3524+
* next wrap() call and corrupts the TLS session.
3525+
*
3526+
* The partial-drain branch requires
3527+
* internalIOSendBufOffset > out.remaining() >= packetBufferSize
3528+
* which the getPacketBufferSize() precheck in wrap() makes
3529+
* unreachable with a single queued record. Reflection is used to
3530+
* inject a marker byte sequence spanning more than one
3531+
* packetBufferSize worth of queued data, simulating the
3532+
* multi-record burst (TLS 1.3 NewSessionTicket / alert +
3533+
* handshake fragment) condition called out in the issue. */
3534+
System.out.print("\tTesting wrap() partial drain offset");
3535+
3536+
this.ctx = tf.createSSLContext("TLS", engineProvider);
3537+
SSLEngine server = this.ctx.createSSLEngine();
3538+
SSLEngine client = this.ctx.createSSLEngine("wolfSSL test", 11111);
3539+
3540+
server.setUseClientMode(false);
3541+
server.setNeedClientAuth(false);
3542+
client.setUseClientMode(true);
3543+
3544+
server.beginHandshake();
3545+
client.beginHandshake();
3546+
3547+
int ret = tf.testConnection(server, client, null, null,
3548+
"partial drain test");
3549+
if (ret != 0) {
3550+
error("\t... failed");
3551+
fail("failed to create connection");
3552+
}
3553+
3554+
/* Inject a known marker sequence into the client's internal
3555+
* send buffer. Size is packetBufferSize + 1000 so the first
3556+
* CopyOutPacket drains packetBufferSize bytes and leaves 1000
3557+
* bytes queued. */
3558+
int packetSz = client.getSession().getPacketBufferSize();
3559+
int queuedSz = packetSz + 1000;
3560+
byte[] marker = new byte[queuedSz];
3561+
for (int i = 0; i < queuedSz; i++) {
3562+
marker[i] = (byte)((i * 31) & 0xFF);
3563+
}
3564+
3565+
Field bufField =
3566+
WolfSSLEngine.class.getDeclaredField("internalIOSendBuf");
3567+
Field bufSzField =
3568+
WolfSSLEngine.class.getDeclaredField("internalIOSendBufSz");
3569+
Field offField =
3570+
WolfSSLEngine.class.getDeclaredField(
3571+
"internalIOSendBufOffset");
3572+
bufField.setAccessible(true);
3573+
bufSzField.setAccessible(true);
3574+
offField.setAccessible(true);
3575+
3576+
bufField.set(client, marker);
3577+
bufSzField.setInt(client, queuedSz);
3578+
offField.setInt(client, queuedSz);
3579+
3580+
/* First wrap: out buffer sized exactly to packetBufferSize so
3581+
* the guard passes but CopyOutPacket partial-drains. */
3582+
ByteBuffer empty = ByteBuffer.allocate(0);
3583+
ByteBuffer firstOut = ByteBuffer.allocate(packetSz);
3584+
SSLEngineResult result = client.wrap(empty, firstOut);
3585+
3586+
if (result.bytesProduced() != packetSz) {
3587+
error("\t... failed");
3588+
fail("first wrap expected " + packetSz +
3589+
" produced, got " + result.bytesProduced());
3590+
}
3591+
3592+
/* Fix-sensitive check: unfixed code leaves offset at queuedSz. */
3593+
int offAfterFirst = offField.getInt(client);
3594+
if (offAfterFirst != queuedSz - packetSz) {
3595+
error("\t... failed");
3596+
fail("internalIOSendBufOffset not decremented after " +
3597+
"partial drain: expected " + (queuedSz - packetSz) +
3598+
", got " + offAfterFirst);
3599+
}
3600+
3601+
/* Second wrap: must drain only the remaining 1000 bytes.
3602+
* Unfixed code would produce packetSz bytes again, re-emitting
3603+
* the stale tail of the buffer. */
3604+
ByteBuffer secondOut = ByteBuffer.allocate(packetSz);
3605+
result = client.wrap(empty, secondOut);
3606+
3607+
int expectedRemainder = queuedSz - packetSz;
3608+
if (result.bytesProduced() != expectedRemainder) {
3609+
error("\t... failed");
3610+
fail("second wrap expected " + expectedRemainder +
3611+
" produced (remainder only), got " +
3612+
result.bytesProduced() +
3613+
" — stale bytes re-sent after partial drain");
3614+
}
3615+
3616+
if (offField.getInt(client) != 0) {
3617+
error("\t... failed");
3618+
fail("internalIOSendBufOffset not reset to 0 after " +
3619+
"remainder drained");
3620+
}
3621+
3622+
/* Full integrity check: concatenated drained output must equal
3623+
* the original injected marker exactly. */
3624+
firstOut.flip();
3625+
secondOut.flip();
3626+
byte[] drained = new byte[queuedSz];
3627+
firstOut.get(drained, 0, packetSz);
3628+
secondOut.get(drained, packetSz, expectedRemainder);
3629+
if (!Arrays.equals(marker, drained)) {
3630+
error("\t... failed");
3631+
fail("drained output does not match injected queue");
3632+
}
3633+
3634+
pass("\t... passed");
3635+
}
3636+
35113637
}

0 commit comments

Comments
 (0)