Extended (stderr) data: buffering and flow control - #1054
Conversation
There was a problem hiding this comment.
Pull request overview
This PR updates wolfSSH’s handling of SSH_MSG_CHANNEL_EXTENDED_DATA (stderr) to (1) accumulate unread extended data instead of overwriting it, (2) ignore unknown extended-data type codes per RFC behavior, and (3) apply channel window back-pressure semantics for stderr by charging the receive window on receipt and replenishing it only when the application drains the buffered stderr.
Changes:
- Accumulate buffered stderr across multiple packets and preserve unread data ordering.
- Implement receive-window accounting for stderr (charge on receipt; replenish on
wolfSSH_extended_data_read()), including a documented single-session limitation for multi-channel stderr buffering. - Add unit test coverage for accumulation, window back-pressure, overflow rejection, unknown-type ignore, and cross-channel fail-safe behavior.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| wolfssh/ssh.h | Documents the public contract for draining stderr and the single-channel buffering limitation. |
| wolfssh/internal.h | Adds internal tracking (extDataChannelId) and documents shared-buffer/window semantics. |
| wolfssh/error.h | Clarifies WS_INVALID_EXTDATA is retained for compatibility but no longer produced. |
| src/internal.c | Changes stderr buffering to append; adds window charging and unknown-type ignore behavior. |
| src/ssh.c | Updates wolfSSH_extended_data_read() to replenish window on drain and gate sends during KEX. |
| tests/unit.c | Adds a comprehensive unit test for extended-data accumulation + window flow control. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
aidangarske
left a comment
There was a problem hiding this comment.
🐺 Skoll Code Review
Overall recommendation: REQUEST_CHANGES
Findings: 2 total — 2 posted, 0 skipped
Posted findings
- [High] Deferred discarded extended data is not charged against the channel receive window during rekey —
src/internal.c:10296-10334 - [Low] Extended-data read hides nonblocking WINDOW_ADJUST back-pressure —
src/ssh.c:1471-1475
Review generated by Skoll.
aidangarske
left a comment
There was a problem hiding this comment.
Skoll Multi-Scan Review
Modes: review + review-security + bugsOverall recommendation: REQUEST_CHANGES
Findings: 5 total — 5 posted, 0 skipped
4 finding(s) posted as inline comments (see file-level comments below)
1 finding(s) not tied to a diff line (full detail below)
Posted findings
- [High] [review+review-security+bugs] wolfSSH_worker masks extended-data events during rekey —
src/ssh.c:2860-2867 - [High] [review+review-security] Parked receive-window credit is counted as advertised before the peer is credited —
src/ssh.c:3196-3204 - [Medium] [bugs] Ignored extended-data credit errors are reported as success —
src/internal.c:11336-11340 - [Low] stream_read returns WS_ERROR for non-head extended data but leaves ssh-error == WS_EXTDATA —
src/ssh.c:1257-1266
Findings not tied to a diff line
wolfSSH_extended_data_read now rejects outSz==0 with WS_BAD_ARGUMENT (behavior change)
File: src/ssh.c:1456-1470 (via _ChannelReadExt guard at src/ssh.c:3176)
Function: wolfSSH_extended_data_read / _ChannelReadExt
Severity: Low
The rewritten wolfSSH_extended_data_read() delegates to _ChannelReadExt(), whose first guard is if (channel == NULL || buf == NULL || bufSz == 0) return WS_BAD_ARGUMENT;. The previous implementation computed bufSz = min(outSz, length - idx) and, for outSz == 0, copied 0 bytes and returned 0. So a caller that passes outSz == 0 now gets a negative error code (WS_BAD_ARGUMENT) instead of 0. A drain loop written as while ((n = wolfSSH_extended_data_read(ssh, buf, room)) > 0) where room can legitimately reach 0 (e.g. a full application buffer) would previously terminate cleanly on the 0 return and now terminates on a negative value that reads as a failure. The in-tree callers (apps/wolfssh/wolfssh.c, examples/client/client.c, src/wolfscp.c) all pass SIZE-1 > 0, so they are unaffected, and the change is asserted intentionally by test_ChannelReadExtBadArgs. Flagging so the behavior change is a conscious, documented decision for external API consumers.
Recommendation: Confirm the outSz==0 -> WS_BAD_ARGUMENT change is intended and document it for API consumers, or special-case outSz==0 to return 0 to preserve prior behavior.
Referenced code: src/ssh.c:1456-1470 (via _ChannelReadExt guard at src/ssh.c:3176) (2 lines)
Review generated by Skoll
7a4e98b to
281b79a
Compare
Ignore unknown CHANNEL_EXTENDED_DATA type codes per RFC 4254/4251 instead of returning WS_INVALID_EXTDATA. - Consume and discard the unknown payload, replenish the window. - Keep WS_INVALID_EXTDATA defined for compatibility; no longer produced. Issue: F-2078
wolfSSL-Fenrir-bot
left a comment
There was a problem hiding this comment.
Fenrir Automated Review — PR #1054
Scan targets checked: wolfssh-bugs, wolfssh-src
Findings: 2
2 finding(s) posted as inline comments (see file-level comments below)
This review was generated automatically by Fenrir. Findings are non-blocking.
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.
- Propagate hard credit-send failures on unknown extended-data types;
WS_WANT_WRITE and WS_OVERFLOW_E stay success.
- 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
Issues: F-864, F-2078