diff --git a/src/internal.c b/src/internal.c index f142c0db9..ec56ff921 100644 --- a/src/internal.c +++ b/src/internal.c @@ -12599,8 +12599,8 @@ static int DoGlobalRequestFwd(WOLFSSH* ssh, WLOG(WS_LOG_WARN, "Forward cleanup after rejection failed, " "ret = %d", cleanupRet); } - ret = WS_RESOURCE_E; } + ret = WS_RESOURCE_E; } } @@ -12895,6 +12895,14 @@ static int DoChannelOpen(WOLFSSH* ssh, if (ret == WS_SUCCESS) ret = GetUint32(&peerMaxPacketSz, buf, len, &begin); + if (ret == WS_SUCCESS && ssh->channelListSz >= WOLFSSH_MAX_CHANNELS) { + /* Per peer open, so DEBUG. */ + WLOG(WS_LOG_DEBUG, "Rejecting channel open, %u channels open", + ssh->channelListSz); + ret = WS_RESOURCE_E; + fail_reason = OPEN_RESOURCE_SHORTAGE; + } + if (ret == WS_SUCCESS) { WLOG(WS_LOG_INFO, " type = %s", type); WLOG(WS_LOG_INFO, " peerChannelId = %u", peerChannelId); diff --git a/tests/regress.c b/tests/regress.c index 352b054d7..0bfc9f007 100644 --- a/tests/regress.c +++ b/tests/regress.c @@ -4817,6 +4817,7 @@ static void TestAgentRelayChannelDrainsOversizeBacklog(void) FreeChannelOpenHarness(&harness.base); } +#if WOLFSSH_MAX_CHANNELS >= 2 /* opens a second channel */ /* A partial request on one channel is dropped rather than prepended to the * next channel's data. */ static void TestAgentRelayChannelResetsOnChannelChange(void) @@ -4851,6 +4852,7 @@ static void TestAgentRelayChannelResetsOnChannelChange(void) FreeChannelOpenHarness(&harness.base); } +#endif /* A declared length past the build's ceiling is refused and nothing is sent * to the agent. */ @@ -5357,6 +5359,7 @@ static void TestAgentRelayChannelFlushesUnderRekey(void) } +#if WOLFSSH_MAX_CHANNELS >= 2 /* opens a second channel */ /* Shifting a held request down leaves a copy of it above the buffer's * length, and a channel change scrubs that copy too. */ static void TestAgentRelayChannelScrubsShiftedRequest(void) @@ -5406,6 +5409,7 @@ static void TestAgentRelayChannelScrubsShiftedRequest(void) FreeChannelOpenHarness(&harness.base); } +#endif #endif /* WOLFSSH_AGENT */ @@ -5868,6 +5872,11 @@ static void TestSecondSessionChannelRejected(void) word32 in1Sz; word32 in2Sz; int ret; +#if WOLFSSH_MAX_CHANNELS < 2 + const word32 reason = OPEN_RESOURCE_SHORTAGE; +#else + const word32 reason = OPEN_ADMINISTRATIVELY_PROHIBITED; +#endif in1Sz = BuildChannelOpenPacket("session", 7, 0x4000, 0x8000, NULL, 0, in1, sizeof(in1)); @@ -5902,7 +5911,7 @@ static void TestSecondSessionChannelRejected(void) AssertIntEQ(ParseChannelOpenFailRecipient(harness.io.out, harness.io.outSz), 8); /* RFC 4254 5.1: server must echo the peer's channel ID */ AssertIntEQ(ParseChannelOpenFailReason(harness.io.out, harness.io.outSz), - OPEN_ADMINISTRATIVELY_PROHIBITED); + reason); AssertIntEQ(harness.ssh->channelListSz, 1); /* original channel intact */ FreeChannelOpenHarness(&harness); @@ -8842,6 +8851,54 @@ static void TestDirectTcpipNoFwdCbSendsOpenFail(void) FreeChannelOpenHarness(&harness); } +/* Peer opens up to WOLFSSH_MAX_CHANNELS are accepted; the next is refused + * with OPEN_RESOURCE_SHORTAGE, the session stays up, and closing a channel + * makes room again. */ +static void TestChannelOpenPastCapSendsResourceShortage(void) +{ + ChannelOpenHarness harness; + byte extra[128]; + byte in[192]; + word32 extraSz; + word32 inSz; + word32 i; + + extraSz = BuildDirectTcpipExtra("127.0.0.1", 8080, "127.0.0.1", 2222, + extra, sizeof(extra)); + + InitChannelOpenHarness(&harness, NULL, 0); + AssertIntEQ(wolfSSH_CTX_SetFwdCb(harness.ctx, AcceptFwdCb, NULL), + WS_SUCCESS); + /* Keep the test's footprint small; the cap is on count, not size. */ + AssertIntEQ(wolfSSH_CTX_SetWindowPacketSize(harness.ctx, 1024, 1024), + WS_SUCCESS); + + for (i = 0; i < WOLFSSH_MAX_CHANNELS; i++) { + inSz = BuildChannelOpenPacket("direct-tcpip", 100 + i, 0x4000, 0x8000, + extra, extraSz, in, sizeof(in)); + AssertIntEQ(ReplyToPacket(&harness, in, inSz), + MSGID_CHANNEL_OPEN_CONF); + } + AssertIntEQ(harness.ssh->channelListSz, WOLFSSH_MAX_CHANNELS); + + inSz = BuildChannelOpenPacket("direct-tcpip", 9, 0x4000, 0x8000, + extra, extraSz, in, sizeof(in)); + AssertIntEQ(ReplyToPacket(&harness, in, inSz), MSGID_CHANNEL_OPEN_FAIL); + AssertIntEQ(ParseChannelOpenFailRecipient(harness.io.out, + harness.io.outSz), 9); + AssertIntEQ(ParseChannelOpenFailReason(harness.io.out, harness.io.outSz), + OPEN_RESOURCE_SHORTAGE); + AssertIntEQ(harness.ssh->channelListSz, WOLFSSH_MAX_CHANNELS); + AssertFalse(harness.ssh->disconnected); + + AssertIntEQ(ChannelRemove(harness.ssh, 100, WS_CHANNEL_ID_PEER), + WS_SUCCESS); + AssertIntEQ(ReplyToPacket(&harness, in, inSz), MSGID_CHANNEL_OPEN_CONF); + AssertIntEQ(harness.ssh->channelListSz, WOLFSSH_MAX_CHANNELS); + + FreeChannelOpenHarness(&harness); +} + /* Both a channelOpenCb and a fwdCb registered, open callback rejects. The * rejection has to stand: the forwarding hook must not run, and must not * overwrite the rejection with its own return. That clobber shipped in @@ -13226,6 +13283,7 @@ static void TestForwardedTcpipPortZeroFoldSettlesQueuedCancel(void) FreeChannelOpenHarness(&harness); } +#if WOLFSSH_MAX_CHANNELS >= 2 /* opens a second channel */ static void TestForwardedTcpipRepliesPairInSendOrder(void) { ChannelOpenHarness harness; @@ -13253,6 +13311,7 @@ static void TestForwardedTcpipRepliesPairInSendOrder(void) FreeChannelOpenHarness(&harness); } +#endif /* A port-0 request whose reply names no usable port leaves nothing to match * on, so the registration goes. */ @@ -21942,7 +22001,9 @@ int main(int argc, char** argv) TestAgentRelayChannelReassemblesFragmentedRequest(); TestAgentRelayChannelKeepsTrailingBytes(); TestAgentRelayChannelDrainsOversizeBacklog(); +#if WOLFSSH_MAX_CHANNELS >= 2 TestAgentRelayChannelResetsOnChannelChange(); +#endif TestAgentRelayChannelRejectsOversizeLength(); TestAgentRelayChannelRejectsZeroLength(); TestAgentRelayChannelReplyReachesChannel(); @@ -21960,9 +22021,11 @@ int main(int argc, char** argv) TestAgentRelayChannelReconnectsOnDeadSocket(); TestAgentRelayChannelHoldsOnRekey(); TestAgentRelayChannelFlushesUnderRekey(); +#if WOLFSSH_MAX_CHANNELS >= 2 TestAgentRelayChannelScrubsShiftedRequest(); #endif #endif +#endif #if defined(WOLFSSH_AGENT) && !defined(NO_WOLFSSH_SERVER) TestAgentOpenOnServerFails(); #endif @@ -22052,6 +22115,7 @@ int main(int argc, char** argv) #ifdef WOLFSSH_FWD TestDirectTcpipRejectSendsOpenFail(); TestDirectTcpipNoFwdCbSendsOpenFail(); + TestChannelOpenPastCapSendsResourceShortage(); TestDirectTcpipOpenCbRejectBeatsFwdCb(); TestDirectTcpipFwdCbRejectAfterOpenCbAccept(); TestDirectTcpipFwdCbRejectsChannelId(); @@ -22211,7 +22275,9 @@ int main(int argc, char** argv) TestForwardedTcpipInterruptedSendStillRegisters(); TestInterruptedSendRetriesForAnySender(); TestGlobalRequestNoReplyQueuesNothing(); +#if WOLFSSH_MAX_CHANNELS >= 2 TestForwardedTcpipRepliesPairInSendOrder(); +#endif TestForwardedTcpipUnusablePortReplySendsOpenFail(); TestForwardedTcpipPortZeroOtherPortSendsOpenFail(); #endif /* WOLFSSH_FWD && !NO_WOLFSSH_CLIENT */ diff --git a/wolfssh/internal.h b/wolfssh/internal.h index d9c550995..5b7cb91a0 100644 --- a/wolfssh/internal.h +++ b/wolfssh/internal.h @@ -702,6 +702,14 @@ enum NameIdType { * MaxAuthTries default. */ #define DEFAULT_MAX_AUTH_ATTEMPTS 6 #endif +#ifndef WOLFSSH_MAX_CHANNELS + /* Cap on open channels; a peer CHANNEL_OPEN past it is refused with + * OPEN_RESOURCE_SHORTAGE. Each channel holds a window-sized buffer. */ + #define WOLFSSH_MAX_CHANNELS 64 +#endif +#if WOLFSSH_MAX_CHANNELS < 1 + #error "WOLFSSH_MAX_CHANNELS must be at least 1" +#endif #ifndef MAX_PACKET_SZ /* This is from RFC 4253 section 6.1. */ #define MAX_PACKET_SZ 35000