internal: send LOCAL_CLEANUP on forward delete

WOLFSSH_FWD_LOCAL_CLEANUP is part of the public WS_FwdCbAction contract
and the library never sent it, so an application's handler never ran and
every peer-opened forward leaked what its setup callback allocated.
ChannelDelete() now sends it, so a peer close, an open refused after the
setup ran, wolfSSH_ChannelFree(), and freeing the session all report it.

- record the setup in a new fwdSetupTxd bit on WOLFSSH_CHANNEL: a
  locally opened forward gets no LOCAL_SETUP, so the channel type alone
  cannot say whether the application holds anything
- clear the bit when the cleanup goes out, so a channel reports it once
- pass the channel's id in the port parameter, the way
  WOLFSSH_FWD_CHANNEL_ID does, so an application with two forwards can
  tell which one ended
- TestDirectTcpipFwdCbRejectsChannelId now counts three callback calls
pull/1232/head
John Safranek 2026-09-02 10:36:46 -07:00 committed by philljj
parent f8b72ac079
commit 6485062d3d
3 changed files with 40 additions and 1 deletions

View File

@ -3840,12 +3840,45 @@ WOLFSSH_CHANNEL* ChannelNew(WOLFSSH* ssh, byte channelType,
}
#ifdef WOLFSSH_FWD
/* Counterpart of the WOLFSSH_FWD_LOCAL_SETUP sent when a forwarding channel
* was opened, so the application can release what it set up there. Gated on
* that setup having succeeded: a locally opened forward draws no setup, and
* one that reported failure set nothing up. Cleaning up after either would
* free what the application does not own. Runs from ChannelDelete() so every
* way a channel goes, a peer close, a refused open, wolfSSH_ChannelFree(),
* or the session being freed, reports it once.
* The channel's id rides in the port parameter, the way CHANNEL_ID passes
* it. */
static void NotifyFwdLocalCleanup(WOLFSSH_CHANNEL* channel)
{
WOLFSSH* ssh;
int ret;
if (channel == NULL || !channel->fwdSetupTxd)
return;
ssh = channel->ssh;
if (ssh == NULL || ssh->ctx->fwdCb == NULL)
return;
channel->fwdSetupTxd = 0;
ret = ssh->ctx->fwdCb(WOLFSSH_FWD_LOCAL_CLEANUP, ssh->fwdCbCtx,
NULL, channel->channel);
if (ret != WS_SUCCESS) {
WLOG(WS_LOG_WARN, "Forward cleanup failed for channel %u, ret = %d",
channel->channel, ret);
}
}
#endif /* WOLFSSH_FWD */
void ChannelDelete(WOLFSSH_CHANNEL* channel, void* heap)
{
WOLFSSH_UNUSED(heap);
if (channel) {
#ifdef WOLFSSH_FWD
NotifyFwdLocalCleanup(channel);
if (channel->host)
WFREE(channel->host, heap, DYNTYPE_STRING);
if (channel->origin)
@ -12053,6 +12086,9 @@ static int DoChannelOpen(WOLFSSH* ssh,
ret = ssh->ctx->fwdCb(WOLFSSH_FWD_LOCAL_SETUP,
ssh->fwdCbCtx, host, hostPort);
if (ret == WS_SUCCESS) {
/* The application now owns whatever the setup made,
* so it is owed the matching cleanup. */
newChannel->fwdSetupTxd = 1;
ret = ssh->ctx->fwdCb(WOLFSSH_FWD_CHANNEL_ID,
ssh->fwdCbCtx, NULL, newChannel->channel);
}

View File

@ -3799,7 +3799,9 @@ static void TestDirectTcpipFwdCbRejectsChannelId(void)
* unlike the rejections that set the reason themselves. */
AssertIntEQ(ParseChannelOpenFailReason(harness.io.out, harness.io.outSz),
OPEN_ADMINISTRATIVELY_PROHIBITED);
AssertIntEQ(fwdCbCallCount, 2);
/* Three, not two: the setup that succeeded is owed its cleanup even
* though the open went on to fail. */
AssertIntEQ(fwdCbCallCount, 3);
FreeChannelOpenHarness(&harness);
}

View File

@ -1375,6 +1375,7 @@ struct WOLFSSH_CHANNEL {
byte eofTxd : 1;
byte openConfirmed : 1;
byte ptyReq : 1; /* flag for if interactive pty request was received */
byte fwdSetupTxd : 1; /* a LOCAL_SETUP succeeded, a cleanup is owed */
word32 channel;
word32 windowSz;
word32 maxPacketSz;