|
26 | 26 | import com.wolfssl.provider.jsse.WolfSSLProvider; |
27 | 27 | import java.io.EOFException; |
28 | 28 | import java.io.IOException; |
| 29 | +import java.lang.reflect.Field; |
29 | 30 | import java.net.InetAddress; |
30 | 31 | import java.nio.ByteBuffer; |
31 | 32 | import java.nio.channels.ServerSocketChannel; |
@@ -3508,4 +3509,129 @@ public void testBufferOverflowSmallOutput() |
3508 | 3509 |
|
3509 | 3510 | pass("\t\t... passed"); |
3510 | 3511 | } |
| 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 | + |
3511 | 3637 | } |
0 commit comments