@@ -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 ;
@@ -142,11 +143,6 @@ public class WolfSSLEngine extends SSLEngine {
142143 * and expanded only when a larger output window requires it. */
143144 private byte [] recvAppDataBuf = new byte [WolfSSL .MAX_RECORD_SIZE ];
144145
145- /* TLS record header is: type(1) + version(2) + length(2) */
146- private static final int TLS_RECORD_HEADER_LEN = 5 ;
147- private static final int TLS_RECORD_LEN_HI_OFF = 3 ;
148- private static final int TLS_RECORD_LEN_LO_OFF = 4 ;
149-
150146 /* Default size of internalIOSendBuf, 16k to match TLS record size.
151147 * TODO - add upper bound on I/O send buf resize allocations. */
152148 private static final int INTERNAL_IOSEND_BUF_SZ = WolfSSL .MAX_RECORD_SIZE ;
@@ -365,7 +361,7 @@ private List<SNIServerName> parseRequestedServerNamesFromNetData() {
365361
366362 synchronized (netDataLock ) {
367363 if (this .netData == null ||
368- this .netData .remaining () < TLS_RECORD_HEADER_LEN ) {
364+ this .netData .remaining () < WolfSSL . TLS_RECORD_HEADER_LEN ) {
369365 return null ;
370366 }
371367 in = this .netData .asReadOnlyBuffer ();
@@ -1289,6 +1285,55 @@ private byte[] getRecvAppDataBuf(int minSz) {
12891285 return this .recvAppDataBuf ;
12901286 }
12911287
1288+ /**
1289+ * TLS-only: peek at the record header and
1290+ * return BUFFER_UNDERFLOW before calling into
1291+ * JNI when the full record is not yet present.
1292+ * Without this, native wolfSSL consumes partial
1293+ * record bytes via the I/O callback, violating
1294+ * the JSSE contract that BUFFER_UNDERFLOW must
1295+ * report bytesConsumed() == 0.
1296+ *
1297+ * @param in input buffer.
1298+ * @param inRemaining bytes remaining in input buffer.
1299+ *
1300+ * @return true if buffer underflow detected, false otherwise.
1301+ */
1302+ private boolean peekTlsRecordHeader (ByteBuffer in , int inRemaining ) {
1303+ boolean bufferUnderflow = false ;
1304+ /* DTLS still relies on the native WANT_READ path. */
1305+ if (inRemaining > 0 && (this .ssl .dtls () == 0 )) {
1306+ int pos = in .position ();
1307+ if (inRemaining < WolfSSL .TLS_RECORD_HEADER_LEN ) {
1308+ /* Not enough for TLS record header */
1309+ bufferUnderflow = true ;
1310+ } else {
1311+ /* Check if header is plausible.
1312+ * Content type between 20-23 and
1313+ * version major corresponds to TLS. */
1314+ if ((in .get (pos ) & 0xFF ) >= WolfSSL .TLS_RECORD_CONT_MIN &&
1315+ (in .get (pos ) & 0xFF ) <= WolfSSL .TLS_RECORD_CONT_MAX &&
1316+ (in .get (pos + WolfSSL .TLS_RECORD_VERS_OFF )
1317+ & 0xFF ) == WolfSSL .TLS_RECORD_VERS_MAJOR ) {
1318+ /* Peek at record length from header
1319+ * bytes 3-4 (big-endian). */
1320+ int recLen =
1321+ ((in .get (pos + WolfSSL .TLS_RECORD_LEN_HI_OFF )
1322+ & 0xFF ) << 8 ) |
1323+ (in .get (pos + WolfSSL .TLS_RECORD_LEN_LO_OFF )
1324+ & 0xFF );
1325+ /* Prevent overflow of inRemaining. Fall through if recLen
1326+ * exceeds max packet buffer size. */
1327+ if (recLen <= this .getSession ().getPacketBufferSize () &&
1328+ inRemaining < WolfSSL .TLS_RECORD_HEADER_LEN + recLen ) {
1329+ bufferUnderflow = true ;
1330+ }
1331+ }
1332+ }
1333+ }
1334+ return bufferUnderflow ;
1335+ }
1336+
12921337 @ Override
12931338 public synchronized SSLEngineResult unwrap (ByteBuffer in , ByteBuffer out )
12941339 throws SSLException {
@@ -1313,6 +1358,7 @@ public synchronized SSLEngineResult unwrap(ByteBuffer in, ByteBuffer[] out,
13131358 long dtlsPrevDropCount = 0 ;
13141359 long dtlsCurrDropCount = 0 ;
13151360 int prevSessionTicketCount = 0 ;
1361+ boolean bufferUnderflow = false ;
13161362 final int tmpRet ;
13171363
13181364 /* Set initial status for SSLEngineResult return */
@@ -1463,38 +1509,22 @@ else if (hs == SSLEngineResult.HandshakeStatus.NEED_WRAP &&
14631509 if (this .handshakeFinished == false ) {
14641510 WolfSSLDebug .log (getClass (), WolfSSLDebug .INFO ,
14651511 () -> "starting or continuing handshake" );
1466- ret = DoHandshake (false );
1512+ if (!this .contPartialRecord ) {
1513+ bufferUnderflow =
1514+ this .getUseClientMode () &&
1515+ peekTlsRecordHeader (in , inRemaining );
1516+ }
1517+ if (bufferUnderflow ) {
1518+ status = SSLEngineResult .Status .BUFFER_UNDERFLOW ;
1519+ }
1520+ else {
1521+ ret = DoHandshake (false );
1522+ }
14671523 }
14681524 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- }
1525+ if (!this .contPartialRecord ) {
1526+ bufferUnderflow =
1527+ peekTlsRecordHeader (in , inRemaining );
14981528 }
14991529
15001530 /* Serve stashed data from previous
@@ -1629,8 +1659,8 @@ else if (inRemaining > 0 &&
16291659 }
16301660 } /* end DoHandshake() / RecvAppData() */
16311661
1632- if (outBoundOpen == false || this .closeNotifySent ||
1633- this .closeNotifyReceived ) {
1662+ if (( outBoundOpen == false || this .closeNotifySent ||
1663+ this .closeNotifyReceived )) {
16341664 /* Mark SSLEngine status as CLOSED */
16351665 status = SSLEngineResult .Status .CLOSED ;
16361666 /* Handshake has finished and SSLEngine is closed,
@@ -1697,7 +1727,8 @@ else if (ret < 0 &&
16971727 }
16981728 }
16991729 else if (!this .handshakeFinished && (ret == 0 ) &&
1700- (err == 0 || err == WolfSSL .SSL_ERROR_ZERO_RETURN )) {
1730+ (err == 0 || err == WolfSSL .SSL_ERROR_ZERO_RETURN )
1731+ && !bufferUnderflow ) {
17011732
17021733 boolean gotCloseNotify = false ;
17031734 synchronized (ioLock ) {
@@ -2650,6 +2681,11 @@ protected synchronized int internalRecvCb(ByteBuffer toRead, int sz) {
26502681 if (this .ssl .dtls () == 1 && this .handshakeFinished ) {
26512682 this .nativeWantsToRead = 1 ;
26522683 }
2684+ /* Buffer exhausted mid-record: skip the header peek on the
2685+ * next unwrap() to pass continuation bytes to DoHandshake. */
2686+ if (this .netData != null && this .netData .remaining () == 0 ) {
2687+ this .contPartialRecord = true ;
2688+ }
26532689 return WolfSSL .WOLFSSL_CBIO_ERR_WANT_READ ;
26542690 }
26552691
@@ -2676,6 +2712,8 @@ protected synchronized int internalRecvCb(ByteBuffer toRead, int sz) {
26762712 toRead .position (toReadPos );
26772713 }
26782714
2715+ this .contPartialRecord = (max < sz );
2716+
26792717 return max ;
26802718 }
26812719 }
0 commit comments