@@ -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. */
43494449static 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();
0 commit comments