Merge pull request #360 from JeremiahM37/fenrir-fixes-3

WolfSSLEngine ByteBuffer offset/bounds fixes and JNI arrayOffset honoring
pull/364/head
Chris Conlon 2026-04-29 13:31:12 -06:00 committed by GitHub
commit 57f35cff0c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 216 additions and 13 deletions

View File

@ -67,6 +67,7 @@ jmethodID g_bufferPositionMethodId = NULL;
jmethodID g_bufferLimitMethodId = NULL;
jmethodID g_bufferHasArrayMethodId = NULL;
jmethodID g_bufferArrayMethodId = NULL;
jmethodID g_bufferArrayOffsetMethodId = NULL;
jmethodID g_bufferSetPositionMethodId = NULL;
jmethodID g_verifyCallbackMethodId = NULL;
@ -185,6 +186,12 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved)
return JNI_ERR;
}
g_bufferArrayOffsetMethodId = (*env)->GetMethodID(env, byteBufferClass,
"arrayOffset", "()I");
if (g_bufferArrayOffsetMethodId == NULL) {
return JNI_ERR;
}
g_bufferSetPositionMethodId = (*env)->GetMethodID(env, byteBufferClass,
"position", "(I)Ljava/nio/Buffer;");
if (g_bufferSetPositionMethodId == NULL) {
@ -236,6 +243,7 @@ JNIEXPORT void JNICALL JNI_OnUnload(JavaVM* vm, void* reserved)
g_bufferLimitMethodId = NULL;
g_bufferHasArrayMethodId = NULL;
g_bufferArrayMethodId = NULL;
g_bufferArrayOffsetMethodId = NULL;
g_bufferSetPositionMethodId = NULL;
g_verifyCallbackMethodId = NULL;
}

View File

@ -1303,6 +1303,7 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_write__JLjava_nio_ByteBuf
int ret = BAD_FUNC_ARG;
int maxInputSz;
int inSz = length;
int arrayOffset = 0;
byte* data = NULL;
WOLFSSL* ssl = (WOLFSSL*)(uintptr_t)sslPtr;
jbyteArray bufArr = NULL;
@ -1333,6 +1334,15 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_write__JLjava_nio_ByteBuf
return SSL_FAILURE;
}
/* Honor arrayOffset() for sliced/duplicated array-backed
* ByteBuffers, where logical position 0 maps to backing
* array index arrayOffset() */
arrayOffset = (int)(*jenv)->CallIntMethod(jenv, buf,
g_bufferArrayOffsetMethodId);
if ((*jenv)->ExceptionCheck(jenv)) {
return SSL_FAILURE;
}
/* Get array elements */
data = (byte *)(*jenv)->GetByteArrayElements(jenv, bufArr, NULL);
if (data == NULL) {
@ -1356,8 +1366,8 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_write__JLjava_nio_ByteBuf
}
}
ret = SSLWriteNonblockingWithSelectPoll(ssl, data + position,
(int)inSz, (int)timeout);
ret = SSLWriteNonblockingWithSelectPoll(ssl,
data + arrayOffset + position, (int)inSz, (int)timeout);
/* release memory if using array mode */
if (hasArray) {
@ -1530,6 +1540,7 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_read__JLjava_nio_ByteBuff
int size = 0;
int maxOutputSz;
int outSz = length;
int arrayOffset = 0;
byte* data = NULL;
WOLFSSL* ssl = (WOLFSSL*)(uintptr_t)sslPtr;
jbyteArray bufArr = NULL;
@ -1560,6 +1571,15 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_read__JLjava_nio_ByteBuff
return SSL_FAILURE;
}
/* Honor arrayOffset() for sliced/duplicated array-backed
* ByteBuffers, where logical position 0 maps to backing
* array index arrayOffset() */
arrayOffset = (int)(*jenv)->CallIntMethod(jenv, buf,
g_bufferArrayOffsetMethodId);
if ((*jenv)->ExceptionCheck(jenv)) {
return SSL_FAILURE;
}
/* Get array elements */
data = (byte *)(*jenv)->GetByteArrayElements(jenv, bufArr, NULL);
if (data == NULL) {
@ -1583,8 +1603,8 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_read__JLjava_nio_ByteBuff
}
}
size = SSLReadNonblockingWithSelectPoll(ssl, data + position,
outSz, (int)timeout);
size = SSLReadNonblockingWithSelectPoll(ssl,
data + arrayOffset + position, outSz, (int)timeout);
/* Release array elements if using array-backed buffer.
* Note: DirectByteBuffer doesn't need releasing data */

View File

@ -42,6 +42,7 @@ extern jmethodID g_bufferPositionMethodId; /* ByteBuffer.position() */
extern jmethodID g_bufferLimitMethodId; /* ByteBuffer.limit() */
extern jmethodID g_bufferHasArrayMethodId; /* ByteBuffer.hasArray() */
extern jmethodID g_bufferArrayMethodId; /* ByteBuffer.array() */
extern jmethodID g_bufferArrayOffsetMethodId; /* ByteBuffer.arrayOffset() */
extern jmethodID g_bufferSetPositionMethodId; /* ByteBuffer.position(int) */
extern jmethodID g_verifyCallbackMethodId; /* WolfSSLVerifyCallback.verifyCallback */

View File

@ -742,8 +742,8 @@ public class WolfSSLEngine extends SSLEngine {
/* Get total input data size, store input array positions */
for (i = ofst; i < ofst + len; i++) {
totalIn += in[i].remaining();
pos[i] = in[i].position();
limit[i] = in[i].limit();
pos[i - ofst] = in[i].position();
limit[i - ofst] = in[i].limit();
}
/* Allocate static buffer for application data, clear before use */
@ -765,7 +765,7 @@ public class WolfSSLEngine extends SSLEngine {
in[i].limit(in[i].position() + bufChunk); /* set limit */
this.staticAppDataBuf.put(in[i]); /* get data */
inputLeft -= bufChunk;
in[i].limit(limit[i]); /* reset limit */
in[i].limit(limit[i - ofst]); /* reset limit */
if (inputLeft == 0) {
break; /* reached data size needed, stop reading */
@ -786,7 +786,7 @@ public class WolfSSLEngine extends SSLEngine {
if (ret <= 0) {
/* error, reset in[] positions for next call */
for (i = ofst; i < ofst + len; i++) {
in[i].position(pos[i]);
in[i].position(pos[i - ofst]);
}
}
@ -828,7 +828,7 @@ public class WolfSSLEngine extends SSLEngine {
throw new IndexOutOfBoundsException();
}
for (i = ofst; i < len; ++i) {
for (i = ofst; i < ofst + len; ++i) {
if (in[i] == null) {
throw new SSLException("SSLEngine.wrap() bad arguments");
}
@ -850,7 +850,7 @@ public class WolfSSLEngine extends SSLEngine {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
() -> "setUseClientMode: " +
this.engineHelper.getUseClientMode());
for (i = 0; i < len; i++) {
for (i = ofst; i < ofst + len; i++) {
final int idx = i;
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
() -> "ByteBuffer in["+idx+"].remaining(): " +
@ -1392,7 +1392,7 @@ public class WolfSSLEngine extends SSLEngine {
throw new IndexOutOfBoundsException();
}
for (i = ofst; i < length; ++i) {
for (i = ofst; i < ofst + length; ++i) {
if (out[i] == null) {
throw new IllegalArgumentException(
"SSLEngine.unwrap() bad arguments");
@ -1434,7 +1434,7 @@ public class WolfSSLEngine extends SSLEngine {
() -> "in.position(): " + in.position());
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
() -> "in.limit(): " + in.limit());
for (i = 0; i < length; i++) {
for (i = ofst; i < ofst + length; i++) {
final int idx = i;
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
() -> "out["+idx+"].remaining(): " +
@ -2093,7 +2093,7 @@ public class WolfSSLEngine extends SSLEngine {
/* If handshake has not started yet, close inBound as well */
if (needInit) {
inBoundOpen = true;
inBoundOpen = false;
}
/* Update status based on internal state. Some calling applications

View File

@ -70,6 +70,7 @@ import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.atomic.AtomicIntegerArray;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
@ -3347,5 +3348,76 @@ public class WolfSSLEngineTest {
fail("drained output does not match injected queue");
}
}
/* Regression: closeOutbound() before handshake must also close
* inbound, otherwise isInboundDone() never returns true. */
@Test
public void testCloseOutboundBeforeHandshake() throws Exception {
this.ctx = tf.createSSLContext("TLS", engineProvider);
SSLEngine e = this.ctx.createSSLEngine();
e.setUseClientMode(true);
e.closeOutbound();
assertTrue(e.isOutboundDone());
assertTrue(e.isInboundDone());
}
/* Regression for wrap(ByteBuffer[], ofst, len, out) when ofst > 0:
* pos[]/limit[] OOB and null-check loop bound. */
@Test
public void testWrapWithBufferArrayOffset() throws Exception {
this.ctx = tf.createSSLContext("TLS", engineProvider);
SSLEngine server = this.ctx.createSSLEngine();
SSLEngine client = this.ctx.createSSLEngine("wolfSSL test", 11111);
server.setUseClientMode(false);
client.setUseClientMode(true);
server.beginHandshake();
client.beginHandshake();
assertEquals(0, tf.testConnection(server, client, null, null, "x"));
byte[] payload = "real-payload".getBytes();
ByteBuffer[] in = {ByteBuffer.wrap("DECOY".getBytes()),
ByteBuffer.wrap(payload)};
ByteBuffer net = ByteBuffer.allocateDirect(
client.getSession().getPacketBufferSize());
SSLEngineResult r = client.wrap(in, 1, 1, net);
assertEquals(SSLEngineResult.Status.OK, r.getStatus());
assertEquals(0, in[0].position());
assertEquals(payload.length, in[1].position());
net.flip();
ByteBuffer plain = ByteBuffer.allocate(
server.getSession().getApplicationBufferSize());
assertEquals(SSLEngineResult.Status.OK,
server.unwrap(net, plain).getStatus());
plain.flip();
byte[] got = new byte[plain.remaining()];
plain.get(got);
assertArrayEquals(payload, got);
}
/* Direct regression: wrap() null-check must reach in[ofst+len-1]. */
@Test(expected = SSLException.class)
public void testWrapRejectsNullAtOffset() throws Exception {
this.ctx = tf.createSSLContext("TLS", engineProvider);
SSLEngine c = this.ctx.createSSLEngine("wolfSSL test", 11111);
c.setUseClientMode(true);
ByteBuffer[] in = {ByteBuffer.wrap("x".getBytes()), null};
c.wrap(in, 1, 1, ByteBuffer.allocateDirect(
c.getSession().getPacketBufferSize()));
}
/* Direct regression: unwrap() readOnly-check must reach
* out[ofst+length-1]. */
@Test(expected = java.nio.ReadOnlyBufferException.class)
public void testUnwrapRejectsReadOnlyAtOffset() throws Exception {
this.ctx = tf.createSSLContext("TLS", engineProvider);
SSLEngine s = this.ctx.createSSLEngine();
s.setUseClientMode(false);
ByteBuffer[] out = {ByteBuffer.allocate(64),
ByteBuffer.allocate(64).asReadOnlyBuffer()};
s.unwrap(ByteBuffer.allocateDirect(
s.getSession().getPacketBufferSize()), out, 1, 1);
}
}

View File

@ -4210,5 +4210,107 @@ public class WolfSSLSessionTest {
}
}
}
/* Regression: read(ByteBuffer) must honor arrayOffset() so a
* sliced array-backed buffer reads into backing[arrayOffset+pos),
* not backing[pos). */
@Test
public void test_WolfSSLSession_readSlicedByteBuffer() throws Exception {
final ServerSocket srvSocket = new ServerSocket(0);
final WolfSSLContext srvCtx = createAndSetupWolfSSLContext(
srvCert, srvKey, WolfSSL.SSL_FILETYPE_PEM, cliCert,
WolfSSL.SSLv23_ServerMethod());
WolfSSLContext cliCtx = createAndSetupWolfSSLContext(
cliCert, cliKey, WolfSSL.SSL_FILETYPE_PEM, caCert,
WolfSSL.SSLv23_ClientMethod());
final byte[] payload = "sliced-buf-payload".getBytes();
ExecutorService es = Executors.newSingleThreadExecutor();
Future<Void> srv = es.submit(() -> {
try (Socket s = srvSocket.accept()) {
WolfSSLSession ss = new WolfSSLSession(srvCtx);
ss.setFd(s);
int r;
int e;
do {
r = ss.accept();
e = ss.getError(r);
} while (r != WolfSSL.SSL_SUCCESS &&
(e == WolfSSL.SSL_ERROR_WANT_READ ||
e == WolfSSL.SSL_ERROR_WANT_WRITE));
ss.write(payload, payload.length, 0);
ss.shutdownSSL();
ss.freeSSL();
}
return null;
});
Socket cliSock = null;
WolfSSLSession cliSes = null;
try {
cliSock = new Socket(InetAddress.getLoopbackAddress(),
srvSocket.getLocalPort());
cliSes = new WolfSSLSession(cliCtx);
cliSes.setFd(cliSock);
int r;
int e;
do {
r = cliSes.connect();
e = cliSes.getError(r);
} while (r != WolfSSL.SSL_SUCCESS &&
(e == WolfSSL.SSL_ERROR_WANT_READ ||
e == WolfSSL.SSL_ERROR_WANT_WRITE));
int prefix = 64;
ByteBuffer parent = ByteBuffer.allocate(256);
byte[] backing = parent.array();
byte sentinel = (byte) 0xA5;
Arrays.fill(backing, sentinel);
parent.position(prefix);
ByteBuffer slice = parent.slice();
assertEquals(prefix, slice.arrayOffset());
int total = 0;
while (total < payload.length) {
int n = cliSes.read(slice, payload.length - total, 5000);
if (n > 0) {
total += n;
continue;
}
int err = cliSes.getError(n);
if (err == WolfSSL.SSL_ERROR_WANT_READ ||
err == WolfSSL.SSL_ERROR_WANT_WRITE) {
continue;
}
fail("cliSes.read() failed: ret=" + n + " err=" + err +
" total=" + total + "/" + payload.length);
}
for (int i = 0; i < prefix; i++) {
assertEquals("backing[" + i + "] corrupted",
sentinel, backing[i]);
}
assertArrayEquals(payload, Arrays.copyOfRange(backing,
prefix, prefix + payload.length));
assertEquals(payload.length, slice.position());
cliSes.shutdownSSL();
} finally {
try {
srv.get(10, TimeUnit.SECONDS);
} finally {
es.shutdownNow();
if (cliSes != null) {
cliSes.freeSSL();
}
if (cliSock != null) {
cliSock.close();
}
srvSocket.close();
cliCtx.free();
srvCtx.free();
}
}
}
}