Skip to content

Commit 967a2b2

Browse files
committed
Commit forward state before the post-send callback
The forward a "tcpip-forward" or "cancel-tcpip-forward" establishes was committed after SendGlobalRequestFwd() returned, which is after the post-send highwater callback had run. A request that callback sends goes out behind this one but committed ahead of it, so the earlier request had the last word and the client ended up on the opposite side of the forward from the peer. A first setup whose callback cancels it left the forward registered with no listener on the peer, and a cancel whose callback re-establishes the forward unlinked it, refusing every open for a listener the peer holds. - Split the post-send highwater check off wolfSSH_SendPacket() as SendPacketFlush(), for a sender with state to commit first. - SendGlobalRequestFwd() takes the pending forward and settles it inside the send window, then runs the check. - Commit order is send order now, so the last request sent governs, whichever call made it. - FwdPendingCommit() still re-resolves the entry: the IO send callback can reenter mid-flush, which no ordering fixes. - Tests cover a reentrant cancel of a first setup, a reentrant setup during a cancel, and an inbound forwarded-tcpip open pumped from the callback.
1 parent 34c71c2 commit 967a2b2

5 files changed

Lines changed: 181 additions & 39 deletions

File tree

src/internal.c

Lines changed: 47 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4919,10 +4919,15 @@ static int GetInputLine(WOLFSSH* ssh, byte** pEol)
49194919
}
49204920

49214921

4922-
/* returns WS_SUCCESS on success */
4923-
int wolfSSH_SendPacket(WOLFSSH* ssh)
4922+
/* Push everything framed at the peer, stopping short of the post-send
4923+
* highwater check. A sender with state to commit runs that check itself, after
4924+
* committing: the callback it fires can reenter the library and send a request
4925+
* of its own, which goes out behind this one and has to commit behind it too.
4926+
*
4927+
* returns WS_SUCCESS on success */
4928+
static int SendPacketFlush(WOLFSSH* ssh)
49244929
{
4925-
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_SendPacket()");
4930+
WLOG(WS_LOG_DEBUG, "Entering SendPacketFlush()");
49264931

49274932
if (ssh->ctx->ioSendCb == NULL) {
49284933
WLOG(WS_LOG_DEBUG, "Your IO Send callback is null, please set");
@@ -4999,7 +5004,23 @@ int wolfSSH_SendPacket(WOLFSSH* ssh)
49995004

50005005
WLOG(WS_LOG_DEBUG, "SB: Shrinking output buffer");
50015006
ShrinkBuffer(&ssh->outputBuffer, 0);
5002-
return HighwaterCheck(ssh, WOLFSSH_HWSIDE_TRANSMIT);
5007+
return WS_SUCCESS;
5008+
}
5009+
5010+
5011+
/* returns WS_SUCCESS on success */
5012+
int wolfSSH_SendPacket(WOLFSSH* ssh)
5013+
{
5014+
int ret;
5015+
5016+
ret = SendPacketFlush(ssh);
5017+
5018+
/* Only a complete flush reaches the check, as the peer has the whole
5019+
* packet by then. */
5020+
if (ret == WS_SUCCESS)
5021+
ret = HighwaterCheck(ssh, WOLFSSH_HWSIDE_TRANSMIT);
5022+
5023+
return ret;
50035024
}
50045025

50055026

@@ -17678,23 +17699,24 @@ int SendGlobalRequest(WOLFSSH* ssh,
1767817699
#ifdef WOLFSSH_FWD
1767917700
/* Send a "tcpip-forward" or "cancel-tcpip-forward" global request. The bind
1768017701
* address and port follow the want-reply boolean, an ordering the generic
17681-
* SendGlobalRequest() framing cannot express. RFC 4254 7.1. */
17702+
* SendGlobalRequest() framing cannot express. RFC 4254 7.1.
17703+
*
17704+
* What FwdRemotePrepare() built for the request is settled here rather than by
17705+
* the caller, since it has to happen inside the send window. */
1768217706
int SendGlobalRequestFwd(WOLFSSH* ssh,
1768317707
const char* bindAddr, word32 bindPort, int isCancel, int wantReply,
17684-
int* sent)
17708+
WOLFSSH_FWD_PENDING* pend)
1768517709
{
1768617710
byte* output;
1768717711
word32 idx = 0;
1768817712
word32 reqNameSz;
1768917713
word32 bindAddrSz;
1769017714
const char* reqName;
17715+
int sent = 0;
1769117716
int ret = WS_SUCCESS;
1769217717

1769317718
WLOG(WS_LOG_DEBUG, "Entering SendGlobalRequestFwd()");
1769417719

17695-
if (sent != NULL)
17696-
*sent = 0;
17697-
1769817720
if (ssh == NULL || bindAddr == NULL)
1769917721
ret = WS_BAD_ARGUMENT;
1770017722

@@ -17732,12 +17754,24 @@ int SendGlobalRequestFwd(WOLFSSH* ssh,
1773217754
if (ret == WS_SUCCESS) {
1773317755
word32 flushes = ssh->txFlushCount;
1773417756

17735-
ret = wolfSSH_SendPacket(ssh);
17736-
17737-
if (sent != NULL)
17738-
*sent = SendPacketDelivered(ssh, flushes, ret);
17757+
ret = SendPacketFlush(ssh);
17758+
sent = SendPacketDelivered(ssh, flushes, ret);
1773917759
}
1774017760

17761+
/* Whether the peer will bind the listener, not whether this call
17762+
* succeeded: a request still framed and waiting to flush reaches it. Only
17763+
* what never left unwinds. */
17764+
if (sent)
17765+
FwdPendingCommit(ssh, pend);
17766+
else
17767+
FwdPendingDiscard(ssh, pend);
17768+
17769+
/* Held back until the commit is done. The callback can reenter and send a
17770+
* request of its own, which goes out behind this one, and the last request
17771+
* sent is the one that governs. */
17772+
if (ret == WS_SUCCESS)
17773+
ret = HighwaterCheck(ssh, WOLFSSH_HWSIDE_TRANSMIT);
17774+
1774117775
WLOG(WS_LOG_DEBUG, "Leaving SendGlobalRequestFwd(), ret = %d", ret);
1774217776

1774317777
return ret;

src/ssh.c

Lines changed: 6 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3902,21 +3902,11 @@ int wolfSSH_FwdRemoteSetup(WOLFSSH* ssh, const char* bindAddr,
39023902
if (ret == WS_SUCCESS)
39033903
ret = FwdRemotePrepare(ssh, bindAddr, bindPort, wantReply, 0, &pend);
39043904

3905-
if (ret == WS_SUCCESS) {
3906-
int sent = 0;
3907-
3905+
/* The send settles pend: what reached the peer registers, even when the
3906+
* post-send highwater callback reports an error afterwards. */
3907+
if (ret == WS_SUCCESS)
39083908
ret = SendGlobalRequestFwd(ssh, bindAddr, bindPort, 0, wantReply,
3909-
&sent);
3910-
3911-
/* Whether the peer will bind the listener, not whether this call
3912-
* succeeded: a request still framed and waiting to flush reaches it,
3913-
* and so does one the post-send highwater callback reports an error
3914-
* for. Only what never left unwinds. */
3915-
if (sent)
3916-
FwdPendingCommit(ssh, &pend);
3917-
else
3918-
FwdPendingDiscard(ssh, &pend);
3919-
}
3909+
&pend);
39203910

39213911
WLOG(WS_LOG_DEBUG, "Leaving wolfSSH_FwdRemoteSetup(), ret = %d", ret);
39223912
return ret;
@@ -3956,17 +3946,9 @@ int wolfSSH_FwdRemoteCancel(WOLFSSH* ssh, const char* bindAddr,
39563946
if (ret == WS_SUCCESS)
39573947
ret = FwdRemotePrepare(ssh, bindAddr, bindPort, wantReply, 1, &pend);
39583948

3959-
if (ret == WS_SUCCESS) {
3960-
int sent = 0;
3961-
3949+
if (ret == WS_SUCCESS)
39623950
ret = SendGlobalRequestFwd(ssh, bindAddr, bindPort, 1, wantReply,
3963-
&sent);
3964-
3965-
if (sent)
3966-
FwdPendingCommit(ssh, &pend);
3967-
else
3968-
FwdPendingDiscard(ssh, &pend);
3969-
}
3951+
&pend);
39703952

39713953
WLOG(WS_LOG_DEBUG, "Leaving wolfSSH_FwdRemoteCancel(), ret = %d", ret);
39723954
return ret;

tests/regress.c

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4570,6 +4570,123 @@ static void TestForwardedTcpipCancelAnsweredDuringResetupKeepsForward(void)
45704570
/* A peer that canonicalises the bind it echoes back has every open refused
45714571
* under the default, so the port it was asked for can be made the whole
45724572
* test. */
4573+
static int CancelFirstSetupHighwaterCb(byte side, void* ctx)
4574+
{
4575+
WOLFSSH* ssh = (WOLFSSH*)ctx;
4576+
4577+
WOLFSSH_UNUSED(side);
4578+
4579+
if (ssh != NULL)
4580+
wolfSSH_FwdRemoteCancel(ssh, "127.0.0.1", 8080, 0);
4581+
4582+
return WS_SUCCESS;
4583+
}
4584+
4585+
/* The callback cancels the very forward the request in flight is establishing,
4586+
* and with no earlier registration to find it has nothing to work from but
4587+
* what this request left. The cancel went out behind the setup, so the peer
4588+
* holds no listener and neither may this side. */
4589+
static void TestForwardedTcpipReentrantCancelOfFirstSetup(void)
4590+
{
4591+
ChannelOpenHarness harness;
4592+
4593+
InitFwdRemoteHarness(&harness);
4594+
4595+
wolfSSH_SetHighwaterCb(harness.ctx, 1, CancelFirstSetupHighwaterCb);
4596+
wolfSSH_SetHighwaterCtx(harness.ssh, harness.ssh);
4597+
/* Cross the mark on the request's own send. */
4598+
harness.ssh->highwaterMark = 1;
4599+
harness.ssh->txCount = 1;
4600+
4601+
AssertIntEQ(wolfSSH_FwdRemoteSetup(harness.ssh, "127.0.0.1", 8080, 0),
4602+
WS_SUCCESS);
4603+
4604+
AssertIntEQ(FwdRemoteCount(harness.ssh), 0);
4605+
4606+
harness.io.outSz = 0;
4607+
AssertForwardedOpenRefused(&harness, "127.0.0.1", 8080);
4608+
4609+
FreeChannelOpenHarness(&harness);
4610+
}
4611+
4612+
static int SetupDuringCancelHighwaterCb(byte side, void* ctx)
4613+
{
4614+
WOLFSSH* ssh = (WOLFSSH*)ctx;
4615+
4616+
WOLFSSH_UNUSED(side);
4617+
4618+
if (ssh != NULL)
4619+
wolfSSH_FwdRemoteSetup(ssh, "127.0.0.1", 8080, 0);
4620+
4621+
return WS_SUCCESS;
4622+
}
4623+
4624+
/* The same window the other way around: the callback re-establishes the
4625+
* forward the cancel in flight is taking down. The setup went out behind the
4626+
* cancel, so the peer binds a listener and this side keeps matching for it. */
4627+
static void TestForwardedTcpipReentrantSetupDuringCancel(void)
4628+
{
4629+
ChannelOpenHarness harness;
4630+
4631+
InitFwdRemoteHarness(&harness);
4632+
4633+
AssertIntEQ(wolfSSH_FwdRemoteSetup(harness.ssh, "127.0.0.1", 8080, 0),
4634+
WS_SUCCESS);
4635+
4636+
wolfSSH_SetHighwaterCb(harness.ctx, 1, SetupDuringCancelHighwaterCb);
4637+
wolfSSH_SetHighwaterCtx(harness.ssh, harness.ssh);
4638+
harness.ssh->highwaterMark = 1;
4639+
harness.ssh->txCount = 1;
4640+
4641+
AssertIntEQ(wolfSSH_FwdRemoteCancel(harness.ssh, "127.0.0.1", 8080, 0),
4642+
WS_SUCCESS);
4643+
4644+
AssertIntEQ(FwdRemoteCount(harness.ssh), 1);
4645+
4646+
harness.io.outSz = 0;
4647+
AssertForwardedOpenRefused(&harness, "10.0.0.1", 9999);
4648+
AssertForwardedOpenAccepted(&harness, "127.0.0.1", 8080, 1);
4649+
4650+
FreeChannelOpenHarness(&harness);
4651+
}
4652+
4653+
static int InboundOpenDuringSendHighwaterCb(byte side, void* ctx)
4654+
{
4655+
ChannelOpenHarness* harness = (ChannelOpenHarness*)ctx;
4656+
4657+
WOLFSSH_UNUSED(side);
4658+
4659+
/* The request is on the wire, so matching has to be live already: this is
4660+
* the first setup, and until it registers nothing is tracked and every
4661+
* open goes unchecked. */
4662+
AssertIntEQ(harness->ssh->fwdRemoteTracked, 1);
4663+
AssertForwardedOpenRefused(harness, "10.0.0.1", 9999);
4664+
4665+
return WS_SUCCESS;
4666+
}
4667+
4668+
/* A callback that pumps the session sees the forwards the request in flight
4669+
* established, not the ones it found on the way in. */
4670+
static void TestForwardedTcpipInboundOpenDuringSend(void)
4671+
{
4672+
ChannelOpenHarness harness;
4673+
4674+
InitFwdRemoteHarness(&harness);
4675+
4676+
wolfSSH_SetHighwaterCb(harness.ctx, 1, InboundOpenDuringSendHighwaterCb);
4677+
wolfSSH_SetHighwaterCtx(harness.ssh, &harness);
4678+
harness.ssh->highwaterMark = 1;
4679+
harness.ssh->txCount = 1;
4680+
4681+
AssertIntEQ(wolfSSH_FwdRemoteSetup(harness.ssh, "127.0.0.1", 8080, 0),
4682+
WS_SUCCESS);
4683+
4684+
harness.io.outSz = 0;
4685+
AssertForwardedOpenAccepted(&harness, "127.0.0.1", 8080, 1);
4686+
4687+
FreeChannelOpenHarness(&harness);
4688+
}
4689+
45734690
static void TestFwdRemoteMatchPortIgnoresBindAddr(void)
45744691
{
45754692
RunForwardedTcpipMatchModeTest(WOLFSSH_FWD_MATCH_STRICT, "localhost", 8080,
@@ -10348,6 +10465,9 @@ int main(int argc, char** argv)
1034810465
TestForwardedTcpipRequestAfterReplyDuringSend();
1034910466
TestForwardedTcpipCancelAnsweredDuringResetupKeepsForward();
1035010467
TestForwardedTcpipReentrantCancelDuringSend();
10468+
TestForwardedTcpipReentrantCancelOfFirstSetup();
10469+
TestForwardedTcpipReentrantSetupDuringCancel();
10470+
TestForwardedTcpipInboundOpenDuringSend();
1035110471
TestFwdRemoteMatchPortIgnoresBindAddr();
1035210472
TestFwdRemoteMatchOffAcceptsUnregistered();
1035310473
TestFwdRemoteMatchRejectsBadSetting();

wolfssh/internal.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1638,9 +1638,13 @@ WOLFSSH_LOCAL int SendGlobalRequestFwdSuccess(WOLFSSH * ssh, int success,
16381638
WOLFSSH_LOCAL int SendGlobalRequest(WOLFSSH * ssh,
16391639
const unsigned char * data, word32 dataSz, int reply, int* sent);
16401640
#ifdef WOLFSSH_FWD
1641+
/* Sends the request and settles pend with it: committed once the request is on
1642+
* its way to the peer, discarded when it never left. Both happen before the
1643+
* post-send highwater callback runs, so a request that callback sends commits
1644+
* behind this one. */
16411645
WOLFSSH_LOCAL int SendGlobalRequestFwd(WOLFSSH* ssh,
16421646
const char* bindAddr, word32 bindPort, int isCancel, int wantReply,
1643-
int* sent);
1647+
WOLFSSH_FWD_PENDING* pend);
16441648
/* On success pend holds what to commit once the request reaches the wire; on
16451649
* error it is zeroed, so there is nothing to commit or give back. */
16461650
WOLFSSH_LOCAL int FwdRemotePrepare(WOLFSSH* ssh, const char* bindAddr,

wolfssh/ssh.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,9 @@ DEPRECATED WOLFSSH_API int wolfSSH_ChannelGetFwdFd(
291291
* Several requests can name one bind at once, and the last one sent governs.
292292
* Registering again while a cancel is outstanding brings the forward back as
293293
* the request goes out, and no answer to that older cancel takes it away
294-
* again, whatever order the peer answers in.
294+
* again, whatever order the peer answers in. A request one of these calls
295+
* makes from a callback it fires is no different: it goes out behind this
296+
* one, so it is the one that governs.
295297
*
296298
* WS_WANT_WRITE means the request is framed and goes out on the next flush,
297299
* with the forward registered. So does an error reported after the request

0 commit comments

Comments
 (0)