@@ -110,6 +110,7 @@ public class WolfSSLEngine extends SSLEngine {
110110
111111 /* session stored (WOLFSSL_SESSION), relevant on client side */
112112 private boolean sessionStored = false ;
113+ private boolean contPartialRecord = false ;
113114
114115 /* TLS 1.3 session ticket received (on client side) */
115116 private boolean sessionTicketReceived = false ;
@@ -1289,6 +1290,47 @@ private byte[] getRecvAppDataBuf(int minSz) {
12891290 return this .recvAppDataBuf ;
12901291 }
12911292
1293+ /**
1294+ * TLS-only: peek at the record header and
1295+ * return BUFFER_UNDERFLOW before calling into
1296+ * JNI when the full record is not yet present.
1297+ * Without this, native wolfSSL consumes partial
1298+ * record bytes via the I/O callback, violating
1299+ * the JSSE contract that BUFFER_UNDERFLOW must
1300+ * report bytesConsumed() == 0. DTLS still
1301+ * relies on the native WANT_READ path.
1302+ *
1303+ * @param in input buffer
1304+ * @param inRemaining bytes remaining in input buffer.
1305+ * @param handshaking whether the function is being called during
1306+ * handshake.
1307+ *
1308+ * @return true if buffer underflow detected, false otherwise
1309+ */
1310+ private boolean peekTlsRecordHeader (ByteBuffer in , int inRemaining ) {
1311+ boolean bufferUnderflow = false ;
1312+ if (inRemaining > 0 && (this .ssl .dtls () == 0 )) {
1313+ int pos = in .position ();
1314+ if (inRemaining < TLS_RECORD_HEADER_LEN ) {
1315+ /* Not enough for TLS record header */
1316+ bufferUnderflow = true ;
1317+ } else {
1318+ /* Peek at record length from header
1319+ * bytes 3-4 (big-endian) */
1320+ int recLen =
1321+ ((in .get (pos + TLS_RECORD_LEN_HI_OFF )
1322+ & 0xFF ) << 8 ) |
1323+ (in .get (pos + TLS_RECORD_LEN_LO_OFF )
1324+ & 0xFF );
1325+ if (recLen <= WolfSSL .MAX_RECORD_SIZE &&
1326+ inRemaining < TLS_RECORD_HEADER_LEN + recLen ) {
1327+ bufferUnderflow = true ;
1328+ }
1329+ }
1330+ }
1331+ return bufferUnderflow ;
1332+ }
1333+
12921334 @ Override
12931335 public synchronized SSLEngineResult unwrap (ByteBuffer in , ByteBuffer out )
12941336 throws SSLException {
@@ -1313,6 +1355,7 @@ public synchronized SSLEngineResult unwrap(ByteBuffer in, ByteBuffer[] out,
13131355 long dtlsPrevDropCount = 0 ;
13141356 long dtlsCurrDropCount = 0 ;
13151357 int prevSessionTicketCount = 0 ;
1358+ boolean bufferUnderflow = false ;
13161359 final int tmpRet ;
13171360
13181361 /* Set initial status for SSLEngineResult return */
@@ -1463,38 +1506,22 @@ else if (hs == SSLEngineResult.HandshakeStatus.NEED_WRAP &&
14631506 if (this .handshakeFinished == false ) {
14641507 WolfSSLDebug .log (getClass (), WolfSSLDebug .INFO ,
14651508 () -> "starting or continuing handshake" );
1466- ret = DoHandshake (false );
1509+ if (!this .contPartialRecord ) {
1510+ bufferUnderflow =
1511+ this .getUseClientMode () &&
1512+ peekTlsRecordHeader (in , inRemaining );
1513+ }
1514+ if (bufferUnderflow ) {
1515+ status = SSLEngineResult .Status .BUFFER_UNDERFLOW ;
1516+ }
1517+ else {
1518+ ret = DoHandshake (false );
1519+ }
14671520 }
14681521 else {
1469- /* TLS-only: peek at the record header and
1470- * return BUFFER_UNDERFLOW before calling into
1471- * JNI when the full record is not yet present.
1472- * Without this, native wolfSSL consumes partial
1473- * record bytes via the I/O callback, violating
1474- * the JSSE contract that BUFFER_UNDERFLOW must
1475- * report bytesConsumed() == 0. DTLS still
1476- * relies on the native WANT_READ path. */
1477- boolean bufferUnderflow = false ;
1478- if (inRemaining > 0 && (this .ssl .dtls () == 0 )) {
1479- synchronized (netDataLock ) {
1480- int pos = in .position ();
1481- if (inRemaining < TLS_RECORD_HEADER_LEN ) {
1482- /* Not enough for TLS record header */
1483- bufferUnderflow = true ;
1484- } else {
1485- /* Peek at record length from header
1486- * bytes 3-4 (big-endian) */
1487- int recLen =
1488- ((in .get (pos + TLS_RECORD_LEN_HI_OFF )
1489- & 0xFF ) << 8 ) |
1490- (in .get (pos + TLS_RECORD_LEN_LO_OFF )
1491- & 0xFF );
1492- if (inRemaining <
1493- TLS_RECORD_HEADER_LEN + recLen ) {
1494- bufferUnderflow = true ;
1495- }
1496- }
1497- }
1522+ if (!this .contPartialRecord ) {
1523+ bufferUnderflow =
1524+ peekTlsRecordHeader (in , inRemaining );
14981525 }
14991526
15001527 /* Serve stashed data from previous
@@ -1629,8 +1656,8 @@ else if (inRemaining > 0 &&
16291656 }
16301657 } /* end DoHandshake() / RecvAppData() */
16311658
1632- if (outBoundOpen == false || this .closeNotifySent ||
1633- this .closeNotifyReceived ) {
1659+ if (( outBoundOpen == false || this .closeNotifySent ||
1660+ this .closeNotifyReceived ) && ! bufferUnderflow ) {
16341661 /* Mark SSLEngine status as CLOSED */
16351662 status = SSLEngineResult .Status .CLOSED ;
16361663 /* Handshake has finished and SSLEngine is closed,
@@ -1697,7 +1724,8 @@ else if (ret < 0 &&
16971724 }
16981725 }
16991726 else if (!this .handshakeFinished && (ret == 0 ) &&
1700- (err == 0 || err == WolfSSL .SSL_ERROR_ZERO_RETURN )) {
1727+ (err == 0 || err == WolfSSL .SSL_ERROR_ZERO_RETURN )
1728+ && !bufferUnderflow ) {
17011729
17021730 boolean gotCloseNotify = false ;
17031731 synchronized (ioLock ) {
@@ -2650,6 +2678,16 @@ protected synchronized int internalRecvCb(ByteBuffer toRead, int sz) {
26502678 if (this .ssl .dtls () == 1 && this .handshakeFinished ) {
26512679 this .nativeWantsToRead = 1 ;
26522680 }
2681+ /* wolfSSL asked for bytes but we have none remaining
2682+ * (netData is valid but empty). Mark as mid-record so
2683+ * the next unwrap() skips the TLS header peek and
2684+ * passes the continuation bytes directly to DoHandshake.
2685+ * Only set when netData is non-null (netData==null means
2686+ * we are inside wrap(), not a mid-record scenario).
2687+ * Prevents stale state from being used. */
2688+ if (this .netData != null ) {
2689+ this .contPartialRecord = true ;
2690+ }
26532691 return WolfSSL .WOLFSSL_CBIO_ERR_WANT_READ ;
26542692 }
26552693
@@ -2676,6 +2714,8 @@ protected synchronized int internalRecvCb(ByteBuffer toRead, int sz) {
26762714 toRead .position (toReadPos );
26772715 }
26782716
2717+ this .contPartialRecord = (max < sz );
2718+
26792719 return max ;
26802720 }
26812721 }
0 commit comments