Skip to content

Commit cfae5fd

Browse files
committed
Fix TLS unwrap buffering for large reads
1 parent 49f5f42 commit cfae5fd

2 files changed

Lines changed: 414 additions & 26 deletions

File tree

core/src/main/java/io/questdb/client/network/JavaTlsClientSocket.java

Lines changed: 66 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ public X509Certificate[] getAcceptedIssuers() {
8787
private SSLEngine sslEngine;
8888
private int state = STATE_EMPTY;
8989
private long unwrapInputBufferPtr;
90+
private long unwrapOutputBufferPtr;
9091
private long wrapOutputBufferPtr;
9192

9293
JavaTlsClientSocket(NetworkFacade nf, Logger log, ClientTlsConfiguration tlsConfig) {
@@ -167,60 +168,66 @@ public void of(int fd) {
167168
public int recv(long bufferPtr, int bufferLen) {
168169
assert sslEngine != null;
169170

170-
resetBufferToPointer(unwrapOutputBuffer, bufferPtr, bufferLen);
171-
unwrapOutputBuffer.position(0);
172-
173171
try {
174-
int plainBytesReceived = 0;
172+
if (unwrapOutputBuffer.position() != 0) {
173+
return drainUnwrapOutputBuffer(bufferPtr, bufferLen);
174+
}
175+
175176
for (; ; ) {
176177
int n = readFromSocket();
177178
assert unwrapInputBuffer.position() == 0 : "unwrapInputBuffer is not compacted";
178179
int bytesAvailable = unwrapInputBuffer.limit();
179180
if (n < 0 && bytesAvailable == 0) {
180-
if (plainBytesReceived == 0) {
181-
// we didn't manage to read anything from the socket, let's return the error
182-
return n;
183-
}
184-
// we have some data to return, let's return it
185-
return plainBytesReceived;
181+
return n;
186182
}
187183

188184
if (bytesAvailable == 0) {
189185
// nothing to unwrap, we are done
190-
return plainBytesReceived;
186+
return 0;
191187
}
192188

193189
SSLEngineResult result = sslEngine.unwrap(unwrapInputBuffer, unwrapOutputBuffer);
194-
plainBytesReceived += result.bytesProduced();
195190

196191
// compact the TLS buffer
197192
int bytesConsumed = result.bytesConsumed();
198193
int bytesRemaining = bytesAvailable - bytesConsumed;
199-
Vect.memcpy(unwrapInputBufferPtr, unwrapInputBufferPtr + bytesConsumed, bytesRemaining);
194+
if (bytesRemaining > 0) {
195+
Vect.memcpy(unwrapInputBufferPtr, unwrapInputBufferPtr + bytesConsumed, bytesRemaining);
196+
}
200197
unwrapInputBuffer.position(0);
201198
unwrapInputBuffer.limit(bytesRemaining);
202199

203200
switch (result.getStatus()) {
204201
case BUFFER_UNDERFLOW:
205-
// we need more data to unwrap, let's return whatever we have
206-
return plainBytesReceived;
202+
if (unwrapOutputBuffer.position() != 0) {
203+
return drainUnwrapOutputBuffer(bufferPtr, bufferLen);
204+
}
205+
// we need more data to unwrap, let's return control to the caller
206+
return 0;
207207
case BUFFER_OVERFLOW:
208208
if (unwrapOutputBuffer.position() == 0) {
209209
// not even a single byte was written to the output buffer even the buffer is empty
210-
throw new AssertionError("Output buffer too small to fit a single TLS record. This should not happen, please report as a bug.");
210+
// apparently the output buffer cannot fit even a single TLS record. let's grow it and try again!
211+
growUnwrapOutputBuffer();
212+
break;
211213
}
212-
// we have some data to return, let's return it
213-
return plainBytesReceived;
214+
return drainUnwrapOutputBuffer(bufferPtr, bufferLen);
214215
case OK:
215-
break;
216+
if (unwrapOutputBuffer.position() != 0) {
217+
return drainUnwrapOutputBuffer(bufferPtr, bufferLen);
218+
}
219+
return 0;
216220
case CLOSED:
217221
log.debug("SSL engine closed");
218222
// We received a TLS close notification from the server. We don't expect any further data from this connection.
219223
// If we have some previously unwrapped data then let's return it so the caller has a chance to process them.
220224
// If a caller calls recv() again and we have no remaining plaintext to return, we will return -1 so the
221225
// caller learned that the connection is closed.
222226
// If we have no plaintext data to return now then we can immediately indicate that we are done with the connection.
223-
return plainBytesReceived == 0 ? -1 : plainBytesReceived;
227+
if (unwrapOutputBuffer.position() == 0) {
228+
return -1;
229+
}
230+
return drainUnwrapOutputBuffer(bufferPtr, bufferLen);
224231
}
225232
}
226233
} catch (SSLException e) {
@@ -303,9 +310,12 @@ public void startTlsSession(CharSequence peerName) throws TlsSessionInitFailedEx
303310
// there cannot be underflow since wrap() during handshake does not read from the input buffer at all
304311
throw new AssertionError("Buffer underflow during TLS handshake. This should not happen. please report as a bug");
305312
case BUFFER_OVERFLOW:
306-
// in theory, this can happen if the output buffer is too small to fit a single TLS handshake record, but that would indicate
307-
// our starting buffer is too small.
308-
throw new AssertionError("Buffer overflow during TLS handshake. This should not happen, please report as a bug");
313+
if (wrapOutputBuffer.position() == 0) {
314+
// in theory, this can happen if the output buffer is too small to fit a single TLS handshake record,
315+
// but that would indicate our starting buffer is too small.
316+
growWrapOutputBuffer();
317+
}
318+
break;
309319
case OK:
310320
// wrapOutputBuffer: write mode
311321
int written = 0;
@@ -336,7 +346,12 @@ public void startTlsSession(CharSequence peerName) throws TlsSessionInitFailedEx
336346
// we need to receive more data from a socket, let's try again
337347
break;
338348
case BUFFER_OVERFLOW:
339-
throw new AssertionError("Buffer overflow during TLS handshake. This should not happen, please report as a bug");
349+
if (unwrapOutputBuffer.position() == 0) {
350+
// in theory, this can happen if the output buffer is too small to fit a single TLS handshake record,
351+
// but that would indicate our starting buffer is too small.
352+
growUnwrapOutputBuffer();
353+
}
354+
break;
340355
case OK:
341356
// good, let's see what we need to do next
342357
break;
@@ -352,7 +367,8 @@ public void startTlsSession(CharSequence peerName) throws TlsSessionInitFailedEx
352367
unwrapInputBuffer.limit(0);
353368

354369
// write mode and empty
355-
unwrapOutputBuffer.clear();
370+
unwrapOutputBuffer.position(0);
371+
unwrapOutputBuffer.limit(unwrapOutputBuffer.capacity());
356372
wrapOutputBuffer.clear();
357373
state = STATE_TLS;
358374
} catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException | IOException |
@@ -478,18 +494,42 @@ private void freeInternalBuffers() {
478494
ptrToFree = unwrapInputBufferPtr;
479495
unwrapInputBufferPtr = 0;
480496
Unsafe.free(ptrToFree, capacity, MemoryTag.NATIVE_TLS_RSS);
497+
498+
capacity = unwrapOutputBuffer.capacity();
499+
assert capacity != 0;
500+
resetBufferToPointer(unwrapOutputBuffer, 0, 0);
501+
ptrToFree = unwrapOutputBufferPtr;
502+
unwrapOutputBufferPtr = 0;
503+
Unsafe.free(ptrToFree, capacity, MemoryTag.NATIVE_TLS_RSS);
481504
}
482505
}
483506

484507
private void growWrapOutputBuffer() {
485508
wrapOutputBufferPtr = expandBuffer(wrapOutputBuffer, wrapOutputBufferPtr);
486509
}
487510

511+
private void growUnwrapOutputBuffer() {
512+
unwrapOutputBufferPtr = expandBuffer(unwrapOutputBuffer, unwrapOutputBufferPtr);
513+
}
514+
488515
private void prepareInternalBuffers() {
489516
int initialCapacity = Integer.getInteger("questdb.experimental.tls.buffersize", INITIAL_BUFFER_CAPACITY_BYTES);
490517
this.wrapOutputBufferPtr = allocateMemoryAndResetBuffer(wrapOutputBuffer, initialCapacity);
491518
this.unwrapInputBufferPtr = allocateMemoryAndResetBuffer(unwrapInputBuffer, initialCapacity);
492519
unwrapInputBuffer.flip(); // read mode
520+
this.unwrapOutputBufferPtr = allocateMemoryAndResetBuffer(unwrapOutputBuffer, initialCapacity);
521+
}
522+
523+
private int drainUnwrapOutputBuffer(long bufferPtr, int bufferLen) {
524+
unwrapOutputBuffer.flip();
525+
int oldPosition = unwrapOutputBuffer.position();
526+
int len = Math.min(bufferLen, unwrapOutputBuffer.remaining());
527+
if (len > 0) {
528+
Vect.memcpy(bufferPtr, unwrapOutputBufferPtr + oldPosition, len);
529+
}
530+
unwrapOutputBuffer.position(oldPosition + len);
531+
unwrapOutputBuffer.compact();
532+
return len;
493533
}
494534

495535
private int readFromSocket() {
@@ -544,4 +584,4 @@ private int writeToSocket(int bytesToSend) {
544584
LIMIT_FIELD_OFFSET = Unsafe.getUnsafe().objectFieldOffset(limitField);
545585
CAPACITY_FIELD_OFFSET = Unsafe.getUnsafe().objectFieldOffset(capacityField);
546586
}
547-
}
587+
}

0 commit comments

Comments
 (0)