Skip to content

Commit 682ce3e

Browse files
committed
Per-channel stderr buffering and flow control
Move extDataBuffer from WOLFSSH to WOLFSSH_CHANNEL and give stderr real back-pressure: charge the window on receipt, credit on read, never lose the credit. - Buffer stderr per channel; accumulate across packets instead of overwriting unread data. - Charge the window on receipt, replenish on read; reject data larger than the advertised window. - Route all credit through ChannelCreditWindow(): parks credit that cannot go out (mid-rekey, failed send), suppresses zero-byte adjusts. - Add wolfSSH_Channel{Id,}ReadExt / SendExt; the stream_/extended_data_ pair stays first-channel-only. - Drain SCP stderr in a loop; document the mandatory-drain contract. Issue: F-864
1 parent 2b65d14 commit 682ce3e

6 files changed

Lines changed: 1982 additions & 56 deletions

File tree

src/internal.c

Lines changed: 158 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
71797271
static 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. */
1912519249
int 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+
2016720302
int wolfSSH_TestDoUserAuthRequest(WOLFSSH* ssh, byte* buf, word32 len,
2016820303
word32* idx)
2016920304
{

0 commit comments

Comments
 (0)