Skip to content

Commit d3fbc86

Browse files
ejohnstownphilljj
authored andcommitted
internal: refuse a second session request
RFC 4254 sec 6.5 lets only one shell, exec or subsystem request succeed per channel. DoChannelRequestSession() now refuses any request after the first grant without asking a callback, so the granted session and its command stand. - a refusal before any grant leaves the channel with no command - ssh.h documents the refusal; wolfsshd's own check is defence in depth - regress.c sends a second exec, shell and subsystem after a granted exec, and a second exec over a generic-callback grant - unit.c expects a shell after an exec to be refused and checks that a refused exec's command is scrubbed
1 parent 8455528 commit d3fbc86

5 files changed

Lines changed: 175 additions & 73 deletions

File tree

‎apps/wolfsshd/wolfsshd.c‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -546,9 +546,9 @@ static int SessionRequestCb(WOLFSSH_CHANNEL* channel, void* vCtx)
546546
break;
547547
}
548548

549-
/* One program start per channel, as RFC 4254 section 6.5 allows. This
550-
* request's grant is recorded once the callback returns, so a flag
551-
* already set is an earlier request's. */
549+
/* One program start per channel, as RFC 4254 section 6.5 allows. The
550+
* library already refuses a second request before calling here, so
551+
* this is defence in depth. */
552552
if (!rej && wolfSSH_ChannelGetSessionGranted(channel) == 1) {
553553
rej = 1;
554554
reason = "a session is already running on the channel";

‎src/internal.c‎

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13550,26 +13550,23 @@ static void SetTerminalSize(WOLFSSH* ssh, word32 widthChar, word32 heightRows,
1355013550

1355113551
/* Answers a shell, exec, or subsystem request. Sets the session type and
1355213552
* command for the callback to read, and keeps them only if it accepts.
13553-
* A request the generic callback already granted asks no callback. */
13553+
* A request the generic callback already granted asks no callback, and
13554+
* one on a channel that already has a session is refused without asking
13555+
* any. Only the first grant sets a command, so a request that gets past
13556+
* that check finds the channel with none. */
1355413557
static int DoChannelRequestSession(WOLFSSH* ssh, word32 channelId,
1355513558
WOLFSSH_CHANNEL* channel, byte sessionType, WS_CallbackChannelReq cb,
1355613559
int granted, byte* buf, word32 len, word32* idx, int* rej)
1355713560
{
1355813561
void* heap = ssh->ctx->heap;
1355913562
byte prevType = channel->sessionType;
1356013563
byte hasCommand = (sessionType != WOLFSSH_SESSION_SHELL);
13561-
char* prevCommand = NULL;
13562-
word32 prevCommandSz = 0;
1356313564
char* command = NULL;
1356413565
word32 commandSz = 0;
1356513566
int ret = WS_SUCCESS;
1356613567

13567-
/* A shell request carries no command, so it leaves the old one alone.
13568-
* The others read into a local, so the old survives a refusal. */
13568+
/* A shell request carries no command. */
1356913569
if (hasCommand) {
13570-
prevCommand = channel->command;
13571-
prevCommandSz = channel->commandSz;
13572-
1357313570
ret = GetStringAlloc(heap, &command, &commandSz, buf, len, idx);
1357413571
if (ret == WS_SUCCESS)
1357513572
WLOG(WS_LOG_DEBUG, " %s = %s",
@@ -13581,6 +13578,15 @@ static int DoChannelRequestSession(WOLFSSH* ssh, word32 channelId,
1358113578
? "subsystem" : "command", "<bad value>");
1358213579
}
1358313580

13581+
/* RFC 4254 sec 6.5: one session request succeeds per channel. Refuse
13582+
* the rest without asking a callback; the grant stands. */
13583+
if (ret == WS_SUCCESS && channel->sessionGranted) {
13584+
WLOG(WS_LOG_DEBUG, " session already granted, rejecting.");
13585+
FreeChannelCommand(heap, command, commandSz);
13586+
*rej = 1;
13587+
return ret;
13588+
}
13589+
1358413590
if (ret == WS_SUCCESS) {
1358513591
if (hasCommand) {
1358613592
channel->command = command;
@@ -13595,29 +13601,31 @@ static int DoChannelRequestSession(WOLFSSH* ssh, word32 channelId,
1359513601
else
1359613602
*rej = ssh->appChannels;
1359713603

13598-
/* A callback may free its own channel, so look it up again. */
13604+
/* A callback may free its own channel, and the command with it,
13605+
* so look it up again. */
1359913606
channel = ChannelFind(ssh, channelId, WS_CHANNEL_ID_SELF);
13600-
if (channel == NULL) {
13601-
/* The new command went with it. */
13602-
FreeChannelCommand(heap, prevCommand, prevCommandSz);
13607+
if (channel == NULL)
1360313608
return ret;
13604-
}
1360513609
}
1360613610

1360713611
if (ret == WS_SUCCESS && !*rej) {
13608-
FreeChannelCommand(heap, prevCommand, prevCommandSz);
1360913612
channel->sessionGranted = 1;
1361013613
ssh->clientState = CLIENT_DONE;
1361113614
}
13612-
else {
13613-
/* A refusal changes nothing, so an earlier grant still stands. */
13615+
else if (ret == WS_SUCCESS) {
13616+
/* A refusal leaves the channel as it found it, with no command. */
1361413617
if (hasCommand) {
1361513618
FreeChannelCommand(heap, command, commandSz);
13616-
channel->command = prevCommand;
13617-
channel->commandSz = prevCommandSz;
13619+
channel->command = NULL;
13620+
channel->commandSz = 0;
1361813621
}
1361913622
channel->sessionType = prevType;
1362013623
}
13624+
else {
13625+
/* A bad request never reached the channel, which may hold an
13626+
* earlier grant. */
13627+
FreeChannelCommand(heap, command, commandSz);
13628+
}
1362113629

1362213630
return ret;
1362313631
}

‎tests/regress.c‎

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2005,6 +2005,11 @@ static void TestAppChannelsLateEnableReturns(void)
20052005
AssertIntEQ(harness.server->acceptState,
20062006
ACCEPT_CLIENT_SESSION_ESTABLISHED);
20072007

2008+
/* White box: the handshake's shell holds the session. Clear the grant
2009+
* before each request so only the mode decides. */
2010+
AssertNotNull(harness.server->channelList);
2011+
harness.server->channelList->sessionGranted = 0;
2012+
20082013
/* Default mode, no callback registered: the request is granted. */
20092014
idx = 0;
20102015
AssertIntEQ(wolfSSH_TestDoChannelRequest(harness.server, payShell,
@@ -2017,6 +2022,7 @@ static void TestAppChannelsLateEnableReturns(void)
20172022
ACCEPT_CLIENT_SESSION_ESTABLISHED);
20182023

20192024
/* Same request, same session, mode now on: refused instead. */
2025+
harness.server->channelList->sessionGranted = 0;
20202026
idx = 0;
20212027
AssertIntEQ(wolfSSH_TestDoChannelRequest(harness.server, payShell,
20222028
(word32)sizeof(payShell), &idx), WS_SUCCESS);
@@ -7550,6 +7556,84 @@ static void TestSessionReqCallbackMayFreeChannel(void)
75507556
FreeChannelOpenHarness(&harness);
75517557
}
75527558

7559+
/* RFC 4254 sec 6.5: after a granted exec, a second session request is
7560+
* refused without asking its callback, and the exec stands. */
7561+
static void CheckSecondSessionRequestRefused(const char* type,
7562+
const char* arg)
7563+
{
7564+
ChannelOpenHarness harness;
7565+
WOLFSSH_CHANNEL* channel;
7566+
WS_SessionType sessionType;
7567+
byte in[128];
7568+
word32 inSz;
7569+
7570+
sessionReqCbCalls = 0;
7571+
sessionReqCbReturn = 0;
7572+
7573+
InitChannelOpenHarness(&harness, NULL, 0);
7574+
AssertIntEQ(wolfSSH_CTX_SetChannelReqShellCb(harness.ctx,
7575+
RecordingSessionReqCb), WS_SUCCESS);
7576+
AssertIntEQ(wolfSSH_CTX_SetChannelReqExecCb(harness.ctx,
7577+
RecordingSessionReqCb), WS_SUCCESS);
7578+
AssertIntEQ(wolfSSH_CTX_SetChannelReqSubsysCb(harness.ctx,
7579+
RecordingSessionReqCb), WS_SUCCESS);
7580+
channel = SeedConfirmedSessionChannel(&harness);
7581+
7582+
inSz = BuildChannelStringRequestPacket(channel->channel, "exec", 1,
7583+
"ls", in, sizeof(in));
7584+
AssertIntEQ(ReplyToPacket(&harness, in, inSz), MSGID_CHANNEL_SUCCESS);
7585+
AssertIntEQ(sessionReqCbCalls, 1);
7586+
AssertIntEQ(channel->sessionGranted, 1);
7587+
7588+
inSz = BuildChannelStringRequestPacket(channel->channel, type, 1, arg,
7589+
in, sizeof(in));
7590+
AssertIntEQ(ReplyToPacket(&harness, in, inSz), MSGID_CHANNEL_FAILURE);
7591+
AssertIntEQ(sessionReqCbCalls, 1);
7592+
AssertIntEQ(channel->sessionGranted, 1);
7593+
sessionType = wolfSSH_ChannelGetSessionType(channel);
7594+
AssertIntEQ(sessionType, WOLFSSH_SESSION_EXEC);
7595+
AssertIntEQ(wolfSSH_ChannelGetSessionCommandSz(channel), 2);
7596+
AssertIntEQ(WSTRCMP(wolfSSH_ChannelGetSessionCommand(channel), "ls"), 0);
7597+
7598+
FreeChannelOpenHarness(&harness);
7599+
}
7600+
7601+
static void TestSecondSessionRequestRefused(void)
7602+
{
7603+
CheckSecondSessionRequestRefused("exec", "whoami");
7604+
CheckSecondSessionRequestRefused("shell", NULL);
7605+
CheckSecondSessionRequestRefused("subsystem", "sftp");
7606+
}
7607+
7608+
/* A grant from the generic callback does not override the rule. */
7609+
static void TestSecondSessionRequestRefusedOverGenericGrant(void)
7610+
{
7611+
ChannelOpenHarness harness;
7612+
WOLFSSH_CHANNEL* channel;
7613+
byte in[128];
7614+
word32 inSz;
7615+
7616+
ResetAnyReqCb(WOLFSSH_REQ_ACCEPT);
7617+
InitChannelOpenHarness(&harness, NULL, 0);
7618+
AssertIntEQ(wolfSSH_CTX_SetChannelReqAnyCb(harness.ctx,
7619+
RecordingChannelReqAnyCb), WS_SUCCESS);
7620+
channel = SeedConfirmedSessionChannel(&harness);
7621+
7622+
inSz = BuildChannelStringRequestPacket(channel->channel, "exec", 1,
7623+
"ls", in, sizeof(in));
7624+
AssertIntEQ(ReplyToPacket(&harness, in, inSz), MSGID_CHANNEL_SUCCESS);
7625+
AssertIntEQ(anyReqCbCalls, 1);
7626+
AssertIntEQ(channel->sessionGranted, 1);
7627+
7628+
inSz = BuildChannelStringRequestPacket(channel->channel, "exec", 1,
7629+
"whoami", in, sizeof(in));
7630+
AssertIntEQ(ReplyToPacket(&harness, in, inSz), MSGID_CHANNEL_FAILURE);
7631+
AssertIntEQ(anyReqCbCalls, 2);
7632+
AssertIntEQ(WSTRCMP(wolfSSH_ChannelGetSessionCommand(channel), "ls"), 0);
7633+
7634+
FreeChannelOpenHarness(&harness);
7635+
}
7636+
75537637
/* accept() re-entered while it is already parked, with a reply still
75547638
* queued, has to flush and stay put. Stepping the state on from here
75557639
* would put the stop behind it, and the loop tests for that state
@@ -20380,6 +20464,8 @@ int main(int argc, char** argv)
2038020464
TestChannelGetSessionGrantedAccessor();
2038120465
TestMalformedSessionRequestSkipsCallback();
2038220466
TestSessionReqCallbackMayFreeChannel();
20467+
TestSecondSessionRequestRefused();
20468+
TestSecondSessionRequestRefusedOverGenericGrant();
2038320469
TestAppChannelsAcceptKeepsStopWithPendingOutput();
2038420470
#ifdef WOLFSSH_SFTP
2038520471
TestSftpAcceptAppChannelsNeedsSession();

0 commit comments

Comments
 (0)