@@ -1533,8 +1533,7 @@ WOLFSSH* SshInit(WOLFSSH* ssh, WOLFSSH_CTX* ctx)
15331533 ssh->keyingCompletionCtx = (void*)ssh;
15341534
15351535 if (BufferInit(&ssh->inputBuffer, 0, ctx->heap) != WS_SUCCESS ||
1536- BufferInit(&ssh->outputBuffer, 0, ctx->heap) != WS_SUCCESS ||
1537- BufferInit(&ssh->extDataBuffer, 0, ctx->heap) != WS_SUCCESS) {
1536+ BufferInit(&ssh->outputBuffer, 0, ctx->heap) != WS_SUCCESS) {
15381537
15391538 wolfSSH_free(ssh);
15401539 ssh = NULL;
@@ -1553,7 +1552,6 @@ void SshResourceFree(WOLFSSH* ssh, void* heap)
15531552
15541553 ShrinkBuffer(&ssh->inputBuffer, 1);
15551554 ShrinkBuffer(&ssh->outputBuffer, 1);
1556- ShrinkBuffer(&ssh->extDataBuffer, 1);
15571555 WS_FORCEZERO(ssh->k, ssh->kSz);
15581556 HandshakeInfoFree(ssh->handshake, heap);
15591557 WS_FORCEZERO(&ssh->keys, sizeof(Keys));
@@ -3523,6 +3521,14 @@ WOLFSSH_CHANNEL* ChannelNew(WOLFSSH* ssh, byte channelType,
35233521 newChannel->inputBuffer.buffer = buffer;
35243522 newChannel->inputBuffer.bufferSz = initialWindowSz;
35253523 newChannel->inputBuffer.dynamicFlag = 1;
3524+ if (BufferInit(&newChannel->extDataBuffer, 0, heap)
3525+ != WS_SUCCESS) {
3526+ WLOG(WS_LOG_DEBUG,
3527+ "Unable to init new channel's ext data buffer");
3528+ WFREE(buffer, heap, DYNTYPE_BUFFER);
3529+ WFREE(newChannel, heap, DYNTYPE_CHANNEL);
3530+ newChannel = NULL;
3531+ }
35263532 }
35273533 else {
35283534 WLOG(WS_LOG_DEBUG, "Unable to allocate new channel's buffer");
@@ -3554,6 +3560,14 @@ void ChannelDelete(WOLFSSH_CHANNEL* channel, void* heap)
35543560 #endif /* WOLFSSH_FWD */
35553561 WFREE(channel->inputBuffer.buffer,
35563562 channel->inputBuffer.heap, DYNTYPE_BUFFER);
3563+ if (channel->extDataBuffer.length > channel->extDataBuffer.idx) {
3564+ /* The app never drained it and the channel is going away. */
3565+ WLOG(WS_LOG_INFO,
3566+ "Discarding %u bytes of unread extended data on channel %u",
3567+ channel->extDataBuffer.length - channel->extDataBuffer.idx,
3568+ channel->channel);
3569+ }
3570+ ShrinkBuffer(&channel->extDataBuffer, 1);
35573571 if (channel->command)
35583572 WFREE(channel->command, heap, DYNTYPE_STRING);
35593573 WFREE(channel, heap, DYNTYPE_CHANNEL);
@@ -7176,6 +7190,84 @@ static int DoKexDhReply(WOLFSSH* ssh, byte* buf, word32 len, word32* idx)
71767190}
71777191
71787192
7193+ /* Returns amount bytes of receive-window credit to the peer, folding in credit
7194+ * already parked on the channel. Credit that cannot reach the transport is
7195+ * parked, not dropped: no WINDOW_ADJUST may be sent mid-rekey (RFC 4253 section
7196+ * 7.1), an unbundled packet queued nothing, and a socket error can discard what
7197+ * was bundled. Credit that reached the output buffer counts as delivered. */
7198+ int ChannelCreditWindow(WOLFSSH* ssh, WOLFSSH_CHANNEL* channel, word32 amount)
7199+ {
7200+ word32 total;
7201+ int ret;
7202+ byte bundled = 0;
7203+
7204+ if (ssh == NULL || channel == NULL)
7205+ return WS_BAD_ARGUMENT;
7206+
7207+ if (amount > UINT32_MAX - channel->pendingWindowAdjust) {
7208+ WLOG(WS_LOG_ERROR, "pending window adjust would overflow");
7209+ return WS_OVERFLOW_E;
7210+ }
7211+
7212+ total = channel->pendingWindowAdjust + amount;
7213+ if (total == 0)
7214+ return WS_SUCCESS;
7215+
7216+ if (ssh->isKeying) {
7217+ channel->pendingWindowAdjust = total;
7218+ return WS_SUCCESS;
7219+ }
7220+
7221+ channel->pendingWindowAdjust = 0;
7222+ ret = SendChannelWindowAdjust(ssh, channel->channel, total, &bundled);
7223+
7224+ /* Keep the credit owed only if it never reached the transport. Once bundled
7225+ * it sits in the output buffer for a later flush, so re-parking after a
7226+ * short write (WS_WANT_WRITE) would send it twice and inflate the peer's
7227+ * window. WS_SOCKET_ERROR_E is the exception: wolfSSH_SendPacket() can
7228+ * discard what it bundled, so it stays owed. */
7229+ if (!bundled || ret == WS_SOCKET_ERROR_E) {
7230+ channel->pendingWindowAdjust = total;
7231+ WLOG(WS_LOG_ERROR,
7232+ "ChannelCreditWindow: window adjust send failed (%d) for "
7233+ "channel %u; %u bytes of credit parked for retry",
7234+ ret, channel->channel, total);
7235+ }
7236+
7237+ return ret;
7238+ }
7239+
7240+
7241+ /* Flush receive-window credit parked on the channels. Called when keying
7242+ * completes. */
7243+ static int SendPendingChannelWindowAdjust(WOLFSSH* ssh)
7244+ {
7245+ WOLFSSH_CHANNEL* cur;
7246+ int savedError;
7247+
7248+ if (ssh == NULL)
7249+ return WS_BAD_ARGUMENT;
7250+
7251+ /* Still mid-KEX: leave the credit parked until both sides finish. */
7252+ if (ssh->isKeying)
7253+ return WS_SUCCESS;
7254+
7255+ /* Save and restore ssh->error: this flush is incidental to the keying flow,
7256+ * and the caller (DoNewKeys/SendNewKeys) reports its own result. A bundled
7257+ * adjust that cannot flush in non-blocking mode sets WS_WANT_WRITE but still
7258+ * goes out on the next flush. */
7259+ savedError = ssh->error;
7260+
7261+ for (cur = ssh->channelList; cur != NULL; cur = cur->next) {
7262+ if (cur->pendingWindowAdjust != 0)
7263+ ChannelCreditWindow(ssh, cur, 0);
7264+ }
7265+
7266+ ssh->error = savedError;
7267+ return WS_SUCCESS;
7268+ }
7269+
7270+
71797271static int DoNewKeys(WOLFSSH* ssh, byte* buf, word32 len, word32* idx)
71807272{
71817273 int ret = WS_SUCCESS;
@@ -7218,6 +7310,9 @@ static int DoNewKeys(WOLFSSH* ssh, byte* buf, word32 len, word32* idx)
72187310 ssh->isKeying &= ~WOLFSSH_PEER_IS_KEYING;
72197311 HandshakeInfoFree(ssh->handshake, ssh->ctx->heap);
72207312 ssh->handshake = NULL;
7313+ /* If this was the last keying flag to clear, flush any window credit
7314+ * that was deferred during the rekey (RFC 4253 section 7.1). */
7315+ SendPendingChannelWindowAdjust(ssh);
72217316 WLOG(WS_LOG_DEBUG, "Keying completed");
72227317 if (ssh->ctx->keyingCompletionCb)
72237318 ssh->ctx->keyingCompletionCb(ssh->keyingCompletionCtx);
@@ -11153,25 +11248,45 @@ static int DoChannelData(WOLFSSH* ssh,
1115311248}
1115411249
1115511250
11156- /* deletes current buffer and updates it
11157- * return WS_SUCCESS on success */
11158- static int PutBuffer (WOLFSSH_BUFFER* buf, byte* data, word32 dataSz)
11251+ /* Appends data, preserving unread bytes. Already-read data (below idx) is
11252+ * compacted away and the new data is appended after the unread region. */
11253+ static int AppendBuffer (WOLFSSH_BUFFER* buf, byte* data, word32 dataSz)
1115911254{
1116011255 int ret;
11256+ word32 usedSz, needSz, growSz;
1116111257
11162- /* reset "used" section of buffer back to 0 */
11163- buf->length = 0;
11164- buf->idx = 0;
11258+ if (buf == NULL || data == NULL)
11259+ return WS_BAD_ARGUMENT;
1116511260
11166- if (dataSz > buf->bufferSz) {
11167- if ((ret = GrowBuffer(buf, dataSz)) != WS_SUCCESS) {
11168- return ret;
11261+ usedSz = buf->length - buf->idx;
11262+ if (dataSz > UINT32_MAX - usedSz)
11263+ return WS_OVERFLOW_E;
11264+ needSz = usedSz + dataSz;
11265+
11266+ /* GrowBuffer's size is the room wanted past the unread bytes; it reallocates
11267+ * when sz + usedSz exceeds bufferSz. Over-ask only when the allocation
11268+ * cannot hold the data, targeting double the capacity so trickled blobs cost
11269+ * amortized O(1) per byte instead of reallocating on every append. */
11270+ growSz = dataSz;
11271+ if (needSz > buf->bufferSz) {
11272+ word32 targetSz = needSz;
11273+
11274+ if (buf->bufferSz <= (UINT32_MAX / 2) &&
11275+ (buf->bufferSz * 2) > targetSz) {
11276+ targetSz = buf->bufferSz * 2;
1116911277 }
11278+ growSz = targetSz - usedSz;
1117011279 }
11171- WMEMCPY(buf->buffer, data, dataSz);
11172- buf->length = dataSz;
1117311280
11174- return WS_SUCCESS;
11281+ /* GrowBuffer compacts the consumed prefix (bytes below idx) down to the
11282+ * front. On return idx is 0 and length is the count of unread bytes. */
11283+ ret = GrowBuffer(buf, growSz);
11284+ if (ret == WS_SUCCESS) {
11285+ WMEMCPY(buf->buffer + buf->length, data, dataSz);
11286+ buf->length += dataSz;
11287+ }
11288+
11289+ return ret;
1117511290}
1117611291
1117711292
@@ -11199,24 +11314,26 @@ static int DoChannelExtendedData(WOLFSSH* ssh,
1119911314 ret = WS_INVALID_CHANID;
1120011315 else if (dataSz > channel->maxPacketSz)
1120111316 ret = WS_RECV_OVERFLOW_E;
11317+ else if (dataSz > channel->windowSz)
11318+ /* Peer sent more than the window we advertised. */
11319+ ret = WS_RECV_OVERFLOW_E;
1120211320 else if (dataTypeCode == CHANNEL_EXTENDED_DATA_STDERR) {
11203- ret = PutBuffer(&ssh ->extDataBuffer, buf + begin, dataSz);
11321+ ret = AppendBuffer(&channel ->extDataBuffer, buf + begin, dataSz);
1120411322 #ifdef DEBUG_WOLFSSH
1120511323 DumpOctetString(buf + begin, dataSz);
1120611324 #endif
11207- if (ret == WS_SUCCESS)
11208- ret = SendChannelWindowAdjust(ssh, channel->channel, dataSz);
1120911325 if (ret == WS_SUCCESS) {
11326+ channel->windowSz -= dataSz;
1121011327 ssh->lastRxId = channelId;
1121111328 ret = WS_EXTDATA;
1121211329 }
1121311330 }
1121411331 else {
1121511332 WLOG(WS_LOG_INFO, "Ignoring unknown extended data type %u",
1121611333 dataTypeCode);
11217- ret = SendChannelWindowAdjust( ssh, channel-> channel, dataSz);
11218- if (ret == WS_SUCCESS)
11219- ssh->lastRxId = channelId ;
11334+ (void)ChannelCreditWindow( ssh, channel, dataSz);
11335+ ssh->lastRxId = channelId;
11336+ ret = WS_SUCCESS ;
1122011337 }
1122111338 *idx = begin + dataSz;
1122211339 }
@@ -15033,6 +15150,10 @@ int SendNewKeys(WOLFSSH* ssh)
1503315150
1503415151 /* Clear self is keying flag */
1503515152 ssh->isKeying &= ~WOLFSSH_SELF_IS_KEYING;
15153+
15154+ /* If this was the last keying flag to clear, flush any window credit
15155+ * that was deferred during the rekey (RFC 4253 section 7.1). */
15156+ SendPendingChannelWindowAdjust(ssh);
1503615157 }
1503715158
1503815159 WLOG(WS_LOG_DEBUG, "Leaving SendNewKeys(), ret = %d", ret);
@@ -19122,8 +19243,11 @@ int SendChannelExtendedData(WOLFSSH* ssh, word32 channelId,
1912219243}
1912319244
1912419245
19246+ /* Sends a WINDOW_ADJUST for bytesToAdd. When bundled is not NULL it is set to 1
19247+ * once the packet is in the output buffer, letting a caller tell an adjust that
19248+ * was never queued from one that was queued but whose flush did not finish. */
1912519249int SendChannelWindowAdjust(WOLFSSH* ssh, word32 channelId,
19126- word32 bytesToAdd)
19250+ word32 bytesToAdd, byte* bundled )
1912719251{
1912819252 byte* output;
1912919253 word32 idx;
@@ -19164,8 +19288,14 @@ int SendChannelWindowAdjust(WOLFSSH* ssh, word32 channelId,
1916419288 ret = BundlePacket(ssh);
1916519289 }
1916619290
19167- if (ret == WS_SUCCESS)
19291+ if (ret == WS_SUCCESS) {
19292+ /* Queued. From here the send either delivers it or reports the error
19293+ * that discarded it; either way it was not lost before reaching the
19294+ * transport. */
19295+ if (bundled != NULL)
19296+ *bundled = 1;
1916819297 ret = wolfSSH_SendPacket(ssh);
19298+ }
1916919299
1917019300 WLOG(WS_LOG_DEBUG, "Leaving SendChannelWindowAdjust(), ret = %d", ret);
1917119301 return ret;
@@ -20164,6 +20294,11 @@ int wolfSSH_TestDoChannelWindowAdjust(WOLFSSH* ssh, byte* buf, word32 len,
2016420294 return DoChannelWindowAdjust(ssh, buf, len, idx);
2016520295}
2016620296
20297+ int wolfSSH_TestSendPendingChannelWindowAdjust(WOLFSSH* ssh)
20298+ {
20299+ return SendPendingChannelWindowAdjust(ssh);
20300+ }
20301+
2016720302int wolfSSH_TestDoUserAuthRequest(WOLFSSH* ssh, byte* buf, word32 len,
2016820303 word32* idx)
2016920304{
0 commit comments