Skip to content

Commit 5ce4c71

Browse files
committed
internal: re-find the channel after the callback
The generic channel request callback may free the channel it was handed, so DoChannelRequest() looks it up again before the type handling reads it. Gone, the request ends there, and a reply the peer wanted fails on the missing channel the way one after a typed session callback does. - ssh.h says the callback may free its channel and what the request does from there - regress.c frees the channel from the callback on each of the three answers, and on a pty-req, the type that wrote to the channel outside a session request
1 parent 8fc8f4f commit 5ce4c71

3 files changed

Lines changed: 125 additions & 2 deletions

File tree

‎src/internal.c‎

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13302,6 +13302,16 @@ static int DoChannelRequest(WOLFSSH* ssh,
1330213302
else if (decision == WOLFSSH_REQ_ACCEPT) {
1330313303
granted = 1;
1330413304
}
13305+
13306+
/* A callback may free its own channel, so look it up again
13307+
* before the handling below reads it. Gone, the request ends
13308+
* here, and a wanted reply fails on the missing channel the
13309+
* way one after a typed callback does. */
13310+
channel = ChannelFind(ssh, channelId, WS_CHANNEL_ID_SELF);
13311+
if (channel == NULL) {
13312+
WLOG(WS_LOG_DEBUG,
13313+
" channel request callback freed the channel.");
13314+
}
1330513315
}
1330613316
}
1330713317

@@ -13323,7 +13333,7 @@ static int DoChannelRequest(WOLFSSH* ssh,
1332313333
}
1332413334
#endif
1332513335

13326-
if (ret == WS_SUCCESS && !rej) {
13336+
if (ret == WS_SUCCESS && !rej && channel != NULL) {
1332713337
if (ChannelRequestIs(type, typeSz, "env")) {
1332813338
char name[WOLFSSH_MAX_NAMESZ];
1332913339
word32 nameSz;

‎tests/regress.c‎

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4345,6 +4345,106 @@ static void TestChannelReqCallbackGrantOverridesAppChannels(void)
43454345
FreeChannelOpenHarness(&harness);
43464346
}
43474347

4348+
/* A generic channel request callback that frees the channel it was handed,
4349+
* the way a policy that tears the channel down in place does, and answers
4350+
* anyReqCbReturn. */
4351+
static int FreeingChannelReqAnyCb(WOLFSSH_CHANNEL* channel, const byte* type,
4352+
word32 typeSz, const byte* data, word32 dataSz, int wantReply,
4353+
void* ctx)
4354+
{
4355+
RecordAnyReq(type, typeSz, data, dataSz, ctx);
4356+
anyReqCbWantReply = wantReply;
4357+
AssertIntEQ(wolfSSH_ChannelFree(channel), WS_SUCCESS);
4358+
4359+
return anyReqCbReturn;
4360+
}
4361+
4362+
/* Runs one request through a fresh harness whose generic callback frees the
4363+
* channel and answers anyReturn, and returns what DoReceive() made of it.
4364+
* The channel id is handed back, since the channel itself is gone. */
4365+
static int RunRequestThroughFreeingCb(ChannelOpenHarness* harness,
4366+
word32* channelId, const char* type, byte wantReply,
4367+
const byte* tail, word32 tailSz, int anyReturn)
4368+
{
4369+
WOLFSSH_CHANNEL* channel;
4370+
byte in[192];
4371+
word32 inSz;
4372+
4373+
ResetAnyReqCb(anyReturn);
4374+
typedReqCbCalls = 0;
4375+
InitChannelOpenHarness(harness, NULL, 0);
4376+
AssertIntEQ(wolfSSH_CTX_SetChannelReqAnyCb(harness->ctx,
4377+
FreeingChannelReqAnyCb), WS_SUCCESS);
4378+
AssertIntEQ(wolfSSH_CTX_SetChannelReqExecCb(harness->ctx,
4379+
CountingSessionReqCb), WS_SUCCESS);
4380+
channel = SeedConfirmedSessionChannel(harness);
4381+
*channelId = channel->channel;
4382+
4383+
inSz = BuildChannelRequestPacket(*channelId, type, wantReply,
4384+
tail, tailSz, in, sizeof(in));
4385+
RepointHarnessInput(harness, in, inSz);
4386+
4387+
return DoReceive(harness->ssh);
4388+
}
4389+
4390+
/* The generic callback may free the channel it was called on. Nothing below
4391+
* it reads the channel again after that, and the request ends there: the
4392+
* type is not handled, no typed callback runs, and a wanted reply fails on
4393+
* the channel that is gone. */
4394+
static void TestChannelReqCallbackMayFreeChannel(void)
4395+
{
4396+
ChannelOpenHarness harness;
4397+
word32 channelId;
4398+
byte tail[32];
4399+
word32 tailSz;
4400+
4401+
/* Left unhandled, the exec handling would read the channel next. */
4402+
tailSz = AppendString(tail, sizeof(tail), 0, "ls");
4403+
AssertIntEQ(RunRequestThroughFreeingCb(&harness, &channelId, "exec", 1,
4404+
tail, tailSz, WOLFSSH_REQ_UNHANDLED), WS_FATAL_ERROR);
4405+
AssertIntEQ(harness.ssh->error, WS_INVALID_CHANID);
4406+
AssertIntEQ(anyReqCbCalls, 1);
4407+
AssertIntEQ(typedReqCbCalls, 0);
4408+
AssertIntEQ(harness.io.outSz, 0);
4409+
AssertNull(ChannelFind(harness.ssh, channelId, WS_CHANNEL_ID_SELF));
4410+
FreeChannelOpenHarness(&harness);
4411+
4412+
/* Granted, the same request would have committed the session on it. */
4413+
AssertIntEQ(RunRequestThroughFreeingCb(&harness, &channelId, "exec", 1,
4414+
tail, tailSz, WOLFSSH_REQ_ACCEPT), WS_FATAL_ERROR);
4415+
AssertIntEQ(harness.ssh->error, WS_INVALID_CHANID);
4416+
AssertIntEQ(typedReqCbCalls, 0);
4417+
AssertTrue(harness.ssh->clientState < CLIENT_DONE);
4418+
AssertNull(ChannelFind(harness.ssh, channelId, WS_CHANNEL_ID_SELF));
4419+
FreeChannelOpenHarness(&harness);
4420+
4421+
/* Refused, the handling was skipped whatever the channel did. */
4422+
AssertIntEQ(RunRequestThroughFreeingCb(&harness, &channelId, "exec", 1,
4423+
tail, tailSz, WOLFSSH_REQ_REJECT), WS_FATAL_ERROR);
4424+
AssertIntEQ(harness.ssh->error, WS_INVALID_CHANID);
4425+
AssertIntEQ(typedReqCbCalls, 0);
4426+
FreeChannelOpenHarness(&harness);
4427+
4428+
#ifdef WOLFSSH_TERM
4429+
/* With no reply wanted there is nothing left to fail, so the packet is
4430+
* taken and the session carries on. A pty-req is the case that wrote to
4431+
* the channel outside a session request. */
4432+
tailSz = AppendString(tail, sizeof(tail), 0, "vt100");
4433+
tailSz = AppendUint32(tail, sizeof(tail), tailSz, 80);
4434+
tailSz = AppendUint32(tail, sizeof(tail), tailSz, 24);
4435+
tailSz = AppendUint32(tail, sizeof(tail), tailSz, 0);
4436+
tailSz = AppendUint32(tail, sizeof(tail), tailSz, 0);
4437+
tailSz = AppendString(tail, sizeof(tail), tailSz, "");
4438+
AssertIntEQ(RunRequestThroughFreeingCb(&harness, &channelId, "pty-req", 0,
4439+
tail, tailSz, WOLFSSH_REQ_UNHANDLED), WS_SUCCESS);
4440+
AssertIntEQ(harness.ssh->error, WS_SUCCESS);
4441+
AssertIntEQ(anyReqCbCalls, 1);
4442+
AssertIntEQ(harness.io.outSz, 0);
4443+
AssertNull(ChannelFind(harness.ssh, channelId, WS_CHANNEL_ID_SELF));
4444+
FreeChannelOpenHarness(&harness);
4445+
#endif /* WOLFSSH_TERM */
4446+
}
4447+
43484448
/* Both setters answer a NULL context, the only error either has. */
43494449
static void TestReqAnyCallbackSettersRejectNullCtx(void)
43504450
{
@@ -15687,6 +15787,7 @@ int main(int argc, char** argv)
1568715787
TestChannelReqCallbackSeesWholeType();
1568815788
TestChannelReqCallbackSettlesSessionRequest();
1568915789
TestChannelReqCallbackGrantOverridesAppChannels();
15790+
TestChannelReqCallbackMayFreeChannel();
1569015791
TestReqAnyCallbackSettersRejectNullCtx();
1569115792
#ifdef WOLFSSH_TERM
1569215793
TestChannelReqCallbackKeepsTypeChecks();

‎wolfssh/ssh.h‎

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -495,7 +495,19 @@ typedef enum WS_ReqCbResult {
495495
* kept; a request that does not fit its type is refused whatever the
496496
* callback said. A type the library does not know is answered
497497
* CHANNEL_SUCCESS on ACCEPT, where it is otherwise refused. Shares the
498-
* channel request context. */
498+
* channel request context.
499+
*
500+
* The callback may free the channel it was handed, with
501+
* wolfSSH_ChannelFree(). The request ends there whatever the answer: the
502+
* type is not handled, and a request wanting a reply has nothing left to
503+
* answer on, so it fails with WS_INVALID_CHANID.
504+
*
505+
* type and data point into the session's input buffer and are good only
506+
* for the length of the call, so a callback keeping either copies it.
507+
* The packet is still being parsed, so the callback must not re-enter
508+
* the receive side of the library on this session -- wolfSSH_worker(),
509+
* wolfSSH_stream_read(), wolfSSH_accept(), the SFTP calls -- which may
510+
* grow or compact that buffer and leave both pointers behind. */
499511
typedef int (*WS_CallbackChannelReqAny)(WOLFSSH_CHANNEL* channel,
500512
const byte* type, word32 typeSz, const byte* data, word32 dataSz,
501513
int wantReply, void* ctx);

0 commit comments

Comments
 (0)