mirror of https://github.com/wolfSSL/wolfssh.git
tests: cover application-driven channels
wolfSSH_SetAppChannels() changes where wolfSSH_accept() stops and what becomes of a session request with no callback behind it, so both modes are exercised. - regress.c drives a server with the pivot on, one with a shell callback and one without, and checks accept() stops at ACCEPT_SERVER_USERAUTH_SENT - regress.c pins the context setter, the session's inheritance of it, and that turning it on after accept() established the session still returns - regress.c re-enters a parked accept() with output still queued, which is the one path that flushes before reading the state, and pins that it leaves the state on the stop - unit.c checks DoChannelRequest() refuses a shell, exec and subsystem request with no callback once the pivot is onpull/1235/head
parent
b723519312
commit
2c832010d2
217
tests/regress.c
217
tests/regress.c
|
|
@ -1490,6 +1490,185 @@ static void AssertHandshakeRejectsMutatedReply(const char* keyAlgo,
|
|||
}
|
||||
|
||||
#ifndef WOLFSSH_NO_RSA_SHA2_256
|
||||
/* Counts the shell requests the application-driven server answered. */
|
||||
static int appChannelsShellReqCount;
|
||||
|
||||
static int AppChannelsShellCb(WOLFSSH_CHANNEL* channel, void* ctx)
|
||||
{
|
||||
(void)channel;
|
||||
(void)ctx;
|
||||
appChannelsShellReqCount++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Drive an application-driven server: wolfSSH_accept() is expected to return
|
||||
* at userauth, so the channel open and the shell request are answered by
|
||||
* wolfSSH_worker() calls the application makes itself. */
|
||||
static void RunAppChannelsHandshake(KexReplyHarness* harness,
|
||||
KexReplyRunResult* result)
|
||||
{
|
||||
word32 step;
|
||||
|
||||
WMEMSET(result, 0, sizeof(*result));
|
||||
result->clientRet = WS_FATAL_ERROR;
|
||||
result->serverRet = WS_FATAL_ERROR;
|
||||
|
||||
for (step = 0; step < REGRESS_MAX_HANDSHAKE_STEPS; step++) {
|
||||
if (!result->clientSuccess) {
|
||||
result->clientRet = wolfSSH_connect(harness->client);
|
||||
result->clientErr = wolfSSH_get_error(harness->client);
|
||||
if (result->clientRet == WS_SUCCESS) {
|
||||
result->clientSuccess = 1;
|
||||
}
|
||||
else if (!IsHandshakeRetryable(result->clientErr)) {
|
||||
result->steps = step + 1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!result->serverSuccess) {
|
||||
result->serverRet = wolfSSH_accept(harness->server);
|
||||
result->serverErr = wolfSSH_get_error(harness->server);
|
||||
if (result->serverRet == WS_SUCCESS) {
|
||||
result->serverSuccess = 1;
|
||||
}
|
||||
else if (!IsHandshakeRetryable(result->serverErr)) {
|
||||
result->steps = step + 1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (harness->server->clientState < CLIENT_DONE) {
|
||||
result->serverRet = wolfSSH_worker(harness->server, NULL);
|
||||
result->serverErr = wolfSSH_get_error(harness->server);
|
||||
if (result->serverRet < WS_SUCCESS
|
||||
&& result->serverErr != WS_CHAN_RXD
|
||||
&& !IsHandshakeRetryable(result->serverErr)) {
|
||||
result->steps = step + 1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (result->clientSuccess && result->serverSuccess
|
||||
&& harness->server->clientState >= CLIENT_DONE) {
|
||||
result->steps = step + 1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
result->steps = REGRESS_MAX_HANDSHAKE_STEPS;
|
||||
}
|
||||
|
||||
/* With wolfSSH_SetAppChannels() on, accept() stops once the user is
|
||||
* authenticated and the shell request lands on the callback instead. */
|
||||
static void TestAppChannelsAcceptStopsAtUserAuth(void)
|
||||
{
|
||||
KexReplyHarness harness;
|
||||
KexReplyRunResult result;
|
||||
|
||||
appChannelsShellReqCount = 0;
|
||||
|
||||
InitKexReplyHarness(&harness, "rsa-sha2-256", REGRESS_SERVER_KEY_PATH,
|
||||
0, NULL);
|
||||
AssertIntEQ(wolfSSH_CTX_SetChannelReqShellCb(harness.serverCtx,
|
||||
AppChannelsShellCb), WS_SUCCESS);
|
||||
AssertIntEQ(wolfSSH_SetAppChannels(harness.server, 1), WS_SUCCESS);
|
||||
|
||||
RunAppChannelsHandshake(&harness, &result);
|
||||
|
||||
AssertTrue(result.clientSuccess);
|
||||
AssertTrue(result.serverSuccess);
|
||||
AssertIntEQ(harness.server->acceptState, ACCEPT_SERVER_USERAUTH_SENT);
|
||||
AssertIntEQ(harness.server->clientState, CLIENT_DONE);
|
||||
AssertIntEQ(appChannelsShellReqCount, 1);
|
||||
AssertIntEQ(harness.client->connectState,
|
||||
CONNECT_SERVER_CHANNEL_REQUEST_DONE);
|
||||
AssertFalse(harness.clientIo.sawDisconnect);
|
||||
AssertFalse(harness.serverIo.sawDisconnect);
|
||||
|
||||
FreeKexReplyHarness(&harness);
|
||||
}
|
||||
|
||||
/* Same mode, no callback registered: nothing can start the shell once
|
||||
* accept() has returned, so the request is refused. The default mode
|
||||
* accepts it, which AssertHandshakeSucceeds() covers. */
|
||||
static void TestAppChannelsNoShellCbRejects(void)
|
||||
{
|
||||
KexReplyHarness harness;
|
||||
KexReplyRunResult result;
|
||||
|
||||
InitKexReplyHarness(&harness, "rsa-sha2-256", REGRESS_SERVER_KEY_PATH,
|
||||
0, NULL);
|
||||
AssertIntEQ(wolfSSH_SetAppChannels(harness.server, 1), WS_SUCCESS);
|
||||
|
||||
RunAppChannelsHandshake(&harness, &result);
|
||||
|
||||
/* RunAppChannelsHandshake() also leaves clientSuccess clear when it
|
||||
* runs out of steps with neither side erroring, so pin the refusal
|
||||
* itself: the client stopped early, and for the right reason. */
|
||||
AssertFalse(result.clientSuccess);
|
||||
AssertTrue(result.steps < REGRESS_MAX_HANDSHAKE_STEPS);
|
||||
AssertIntEQ(result.clientErr, WS_CHANOPEN_FAILED);
|
||||
AssertTrue(harness.client->connectState <
|
||||
CONNECT_SERVER_CHANNEL_REQUEST_DONE);
|
||||
AssertIntEQ(harness.server->acceptState, ACCEPT_SERVER_USERAUTH_SENT);
|
||||
|
||||
FreeKexReplyHarness(&harness);
|
||||
}
|
||||
|
||||
/* The flag is documented as a context setting first, so pin the setter
|
||||
* returns and the inheritance wolfSSH_new() does. */
|
||||
static void TestAppChannelsCtxInherits(void)
|
||||
{
|
||||
WOLFSSH_CTX* ctx;
|
||||
WOLFSSH* ssh;
|
||||
|
||||
AssertIntEQ(wolfSSH_CTX_SetAppChannels(NULL, 1), WS_SSH_CTX_NULL_E);
|
||||
AssertIntEQ(wolfSSH_SetAppChannels(NULL, 1), WS_SSH_NULL_E);
|
||||
|
||||
ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL);
|
||||
AssertNotNull(ctx);
|
||||
|
||||
ssh = wolfSSH_new(ctx);
|
||||
AssertNotNull(ssh);
|
||||
AssertIntEQ(ssh->appChannels, 0);
|
||||
wolfSSH_free(ssh);
|
||||
|
||||
AssertIntEQ(wolfSSH_CTX_SetAppChannels(ctx, 1), WS_SUCCESS);
|
||||
|
||||
ssh = wolfSSH_new(ctx);
|
||||
AssertNotNull(ssh);
|
||||
AssertIntEQ(ssh->appChannels, 1);
|
||||
AssertIntEQ(wolfSSH_SetAppChannels(ssh, 0), WS_SUCCESS);
|
||||
AssertIntEQ(ssh->appChannels, 0);
|
||||
wolfSSH_free(ssh);
|
||||
|
||||
wolfSSH_CTX_free(ctx);
|
||||
}
|
||||
|
||||
/* Turning the mode on after accept() established the session must not leave
|
||||
* the accept loop hunting for a state it has already stepped past. */
|
||||
static void TestAppChannelsLateEnableReturns(void)
|
||||
{
|
||||
KexReplyHarness harness;
|
||||
KexReplyRunResult result;
|
||||
|
||||
InitKexReplyHarness(&harness, "rsa-sha2-256", REGRESS_SERVER_KEY_PATH,
|
||||
0, NULL);
|
||||
|
||||
RunKexReplyHandshake(&harness, &result);
|
||||
|
||||
AssertTrue(result.serverSuccess);
|
||||
AssertIntEQ(harness.server->acceptState,
|
||||
ACCEPT_CLIENT_SESSION_ESTABLISHED);
|
||||
|
||||
AssertIntEQ(wolfSSH_SetAppChannels(harness.server, 1), WS_SUCCESS);
|
||||
AssertIntEQ(wolfSSH_accept(harness.server), WS_SUCCESS);
|
||||
AssertIntEQ(harness.server->acceptState,
|
||||
ACCEPT_CLIENT_SESSION_ESTABLISHED);
|
||||
|
||||
FreeKexReplyHarness(&harness);
|
||||
}
|
||||
|
||||
static void TestKexDhReplyRejectsRsaSha2_256SigNameDowngrade(void)
|
||||
{
|
||||
AssertHandshakeSucceeds("rsa-sha2-256", REGRESS_SERVER_KEY_PATH);
|
||||
|
|
@ -3701,6 +3880,39 @@ static void TestChannelReqSubsysCallbackRuns(void)
|
|||
WOLFSSH_SESSION_SUBSYSTEM), MSGID_CHANNEL_FAILURE);
|
||||
}
|
||||
|
||||
/* accept() re-entered while it is already parked, with a reply still
|
||||
* queued, has to flush and stay put. Stepping the state on from here
|
||||
* would put the stop behind it, and the loop tests for that state
|
||||
* exactly, so the session would run on to established instead. */
|
||||
static void TestAppChannelsAcceptKeepsStopWithPendingOutput(void)
|
||||
{
|
||||
ChannelOpenHarness harness;
|
||||
WOLFSSH_CHANNEL* channel;
|
||||
|
||||
InitChannelOpenHarness(&harness, NULL, 0);
|
||||
AssertIntEQ(wolfSSH_SetAppChannels(harness.ssh, 1), WS_SUCCESS);
|
||||
|
||||
channel = SeedUnconfirmedChannel(&harness);
|
||||
AssertIntEQ(ChannelUpdatePeer(channel, 5, 1024, 1024), WS_SUCCESS);
|
||||
channel->openConfirmed = 1;
|
||||
|
||||
/* A blocked send leaves the channel data queued. Nothing here drives
|
||||
* a channel request, so clientState stays short of CLIENT_DONE and a
|
||||
* state stepped past the stop fails the accept below rather than
|
||||
* spinning in it. */
|
||||
harness.io.blockNext = 1;
|
||||
AssertIntEQ(wolfSSH_stream_send(harness.ssh, (byte*)"x", 1), 1);
|
||||
AssertTrue(harness.ssh->outputBuffer.length > 0);
|
||||
AssertTrue(harness.ssh->clientState < CLIENT_DONE);
|
||||
|
||||
AssertIntEQ(harness.ssh->acceptState, ACCEPT_SERVER_USERAUTH_SENT);
|
||||
AssertIntEQ(wolfSSH_accept(harness.ssh), WS_SUCCESS);
|
||||
AssertIntEQ(harness.ssh->acceptState, ACCEPT_SERVER_USERAUTH_SENT);
|
||||
AssertIntEQ(harness.ssh->outputBuffer.length, 0);
|
||||
|
||||
FreeChannelOpenHarness(&harness);
|
||||
}
|
||||
|
||||
/* A username change after the first userauth request must end the session. */
|
||||
static void TestUsernameChangeDisconnects(void)
|
||||
{
|
||||
|
|
@ -13764,6 +13976,7 @@ int main(int argc, char** argv)
|
|||
TestChannelCloseCallbackReturnIgnored();
|
||||
TestChannelReqExecCallbackRuns();
|
||||
TestChannelReqSubsysCallbackRuns();
|
||||
TestAppChannelsAcceptKeepsStopWithPendingOutput();
|
||||
TestSecondSessionChannelRejected();
|
||||
TestUsernameChangeDisconnects();
|
||||
TestSameUserRetryAllowed();
|
||||
|
|
@ -13968,6 +14181,10 @@ int main(int argc, char** argv)
|
|||
|
||||
#ifdef KEXDH_REPLY_REGRESS_KEX_ALGO
|
||||
#ifndef WOLFSSH_NO_RSA_SHA2_256
|
||||
TestAppChannelsCtxInherits();
|
||||
TestAppChannelsAcceptStopsAtUserAuth();
|
||||
TestAppChannelsNoShellCbRejects();
|
||||
TestAppChannelsLateEnableReturns();
|
||||
TestKexDhReplyRejectsRsaSha2_256SigNameDowngrade();
|
||||
#endif
|
||||
#ifndef WOLFSSH_NO_RSA_SHA2_512
|
||||
|
|
|
|||
62
tests/unit.c
62
tests/unit.c
|
|
@ -9421,6 +9421,68 @@ static int test_DoChannelRequest(void)
|
|||
}
|
||||
#endif /* WOLFSSH_SHELL && WOLFSSH_TERM */
|
||||
|
||||
/* Application-driven channels flip the no-callback default: with
|
||||
* accept() already returned there is nothing left to start a shell,
|
||||
* exec or subsystem, so all three are refused rather than accepted. */
|
||||
{
|
||||
static const byte paySubsys[] = {
|
||||
0x00,0x00,0x00,0x00, /* channelId = 0 */
|
||||
0x00,0x00,0x00,0x09, /* typeSz = 9 */
|
||||
0x73,0x75,0x62,0x73,0x79,0x73,
|
||||
0x74,0x65,0x6D, /* "subsystem" */
|
||||
0x01, /* wantReply = 1 */
|
||||
0x00,0x00,0x00,0x04, /* nameSz = 4 */
|
||||
0x73,0x66,0x74,0x70 /* "sftp" */
|
||||
};
|
||||
struct {
|
||||
const char* label;
|
||||
const byte* payload;
|
||||
word32 payloadSz;
|
||||
int errBase;
|
||||
} appCases[] = {
|
||||
{ "shell", payShell, (word32)sizeof(payShell), -495 },
|
||||
{ "exec", payExec, (word32)sizeof(payExec), -497 },
|
||||
{ "subsystem", paySubsys, (word32)sizeof(paySubsys), -499 }
|
||||
};
|
||||
int a;
|
||||
|
||||
for (a = 0; a < (int)(sizeof(appCases) / sizeof(appCases[0])); a++) {
|
||||
word32 idxApp = 0;
|
||||
int retApp, capMsgId;
|
||||
|
||||
if (wolfSSH_SetAppChannels(ssh, 1) != WS_SUCCESS) {
|
||||
printf("DoChannelRequest[app-%s]: set failed\n",
|
||||
appCases[a].label);
|
||||
result = appCases[a].errBase;
|
||||
goto done;
|
||||
}
|
||||
|
||||
s_chanReqCaptureSz = 0;
|
||||
WMEMSET(s_chanReqCapture, 0, sizeof(s_chanReqCapture));
|
||||
|
||||
retApp = wolfSSH_TestDoChannelRequest(ssh,
|
||||
(byte*)appCases[a].payload, appCases[a].payloadSz,
|
||||
&idxApp);
|
||||
wolfSSH_SetAppChannels(ssh, 0);
|
||||
|
||||
if (retApp != WS_SUCCESS) {
|
||||
printf("DoChannelRequest[app-%s]: ret=%d, expected=%d\n",
|
||||
appCases[a].label, retApp, WS_SUCCESS);
|
||||
result = appCases[a].errBase;
|
||||
goto done;
|
||||
}
|
||||
|
||||
capMsgId = CaptureMsgId(s_chanReqCapture, s_chanReqCaptureSz);
|
||||
if (capMsgId != (int)MSGID_CHANNEL_FAILURE) {
|
||||
printf("DoChannelRequest[app-%s]: msg_id=0x%02x, "
|
||||
"expected=0x%02x\n", appCases[a].label, capMsgId,
|
||||
MSGID_CHANNEL_FAILURE);
|
||||
result = appCases[a].errBase - 1;
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
done:
|
||||
wolfSSH_free(ssh);
|
||||
wolfSSH_CTX_free(ctx);
|
||||
|
|
|
|||
Loading…
Reference in New Issue