Skip to content

Guard remote forwards on a client, disconnect on a disallowed message - #1214

Open
ejohnstown wants to merge 2 commits into
wolfSSL:masterfrom
ejohnstown:msgguard
Open

Guard remote forwards on a client, disconnect on a disallowed message#1214
ejohnstown wants to merge 2 commits into
wolfSSL:masterfrom
ejohnstown:msgguard

Conversation

@ejohnstown

Copy link
Copy Markdown
Contributor

Two of the RFC conformance items reported in #1047. Of the other five, items 1, 5 and 6 are already fixed on master (90692e69, df635a3e, 9e02350a), item 2 is PR #1148, and item 4 was considered and declined -- direct-tcpip is deliberately not direction-checked, and the default no-fwdCb path already answers OPEN_ADMINISTRATIVELY_PROHIBITED. These two were the remainder.

Item 3 -- tcpip-forward handled without a role guard (RFC 4254 section 7.1). DoGlobalRequest() dispatched tcpip-forward and cancel-tcpip-forward to DoGlobalRequestFwd() whatever side it was on, so a client with a fwdCb registered answered REQUEST_SUCCESS and acted on a forward the peer asked for. The callback was the only gate. A client now refuses both, answering REQUEST_FAILURE when the peer asked for a reply and staying quiet when it did not. The answer is given before the request body is parsed, so no forward state is touched on the way to the refusal.

Item 7 -- no disconnect for a message the state disallows (RFC 4252 section 6). DoPacket() returned WS_MSGID_NOT_ALLOWED_E and DoReceive() turned that into a local WS_FATAL_ERROR, so the socket simply went away and the peer was left to guess. It now sends DISCONNECT with PROTOCOL_ERROR where DoPacket() rejects the message id, skipped once the session is already over since RFC 4253 section 11.1 forbids sending after a disconnect. Every rejection there is a protocol error, not only the message-id range the RFC names, so the disconnect covers everywhere IsMessageAllowed() refuses a received message -- and the client side with it, DoPacket() being common to both.

Worth being precise on that second one: the RFC's MUST, respond by disconnecting, was already satisfied by the teardown. What was missing is the "preferably with a proper disconnect message" half.

Regress coverage:

  • TestGlobalRequestFwdOnClientSendsFailure, TestGlobalRequestFwdCancelOnClientSendsFailure: both request names refused on a client, with a fwdCb registered throughout -- without the role check that callback is the only gate and would answer success
  • TestGlobalRequestFwdOnClientNoReplyStaysQuiet: nothing goes back when the peer did not ask for a reply
  • TestGlobalRequestFwdOnServerStillSucceeds: the same request a client refuses is still honoured on a server, so the role check costs the server nothing
  • TestServerHighMsgIdBeforeAuthDisconnects: the case the RFC names, keyed but short of user auth
  • TestChannelOpenRejectedBeforeKex and TestServerServiceRequestRejectedDuringKeying both asserted silence on the reject path; they now assert the disconnect

regress.test, unit.test and api.test pass.

Copilot AI lite review requested due to automatic review settings August 29, 2026 02:02

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves RFC conformance in wolfSSH’s core receive/dispatch path and forwarding global-request handling by (1) adding a client-side role guard for remote-forward global requests and (2) sending an SSH DISCONNECT message when an incoming message is rejected by the session state machine.

Changes:

  • Reject tcpip-forward and cancel-tcpip-forward global requests when received by a client (optionally replying with REQUEST_FAILURE without parsing the request body).
  • Send SSH_MSG_DISCONNECT with PROTOCOL_ERROR when IsMessageAllowed() rejects an inbound message id (unless already disconnected).
  • Expand regress tests to cover the new client/server forwarding behavior and the disconnect-on-disallowed-message behavior.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
src/internal.c Adds client-side guard for remote-forward global requests and sends DISCONNECT on state-disallowed inbound messages.
tests/regress.c Adds harness support for client-side forwarding tests and updates/adds regress coverage for new disconnect and guard behavior.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/internal.c

@wolfSSL-Fenrir-bot wolfSSL-Fenrir-bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fenrir Automated Review — PR #1214

Scan targets checked: wolfssh-bugs, wolfssh-src

Findings: 1
1 finding(s) posted as inline comments (see file-level comments below)

This review was generated automatically by Fenrir. Reported findings require changes before merge.

Comment thread tests/regress.c
RFC 4254 section 7.1 says tcpip-forward and cancel-tcpip-forward are
normally only sent by the client, and that client implementations should
reject them. DoGlobalRequest() dispatched both to DoGlobalRequestFwd()
whatever side it was on, so a client with a forward callback registered
answered REQUEST_SUCCESS and acted on a forward the peer asked for. The
callback was the only gate.

- refuse both requests on a client, answering REQUEST_FAILURE when the
  peer asked for a reply and staying quiet when it did not
- answer before the request body is parsed, so no forward state is
  touched on the way to the refusal
- add regress coverage for both request names, the no-reply case, and a
  server still succeeding

Issue: wolfSSL#1047 (3)
RFC 4252 section 6 has the server disconnect on a message id of 80 or
higher arriving before user authentication finishes, preferably with a
disconnect message so the other end can tell what happened. DoPacket()
returned WS_MSGID_NOT_ALLOWED_E and DoReceive() turned that into a local
WS_FATAL_ERROR, so the socket simply went away and the peer was left to
guess. None of the existing SendDisconnect() call sites covered this
path.

Every rejection here is a protocol error, not only the message-id range
the RFC names, so the disconnect is sent wherever IsMessageAllowed()
refuses a received message.

- send DISCONNECT with PROTOCOL_ERROR where DoPacket() rejects the
  message id
- skip it once the session is already over, since RFC 4253 section 11.1
  forbids sending after a disconnect
- cover the server case the RFC names, keyed but short of user auth
- TestChannelOpenRejectedBeforeKex() and
  TestServerServiceRequestRejectedDuringKeying() asserted silence on the
  reject path; both now assert the disconnect

Issue: wolfSSL#1047 (7)
Comment thread tests/regress.c

@wolfSSL-Fenrir-bot wolfSSL-Fenrir-bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fenrir Automated Review — PR #1214

Scan targets checked: wolfssh-bugs, wolfssh-src

Fenrir result: Approved ✅

No new issues found in the changed files.

Advisory only — this automated result does not count as a GitHub approval.

@wolfSSL-Fenrir-bot
wolfSSL-Fenrir-bot dismissed their stale review August 29, 2026 05:36

Fenrir's latest completed scan found no issues; clearing the prior automated change request.

@ejohnstown
ejohnstown requested a review from philljj August 29, 2026 05:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants