Skip to content

Commit 0e28002

Browse files
committed
JSSE: extract peek logic to internal function. Adds flag to allow
skipping TLS record peek if continuing with the same TLS record. Update bufferUnderflow guards. move/add TLS record header constants to WolfSSL check header bytes for plausible record header before peeking for bufferUnderflow
1 parent 4d7a3ea commit 0e28002

3 files changed

Lines changed: 105 additions & 39 deletions

File tree

native/com_wolfssl_WolfSSL.h

Lines changed: 14 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/java/com/wolfssl/WolfSSL.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,20 @@ public enum TLS_VERSION {
349349
/** Maximum SSL record size (16KB) as defined by the protocol. */
350350
public static final int MAX_RECORD_SIZE = 16384;
351351

352+
/** TLS record header is: type(1) + version(2) + length(2) */
353+
public static final int TLS_RECORD_HEADER_LEN = 5;
354+
/** TLS record header higher 3 bytes offset */
355+
public static final int TLS_RECORD_LEN_HI_OFF = 3;
356+
/** TLS record header lower 4 bytes offset */
357+
public static final int TLS_RECORD_LEN_LO_OFF = 4;
358+
/** TLS record header protocol version offset */
359+
public static final int TLS_RECORD_VERS_OFF = 1;
360+
/** TLS record header protocol version major */
361+
public static final int TLS_RECORD_VERS_MAJOR = 0x03;
362+
/** TLS record header valid content type (RFC 8446 B.1) is between 20 - 24 */
363+
public static final int TLS_RECORD_CONT_MIN = 20;
364+
public static final int TLS_RECORD_CONT_MAX = 24;
365+
352366
/* ------------------ TLS extension specific ------------------------ */
353367
/** SNI Host name type, for UseSNI() */
354368
public static final int WOLFSSL_SNI_HOST_NAME = 0;

src/java/com/wolfssl/provider/jsse/WolfSSLEngine.java

Lines changed: 77 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)