Skip to content

Commit ce94531

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 ce94531

1 file changed

Lines changed: 125 additions & 0 deletions

File tree

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

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3508,4 +3508,129 @@ public void testBufferOverflowSmallOutput()
35083508

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

0 commit comments

Comments
 (0)