Skip to content

Commit d166aa8

Browse files
committed
agent: refuse a channel open after a disconnect
wolfSSH_AGENT_ChannelOpen() answers a poll on a session that is over with WS_FATAL_ERROR and WS_DISCONNECT in ssh->error, the shape every other public sender uses: no channel opened, nothing on the wire, RFC 4253 section 11.1. wolfSSH_accept() gates the open it drives, so the new public entry point is the only way in. - promote SendAfterDisconnect() to WOLFSSH_LOCAL so agent.c uses the same helper as every other public sender - leave an open queued before the disconnect unflushed, the rule wolfSSH_shutdown() applies to all but its own disconnect - keep WS_DISCONNECT in ssh->error at the accept() call site, which used to overwrite it with the status the open returns
1 parent 96aa303 commit d166aa8

5 files changed

Lines changed: 69 additions & 13 deletions

File tree

src/agent.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1749,6 +1749,13 @@ int wolfSSH_AGENT_ChannelOpen(WOLFSSH* ssh)
17491749
* checks below would report a channel a client never opened. */
17501750
ret = WS_BAD_ARGUMENT;
17511751
}
1752+
else if (SendAfterDisconnect(ssh)) {
1753+
/* The session is over, so neither a new open nor the flush of one
1754+
* queued before the disconnect may go out. RFC 4253 section 11.1.
1755+
* WS_DISCONNECT is in ssh->error, where the rest of the API puts
1756+
* it. */
1757+
ret = WS_FATAL_ERROR;
1758+
}
17521759
else if (!ssh->useAgent) {
17531760
/* Nothing asked for agent forwarding on this session. */
17541761
ret = WS_BAD_ARGUMENT;

src/ssh.c

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -567,10 +567,6 @@ static int DoReceiveHandshake(WOLFSSH* ssh)
567567
#endif /* !NO_WOLFSSH_SERVER || !NO_WOLFSSH_CLIENT */
568568

569569

570-
/* Defined below, ahead of both drivers; either can be the only one built. */
571-
static int SendAfterDisconnect(WOLFSSH* ssh);
572-
573-
574570
#ifndef NO_WOLFSSH_SERVER
575571

576572
const char acceptError[] = "accept error: %s, %d";
@@ -764,8 +760,13 @@ int wolfSSH_accept(WOLFSSH* ssh)
764760
#endif /* WOLFSSH_SFTP and !NO_WOLFSSH_SERVER */
765761
#ifdef WOLFSSH_AGENT
766762
if (ssh->useAgent) {
767-
ssh->error = wolfSSH_AGENT_ChannelOpen(ssh);
768-
if (ssh->error < WS_SUCCESS) {
763+
int agentRet = wolfSSH_AGENT_ChannelOpen(ssh);
764+
765+
if (agentRet < WS_SUCCESS) {
766+
/* WS_FATAL_ERROR is the disconnect, which already
767+
* recorded WS_DISCONNECT; keep that. */
768+
if (agentRet != WS_FATAL_ERROR)
769+
ssh->error = agentRet;
769770
WLOG(WS_LOG_DEBUG, acceptError,
770771
"SERVER_USERAUTH_ACCEPT_DONE", ssh->error);
771772
return WS_FATAL_ERROR;
@@ -1094,11 +1095,8 @@ int wolfSSH_connect(WOLFSSH* ssh)
10941095
#endif /* NO_WOLFSSH_CLIENT */
10951096

10961097

1097-
/* A disconnect, sent or received, ends the session, so nothing further may
1098-
* go out. RFC 4253 section 11.1. Reads are deliberately not gated on this:
1099-
* channel data that arrived before the disconnect is still the caller's.
1100-
* Call only after ssh has been checked for NULL. */
1101-
static int SendAfterDisconnect(WOLFSSH* ssh)
1098+
/* See wolfssh/internal.h for the contract. */
1099+
int SendAfterDisconnect(WOLFSSH* ssh)
11021100
{
11031101
if (ssh->disconnected) {
11041102
WLOG(WS_LOG_DEBUG, "Send attempted after a disconnect");

tests/regress.c

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4459,6 +4459,48 @@ static void TestAgentChannelOpenWithoutRequest(void)
44594459
FreeChannelOpenHarness(&harness);
44604460
}
44614461

4462+
/* A poll after the peer disconnects must not open a channel or put anything
4463+
* on the wire. RFC 4253 section 11.1: the session is over. */
4464+
static void TestAgentChannelOpenAfterDisconnect(void)
4465+
{
4466+
ChannelOpenHarness harness;
4467+
4468+
InitChannelOpenHarness(&harness, NULL, 0);
4469+
harness.ssh->useAgent = 1;
4470+
harness.ssh->disconnected = 1;
4471+
4472+
AssertIntEQ(wolfSSH_AGENT_ChannelOpen(harness.ssh), WS_FATAL_ERROR);
4473+
AssertNull(harness.ssh->agent);
4474+
AssertIntEQ(harness.ssh->channelListSz, 0);
4475+
AssertIntEQ(harness.io.outSz, 0);
4476+
AssertIntEQ(harness.ssh->error, WS_DISCONNECT);
4477+
4478+
FreeChannelOpenHarness(&harness);
4479+
}
4480+
4481+
/* An open queued before the disconnect is not flushed either: those bytes
4482+
* belong to a session that is over, the same rule wolfSSH_shutdown() applies
4483+
* to everything but its own queued disconnect. */
4484+
static void TestAgentChannelOpenQueuedThenDisconnect(void)
4485+
{
4486+
ChannelOpenHarness harness;
4487+
4488+
InitChannelOpenHarness(&harness, NULL, 0);
4489+
harness.ssh->useAgent = 1;
4490+
harness.io.blockNext = 1;
4491+
4492+
AssertIntEQ(wolfSSH_AGENT_ChannelOpen(harness.ssh), WS_WANT_WRITE);
4493+
AssertIntEQ(harness.io.outSz, 0);
4494+
4495+
harness.ssh->disconnected = 1;
4496+
4497+
AssertIntEQ(wolfSSH_AGENT_ChannelOpen(harness.ssh), WS_FATAL_ERROR);
4498+
AssertIntEQ(harness.io.outSz, 0);
4499+
AssertIntEQ(harness.ssh->error, WS_DISCONNECT);
4500+
4501+
FreeChannelOpenHarness(&harness);
4502+
}
4503+
44624504
/* A queued open publishes the agent, so the caller's next poll must finish
44634505
* the send rather than report a success the peer never saw, and must not
44644506
* open a second channel. */
@@ -13525,6 +13567,8 @@ int main(int argc, char** argv)
1352513567
TestAgentChannelNullAgentSendsOpenFail();
1352613568
TestAgentChannelOpenWithoutRequest();
1352713569
TestAgentChannelOpenFlushesQueuedOpen();
13570+
TestAgentChannelOpenAfterDisconnect();
13571+
TestAgentChannelOpenQueuedThenDisconnect();
1352813572
TestAgentChannelOpenSendFailureCleansUp();
1352913573
#ifndef NO_WOLFSSH_CLIENT
1353013574
TestAgentChannelOpenOnClientRefused();

wolfssh/agent.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -187,11 +187,12 @@ WOLFSSH_API int wolfSSH_AGENT_enable(WOLFSSH* ssh, byte isEnabled);
187187
* this instead. Opens one channel, then flushes what of the open is queued.
188188
* Returns WS_SUCCESS, WS_BAD_ARGUMENT before the peer asks or on a client
189189
* session, WS_WANT_READ or WS_WANT_WRITE while output is still queued,
190+
* WS_FATAL_ERROR with WS_DISCONNECT in ssh->error once the session is over,
190191
* WS_SSH_NULL_E, WS_MEMORY_E, or whatever the send reports. WS_SUCCESS says
191192
* the open went out, not that the peer took it; a refusal reaches the
192193
* channel-open-fail callback.
193-
* Only the send records in ssh->error, so a poll ahead of the peer's request
194-
* leaves the session fit for wolfSSH_accept(). */
194+
* Only that and the send record in ssh->error, so a poll ahead of the peer's
195+
* request leaves the session fit for wolfSSH_accept(). */
195196
WOLFSSH_API int wolfSSH_AGENT_ChannelOpen(WOLFSSH* ssh);
196197
WOLFSSH_LOCAL int wolfSSH_AGENT_worker(WOLFSSH* ssh);
197198
WOLFSSH_API int wolfSSH_AGENT_Relay(WOLFSSH* ssh,

wolfssh/internal.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1643,6 +1643,12 @@ enum ChannelOpenFailReasons {
16431643
OPEN_RESOURCE_SHORTAGE
16441644
};
16451645

1646+
/* A disconnect, sent or received, ends the session, so nothing further may
1647+
* go out. RFC 4253 section 11.1. Returns 1 and records WS_DISCONNECT in
1648+
* ssh->error when the session is over, 0 otherwise. Reads are deliberately
1649+
* not gated on this: channel data that arrived before the disconnect is
1650+
* still the caller's. Call only after ssh has been checked for NULL. */
1651+
WOLFSSH_LOCAL int SendAfterDisconnect(WOLFSSH* ssh);
16461652
WOLFSSH_LOCAL int DoReceive(WOLFSSH* ssh);
16471653
WOLFSSH_LOCAL int DoProtoId(WOLFSSH* ssh);
16481654
WOLFSSH_LOCAL int wolfSSH_SendPacket(WOLFSSH* ssh);

0 commit comments

Comments
 (0)