Skip to content

Commit b33f59d

Browse files
committed
internal: commit a session only once accepted
A shell, exec or subsystem request changes the channel only once the callback accepts it. The session type and command are set for the callback to read and put back if it refuses, and CLIENT_DONE follows acceptance alone, so wolfSSH_accept() no longer reports an established session, or starts SFTP, on a request it answered CHANNEL_FAILURE. - DoChannelRequestSession() carries the three arms, which differed only in the type and the callback consulted - unit.c drives a refused shell, exec and subsystem request through DoChannelRequest() and checks nothing was committed - regress.c runs a server whose shell callback refuses and checks accept() stays at ACCEPT_SERVER_CHANNEL_ACCEPT_SENT Issue: F-8852
1 parent 8121dac commit b33f59d

3 files changed

Lines changed: 212 additions & 41 deletions

File tree

src/internal.c

Lines changed: 62 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -12617,6 +12617,61 @@ static void SetTerminalSize(WOLFSSH* ssh, word32 widthChar, word32 heightRows,
1261712617
#endif /* WOLFSSH_TERM */
1261812618

1261912619

12620+
/* Answers a shell, exec, or subsystem request. The session type, and the
12621+
* command for the two that carry one, are set for the callback to read and
12622+
* kept only if it accepts; a refused request leaves the channel as it was
12623+
* and the accept loop still waiting, so nothing serves a session the
12624+
* application turned down. Without a callback the request is accepted,
12625+
* unless the application drives its own channels. */
12626+
static int DoChannelRequestSession(WOLFSSH* ssh, WOLFSSH_CHANNEL* channel,
12627+
byte sessionType, WS_CallbackChannelReq cb,
12628+
byte* buf, word32 len, word32* idx, int* rej)
12629+
{
12630+
char* prevCommand = NULL;
12631+
byte prevType = channel->sessionType;
12632+
byte hasCommand = (sessionType != WOLFSSH_SESSION_SHELL);
12633+
int ret = WS_SUCCESS;
12634+
12635+
if (hasCommand) {
12636+
prevCommand = channel->command;
12637+
channel->command = NULL;
12638+
ret = GetStringAlloc(ssh->ctx->heap, &channel->command, NULL,
12639+
buf, len, idx);
12640+
if (ret == WS_SUCCESS) {
12641+
WLOG(WS_LOG_DEBUG, " command = %s", channel->command);
12642+
}
12643+
}
12644+
12645+
if (ret == WS_SUCCESS) {
12646+
channel->sessionType = sessionType;
12647+
if (cb != NULL) {
12648+
*rej = cb(channel, ssh->channelReqCtx);
12649+
}
12650+
else {
12651+
*rej = ssh->appChannels;
12652+
}
12653+
}
12654+
12655+
if (ret == WS_SUCCESS && !*rej) {
12656+
if (prevCommand != NULL) {
12657+
WFREE(prevCommand, ssh->ctx->heap, DYNTYPE_STRING);
12658+
}
12659+
ssh->clientState = CLIENT_DONE;
12660+
}
12661+
else {
12662+
if (hasCommand) {
12663+
if (channel->command != NULL) {
12664+
WFREE(channel->command, ssh->ctx->heap, DYNTYPE_STRING);
12665+
}
12666+
channel->command = prevCommand;
12667+
}
12668+
channel->sessionType = prevType;
12669+
}
12670+
12671+
return ret;
12672+
}
12673+
12674+
1262012675
static int DoChannelRequest(WOLFSSH* ssh,
1262112676
byte* buf, word32 len, word32* idx)
1262212677
{
@@ -12672,42 +12727,17 @@ static int DoChannelRequest(WOLFSSH* ssh,
1267212727
WLOG(WS_LOG_DEBUG, " %s = %s", name, value);
1267312728
}
1267412729
else if (ChannelRequestIs(type, typeSz, "shell")) {
12675-
channel->sessionType = WOLFSSH_SESSION_SHELL;
12676-
if (ssh->ctx->channelReqShellCb) {
12677-
rej = ssh->ctx->channelReqShellCb(channel, ssh->channelReqCtx);
12678-
}
12679-
else {
12680-
rej = ssh->appChannels;
12681-
}
12682-
ssh->clientState = CLIENT_DONE;
12730+
ret = DoChannelRequestSession(ssh, channel, WOLFSSH_SESSION_SHELL,
12731+
ssh->ctx->channelReqShellCb, buf, len, &begin, &rej);
1268312732
}
1268412733
else if (ChannelRequestIs(type, typeSz, "exec")) {
12685-
ret = GetStringAlloc(ssh->ctx->heap, &channel->command, NULL,
12686-
buf, len, &begin);
12687-
channel->sessionType = WOLFSSH_SESSION_EXEC;
12688-
if (ssh->ctx->channelReqExecCb) {
12689-
rej = ssh->ctx->channelReqExecCb(channel, ssh->channelReqCtx);
12690-
}
12691-
else {
12692-
rej = ssh->appChannels;
12693-
}
12694-
ssh->clientState = CLIENT_DONE;
12695-
12696-
WLOG(WS_LOG_DEBUG, " command = %s", channel->command);
12734+
ret = DoChannelRequestSession(ssh, channel, WOLFSSH_SESSION_EXEC,
12735+
ssh->ctx->channelReqExecCb, buf, len, &begin, &rej);
1269712736
}
1269812737
else if (ChannelRequestIs(type, typeSz, "subsystem")) {
12699-
ret = GetStringAlloc(ssh->ctx->heap, &channel->command, NULL,
12700-
buf, len, &begin);
12701-
channel->sessionType = WOLFSSH_SESSION_SUBSYSTEM;
12702-
if (ssh->ctx->channelReqSubsysCb) {
12703-
rej = ssh->ctx->channelReqSubsysCb(channel, ssh->channelReqCtx);
12704-
}
12705-
else {
12706-
rej = ssh->appChannels;
12707-
}
12708-
ssh->clientState = CLIENT_DONE;
12709-
12710-
WLOG(WS_LOG_DEBUG, " subsystem = %s", channel->command);
12738+
ret = DoChannelRequestSession(ssh, channel,
12739+
WOLFSSH_SESSION_SUBSYSTEM, ssh->ctx->channelReqSubsysCb,
12740+
buf, len, &begin, &rej);
1271112741
}
1271212742
#ifdef WOLFSSH_TERM
1271312743
else if (ChannelRequestIs(type, typeSz, "pty-req")) {

tests/regress.c

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1646,6 +1646,57 @@ static void TestAppChannelsLateEnableReturns(void)
16461646
FreeKexReplyHarness(&harness);
16471647
}
16481648

1649+
/* Refuses the session request, and records what the channel showed. */
1650+
static int rejectShellReqCalls;
1651+
static WS_SessionType rejectShellReqType;
1652+
1653+
static int RejectShellReqCb(WOLFSSH_CHANNEL* channel, void* ctx)
1654+
{
1655+
(void)ctx;
1656+
rejectShellReqCalls++;
1657+
rejectShellReqType = wolfSSH_ChannelGetSessionType(channel);
1658+
return 1;
1659+
}
1660+
1661+
/* A shell request the callback refuses gets CHANNEL_FAILURE and nothing
1662+
* more: the channel keeps no session type, and accept() stays where it was,
1663+
* waiting on a request it can grant, rather than reporting an established
1664+
* session it just refused. */
1665+
static void TestSessionReqRejectedKeepsAcceptWaiting(void)
1666+
{
1667+
KexReplyHarness harness;
1668+
KexReplyRunResult result;
1669+
WOLFSSH_CHANNEL* channel;
1670+
1671+
rejectShellReqCalls = 0;
1672+
rejectShellReqType = WOLFSSH_SESSION_UNKNOWN;
1673+
1674+
InitKexReplyHarness(&harness, "rsa-sha2-256", REGRESS_SERVER_KEY_PATH,
1675+
0, NULL);
1676+
AssertIntEQ(wolfSSH_CTX_SetChannelReqShellCb(harness.serverCtx,
1677+
RejectShellReqCb), WS_SUCCESS);
1678+
1679+
RunKexReplyHandshake(&harness, &result);
1680+
1681+
AssertIntEQ(rejectShellReqCalls, 1);
1682+
AssertIntEQ(rejectShellReqType, WOLFSSH_SESSION_SHELL);
1683+
AssertFalse(result.clientSuccess);
1684+
AssertIntEQ(result.clientErr, WS_CHANOPEN_FAILED);
1685+
AssertFalse(result.serverSuccess);
1686+
AssertIntEQ(harness.server->acceptState,
1687+
ACCEPT_SERVER_CHANNEL_ACCEPT_SENT);
1688+
AssertTrue(harness.server->clientState < CLIENT_DONE);
1689+
AssertIntEQ(wolfSSH_GetSessionType(harness.server),
1690+
WOLFSSH_SESSION_UNKNOWN);
1691+
channel = wolfSSH_ChannelNext(harness.server, NULL);
1692+
AssertNotNull(channel);
1693+
AssertIntEQ(channel->sessionType, WOLFSSH_SESSION_UNKNOWN);
1694+
AssertFalse(harness.clientIo.sawDisconnect);
1695+
AssertFalse(harness.serverIo.sawDisconnect);
1696+
1697+
FreeKexReplyHarness(&harness);
1698+
}
1699+
16491700
static void TestKexDhReplyRejectsRsaSha2_256SigNameDowngrade(void)
16501701
{
16511702
AssertHandshakeSucceeds("rsa-sha2-256", REGRESS_SERVER_KEY_PATH);
@@ -12480,6 +12531,7 @@ int main(int argc, char** argv)
1248012531
TestAppChannelsAcceptStopsAtUserAuth();
1248112532
TestAppChannelsNoShellCbRejects();
1248212533
TestAppChannelsLateEnableReturns();
12534+
TestSessionReqRejectedKeepsAcceptWaiting();
1248312535
TestKexDhReplyRejectsRsaSha2_256SigNameDowngrade();
1248412536
#endif
1248512537
#ifndef WOLFSSH_NO_RSA_SHA2_512

tests/unit.c

Lines changed: 98 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8584,6 +8584,23 @@ static int CaptureMsgId(const byte* buf, word32 len)
85848584
* A custom IoSend callback captures the outgoing packet in plaintext
85858585
* (no cipher negotiated on a fresh session). Message ID is read via
85868586
* CaptureMsgId() using LENGTH_SZ + PAD_LENGTH_SZ. */
8587+
/* A session request callback that refuses everything, and counts. The
8588+
* callback sees the session type and command of the request it is vetting;
8589+
* what it does not see is a session already committed to the channel. */
8590+
static int s_rejectChanReqCalls;
8591+
8592+
static int RejectChanReqCb(WOLFSSH_CHANNEL* channel, void* ctx)
8593+
{
8594+
(void)ctx;
8595+
s_rejectChanReqCalls++;
8596+
if (channel == NULL
8597+
|| wolfSSH_ChannelGetSessionType(channel)
8598+
== WOLFSSH_SESSION_UNKNOWN) {
8599+
return 0;
8600+
}
8601+
return 1;
8602+
}
8603+
85878604
static byte s_chanReqCapture[256];
85888605
static word32 s_chanReqCaptureSz = 0;
85898606

@@ -8836,6 +8853,15 @@ static int test_DoChannelRequest(void)
88368853
0x00,0x00,0x00,0x02, /* cmdSz = 2 */
88378854
0x6C,0x73 /* "ls" */
88388855
};
8856+
static const byte paySubsys[] = {
8857+
0x00,0x00,0x00,0x00, /* channelId = 0 */
8858+
0x00,0x00,0x00,0x09, /* typeSz = 9 */
8859+
0x73,0x75,0x62,0x73,0x79,0x73,
8860+
0x74,0x65,0x6D, /* "subsystem" */
8861+
0x01, /* wantReply = 1 */
8862+
0x00,0x00,0x00,0x04, /* nameSz = 4 */
8863+
0x73,0x66,0x74,0x70 /* "sftp" */
8864+
};
88398865
static const byte payUnknown[] = {
88408866
0x00,0x00,0x00,0x00, /* channelId = 0 */
88418867
0x00,0x00,0x00,0x0C, /* typeSz = 12 */
@@ -8962,6 +8988,78 @@ static int test_DoChannelRequest(void)
89628988
}
89638989
}
89648990

8991+
/* A callback that refuses a shell, exec or subsystem request must leave
8992+
* nothing behind: no session type or command on the channel, and the
8993+
* client state short of CLIENT_DONE, or wolfSSH_accept() would go on to
8994+
* serve the session it just refused. */
8995+
{
8996+
struct {
8997+
const char* label;
8998+
const byte* payload;
8999+
word32 payloadSz;
9000+
int errBase;
9001+
} rejCases[] = {
9002+
{ "shell", payShell, (word32)sizeof(payShell), -520 },
9003+
{ "exec", payExec, (word32)sizeof(payExec), -525 },
9004+
{ "subsystem", paySubsys, (word32)sizeof(paySubsys), -530 }
9005+
};
9006+
int r;
9007+
9008+
wolfSSH_CTX_SetChannelReqShellCb(ctx, RejectChanReqCb);
9009+
wolfSSH_CTX_SetChannelReqExecCb(ctx, RejectChanReqCb);
9010+
wolfSSH_CTX_SetChannelReqSubsysCb(ctx, RejectChanReqCb);
9011+
9012+
for (r = 0; r < (int)(sizeof(rejCases) / sizeof(rejCases[0])); r++) {
9013+
word32 idxRej = 0;
9014+
int retRej, capMsgId;
9015+
9016+
s_chanReqCaptureSz = 0;
9017+
WMEMSET(s_chanReqCapture, 0, sizeof(s_chanReqCapture));
9018+
s_rejectChanReqCalls = 0;
9019+
9020+
retRej = wolfSSH_TestDoChannelRequest(ssh,
9021+
(byte*)rejCases[r].payload, rejCases[r].payloadSz,
9022+
&idxRej);
9023+
if (retRej != WS_SUCCESS) {
9024+
printf("DoChannelRequest[rej-%s]: ret=%d, expected=%d\n",
9025+
rejCases[r].label, retRej, WS_SUCCESS);
9026+
result = rejCases[r].errBase;
9027+
goto done;
9028+
}
9029+
if (s_rejectChanReqCalls != 1) {
9030+
printf("DoChannelRequest[rej-%s]: callback ran %d times\n",
9031+
rejCases[r].label, s_rejectChanReqCalls);
9032+
result = rejCases[r].errBase - 1;
9033+
goto done;
9034+
}
9035+
capMsgId = CaptureMsgId(s_chanReqCapture, s_chanReqCaptureSz);
9036+
if (capMsgId != (int)MSGID_CHANNEL_FAILURE) {
9037+
printf("DoChannelRequest[rej-%s]: msg_id=0x%02x, "
9038+
"expected=0x%02x\n", rejCases[r].label, capMsgId,
9039+
MSGID_CHANNEL_FAILURE);
9040+
result = rejCases[r].errBase - 2;
9041+
goto done;
9042+
}
9043+
if (ch->sessionType != WOLFSSH_SESSION_UNKNOWN
9044+
|| ch->command != NULL) {
9045+
printf("DoChannelRequest[rej-%s]: session committed\n",
9046+
rejCases[r].label);
9047+
result = rejCases[r].errBase - 3;
9048+
goto done;
9049+
}
9050+
if (ssh->clientState == CLIENT_DONE) {
9051+
printf("DoChannelRequest[rej-%s]: client state changed\n",
9052+
rejCases[r].label);
9053+
result = rejCases[r].errBase - 4;
9054+
goto done;
9055+
}
9056+
}
9057+
9058+
wolfSSH_CTX_SetChannelReqShellCb(ctx, NULL);
9059+
wolfSSH_CTX_SetChannelReqExecCb(ctx, NULL);
9060+
wolfSSH_CTX_SetChannelReqSubsysCb(ctx, NULL);
9061+
}
9062+
89659063
for (i = 0; i < (int)(sizeof(cases) / sizeof(cases[0])); i++) {
89669064
word32 idx = 0;
89679065
int ret;
@@ -9249,15 +9347,6 @@ static int test_DoChannelRequest(void)
92499347
* accept() already returned there is nothing left to start a shell,
92509348
* exec or subsystem, so all three are refused rather than accepted. */
92519349
{
9252-
static const byte paySubsys[] = {
9253-
0x00,0x00,0x00,0x00, /* channelId = 0 */
9254-
0x00,0x00,0x00,0x09, /* typeSz = 9 */
9255-
0x73,0x75,0x62,0x73,0x79,0x73,
9256-
0x74,0x65,0x6D, /* "subsystem" */
9257-
0x01, /* wantReply = 1 */
9258-
0x00,0x00,0x00,0x04, /* nameSz = 4 */
9259-
0x73,0x66,0x74,0x70 /* "sftp" */
9260-
};
92619350
struct {
92629351
const char* label;
92639352
const byte* payload;

0 commit comments

Comments
 (0)