Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
247 changes: 207 additions & 40 deletions src/internal.c
Original file line number Diff line number Diff line change
Expand Up @@ -4001,6 +4001,104 @@ static void FwdReplyRebind(WOLFSSH* ssh, const WOLFSSH_FWD_REMOTE* from,
}


/* Put a request on the session's list of requests in flight, where anything
* its own send reaches can see it. */
static void FwdPendingPush(WOLFSSH* ssh, WOLFSSH_FWD_PENDING* pend)
{
pend->next = ssh->fwdPendingHead;
ssh->fwdPendingHead = pend;
}


/* Take it back off, its send being over. Requests nest, so this is not always
* the head: a callback's request commits inside the one that ran it. */
static void FwdPendingPop(WOLFSSH* ssh, WOLFSSH_FWD_PENDING* pend)
{
WOLFSSH_FWD_PENDING* cur;

if (ssh->fwdPendingHead == pend) {
ssh->fwdPendingHead = pend->next;
}
else {
for (cur = ssh->fwdPendingHead; cur != NULL; cur = cur->next) {
if (cur->next == pend) {
cur->next = pend->next;
break;
}
}
}

pend->next = NULL;
}


/* Forget a forward every request still in its send window held a pointer to.
* Those requests resolved it before the send; freeing it without this leaves
* their commits naming memory that is gone. */
static void FwdPendingVoid(WOLFSSH* ssh, const WOLFSSH_FWD_REMOTE* entry)
{
WOLFSSH_FWD_PENDING* pend;

for (pend = ssh->fwdPendingHead; pend != NULL; pend = pend->next) {
if (pend->entry == entry)
pend->entry = NULL;
if (pend->found == entry)
pend->found = NULL;
}
}


/* Hand every request in flight naming one forward over to another. A request
* resolves its forward before its send, so when an answer that send pumped in
* folds two registrations into one, the survivor is what it named. */
static void FwdPendingRebind(WOLFSSH* ssh, const WOLFSSH_FWD_REMOTE* from,
WOLFSSH_FWD_REMOTE* to)
{
WOLFSSH_FWD_PENDING* pend;

for (pend = ssh->fwdPendingHead; pend != NULL; pend = pend->next) {
if (pend->found == from)
pend->found = to;
}
}


/* A forward a request in flight is registering, or NULL. It is not on the
* session's list until that request commits, but it is what the peer is being
* asked for, so a request a callback sends meanwhile names the same one. */
static WOLFSSH_FWD_REMOTE* FwdPendingFind(WOLFSSH* ssh, const char* bindAddr,
word32 bindPort)
{
WOLFSSH_FWD_PENDING* pend;

for (pend = ssh->fwdPendingHead; pend != NULL; pend = pend->next) {
if (pend->entry == NULL || pend->entry->portPending ||
pend->entry->bindPort != bindPort)
continue;
if (WSTRCMP(pend->entry->bindAddr, bindAddr) == 0)
return pend->entry;
}

return NULL;
}


/* Is a cancel for this forward inside its own send window? It is on the wire
* ahead of anything a callback could send from there, so it is already the
* last word on the forward. */
static int FwdPendingHasCancel(WOLFSSH* ssh, const WOLFSSH_FWD_REMOTE* entry)
{
WOLFSSH_FWD_PENDING* pend;

for (pend = ssh->fwdPendingHead; pend != NULL; pend = pend->next) {
if (pend->isCancel && pend->found == entry)
return 1;
}

return 0;
}


static void FwdRemoteUnlink(WOLFSSH* ssh, void* heap,
WOLFSSH_FWD_REMOTE* entry)
{
Expand All @@ -4019,6 +4117,7 @@ static void FwdRemoteUnlink(WOLFSSH* ssh, void* heap,
}

FwdReplyVoid(ssh, entry);
FwdPendingVoid(ssh, entry);

WFREE(entry->bindAddr, heap, DYNTYPE_STRING);
WFREE(entry, heap, DYNTYPE_FWD);
Expand Down Expand Up @@ -4165,10 +4264,12 @@ static void FwdRemoteSettle(WOLFSSH* ssh, WOLFSSH_FWD_REMOTE* entry,
WLOG(WS_LOG_INFO, "Remote forward reply named a port already "
"registered");
/* Requests still queued on the stale entry asked about this bind,
* so they answer for the entry that stands at it now. Unlinking
* without this leaves them naming nothing, and a cancel among
* them would settle no forward. */
* so they answer for the entry that stands at it now, and so does
* one still in its send window. Unlinking without this leaves
* them naming nothing, and a cancel among them would settle no
* forward. */
FwdReplyRebind(ssh, dup, entry);
FwdPendingRebind(ssh, dup, entry);
FwdRemoteUnlink(ssh, ssh->ctx->heap, dup);
}
}
Expand Down Expand Up @@ -4267,10 +4368,13 @@ int FwdRemotePrepare(WOLFSSH* ssh, const char* bindAddr, word32 bindPort,

heap = ssh->ctx->heap;
pend->isCancel = (byte)(isCancel != 0);
pend->bindAddr = bindAddr;
pend->bindPort = bindPort;
found = FwdRemoteFind(ssh, bindAddr, bindPort);

/* A request a send callback is making names the forward the request that
* ran it is registering, which is on the wire but not on the list yet. */
if (found == NULL)
found = FwdPendingFind(ssh, bindAddr, bindPort);

if (isCancel) {
if (found == NULL) {
WOLFSSH_FWD_REMOTE* cur;
Expand Down Expand Up @@ -4339,6 +4443,13 @@ int FwdRemotePrepare(WOLFSSH* ssh, const char* bindAddr, word32 bindPort,
/* An error leaves nothing to commit and nothing to give back. */
if (ret != WS_SUCCESS)
WMEMSET(pend, 0, sizeof(*pend));
else {
/* What this request resolved to is settled here rather than looked up
* again at commit: by then a callback the send ran may have registered
* the same bind anew, and this request went out ahead of it. */
pend->found = found;
FwdPendingPush(ssh, pend);
}

WLOG(WS_LOG_DEBUG, "Leaving FwdRemotePrepare(), ret = %d", ret);
return ret;
Expand Down Expand Up @@ -4370,18 +4481,23 @@ int FwdReplyPrepare(WOLFSSH* ssh, WOLFSSH_FWD_PENDING* pend)
pend->reply = FwdReplyNew(ssh, 0, NULL, 0);
ret = pend->reply == NULL ? WS_MEMORY_E : WS_SUCCESS;

/* It names no forward, but every request in flight is on the list. */
if (ret == WS_SUCCESS)
FwdPendingPush(ssh, pend);

WLOG(WS_LOG_DEBUG, "Leaving FwdReplyPrepare(), ret = %d", ret);
return ret;
}


/* The request reached the wire, so link what was prepared. The registration it
* names is looked up again here: the send runs the application's send and
* highwater callbacks, which can reenter the library and free the entry a
* pointer held across the send would name. */
/* The request reached the wire, so link what was prepared. The forward it names
* was resolved before the send and held on the pending: the send runs the
* application's send and highwater callbacks, which can reenter the library,
* and a lookup from here would find what those did afterwards. */
void FwdPendingCommit(WOLFSSH* ssh, WOLFSSH_FWD_PENDING* pend)
{
WOLFSSH_FWD_REMOTE* target = NULL;
WOLFSSH_FWD_REMOTE* target;
WOLFSSH_FWD_REMOTE* dup;
WOLFSSH_FWD_REMOTE* cur;
void* heap;

Expand All @@ -4392,15 +4508,26 @@ void FwdPendingCommit(WOLFSSH* ssh, WOLFSSH_FWD_PENDING* pend)

heap = ssh->ctx->heap;

if (pend->bindAddr != NULL)
target = FwdRemoteFind(ssh, pend->bindAddr, pend->bindPort);
/* Its send is over, so nothing a later request sends can name it. */
FwdPendingPop(ssh, pend);

if (pend->entry != NULL && target != NULL) {
/* A callback the send ran registered this bind first, so the entry
* built for it is one too many. */
WFREE(pend->entry->bindAddr, heap, DYNTYPE_STRING);
WFREE(pend->entry, heap, DYNTYPE_FWD);
pend->entry = NULL;
target = pend->entry != NULL ? pend->entry : pend->found;
Comment thread
ejohnstown marked this conversation as resolved.
Comment thread
ejohnstown marked this conversation as resolved.
Comment thread
ejohnstown marked this conversation as resolved.

/* An answer the send pumped in named the port this request asks for, so
* the bind stands registered already and the entry built for it is one too
* many. A cancel names a forward by its bind alone, so a bind gets one
* registration. A port-0 request has no port to be found by, and folds
* when the answer to it names one. */
if (pend->entry != NULL && !pend->entry->portPending) {
dup = FwdRemoteFind(ssh, pend->entry->bindAddr, pend->entry->bindPort);
if (dup != NULL) {
/* A request a callback sent named the entry built here, so it
* answers for the one that stands at the bind now. */
FwdReplyRebind(ssh, pend->entry, dup);
FwdRemoteUnlink(ssh, heap, pend->entry);
pend->entry = NULL;
target = dup;
}
}

if (pend->entry != NULL) {
Expand All @@ -4412,12 +4539,6 @@ void FwdPendingCommit(WOLFSSH* ssh, WOLFSSH_FWD_PENDING* pend)
ssh->fwdRemoteList = pend->entry;
else
cur->next = pend->entry;

/* From here on, forwarded-tcpip opens are matched against this list. A
* client that never calls wolfSSH_FwdRemoteSetup() never sets this and
* has its opens go unchecked. */
ssh->fwdRemoteTracked = 1;
target = pend->entry;
}

if (pend->reply != NULL && pend->reply->answered) {
Expand Down Expand Up @@ -4467,9 +4588,13 @@ void FwdPendingDiscard(WOLFSSH* ssh, WOLFSSH_FWD_PENDING* pend)

heap = ssh->ctx->heap;

/* Its send is over, so nothing a later request sends can name it. */
FwdPendingPop(ssh, pend);

if (pend->entry != NULL) {
WFREE(pend->entry->bindAddr, heap, DYNTYPE_STRING);
WFREE(pend->entry, heap, DYNTYPE_FWD);
/* Nothing linked it, so there is no list to take it out of, but a
* request a callback sent may have named it and has to let go. */
FwdRemoteUnlink(ssh, heap, pend->entry);
}
if (pend->reply != NULL) {
/* An answer that arrived mid-send dequeued the slot already, and
Expand All @@ -4486,10 +4611,26 @@ void FwdPendingDiscard(WOLFSSH* ssh, WOLFSSH_FWD_PENDING* pend)
}


/* Does the bind this open names reach that forward? A peer that rewrites the
* bind it echoes back can still be held to the port it was asked for. */
static int FwdRemoteAddrMatch(WOLFSSH* ssh, const WOLFSSH_FWD_REMOTE* entry,
const char* addr, word32 port)
{
/* No port to match on until the peer's reply names the one it bound. */
if (entry->portPending || entry->bindPort != port)
return 0;

return ssh->fwdRemoteMatch == WOLFSSH_FWD_MATCH_PORT ||
FwdRemoteAddrIsWild(entry->bindAddr) ||
WSTRCMP(entry->bindAddr, addr) == 0;
}


/* Does an inbound forwarded-tcpip name a forward this client registered? */
static int FwdRemoteMatch(WOLFSSH* ssh, const char* addr, word32 port)
{
WOLFSSH_FWD_REMOTE* cur;
WOLFSSH_FWD_PENDING* pend;

if (ssh == NULL || addr == NULL)
return 0;
Expand All @@ -4501,29 +4642,53 @@ static int FwdRemoteMatch(WOLFSSH* ssh, const char* addr, word32 port)
for (cur = ssh->fwdRemoteList; cur != NULL; cur = cur->next) {
WOLFSSH_FWD_REPLY* newest;

/* No port to match on until the peer's reply names the one it
* bound. */
if (cur->portPending || cur->bindPort != port)
if (!FwdRemoteAddrMatch(ssh, cur, addr, port))
continue;

/* The newest request governs: a cancel stops matching as it goes out,
* so revoking never waits on the peer, and the peer refusing it puts
* the forward back. */
* the forward back. A cancel still inside its own send counts, since
* the session can only be pumped from a callback that send ran. */
newest = FwdReplyNewest(ssh, cur);
if (newest != NULL && newest->isCancel)
if ((newest != NULL && newest->isCancel) ||
FwdPendingHasCancel(ssh, cur))
continue;

/* A forward stands on the peer having bound it, or on a request still
* owed an answer. With neither, nothing speaks for it. */
if (!cur->confirmed && newest == NULL)
continue;

/* A peer that rewrites the bind it echoes back can still be held to
* the port it was asked for. */
if (ssh->fwdRemoteMatch == WOLFSSH_FWD_MATCH_PORT ||
FwdRemoteAddrIsWild(cur->bindAddr) ||
WSTRCMP(cur->bindAddr, addr) == 0)
return 1;
return 1;
}

/* A setup inside its own send is already on the wire, so the listener it
* asks for can start feeding channels before the call returns. It is the
* newest request for its bind by construction, and needs no reply to speak
* for it. */
for (pend = ssh->fwdPendingHead; pend != NULL; pend = pend->next) {
WOLFSSH_FWD_REPLY* newest;

if (pend->entry == NULL ||
!FwdRemoteAddrMatch(ssh, pend->entry, addr, port))
continue;

/* A cancel a callback sent from this setup's send went out behind it
* and committed, so the newest request governs here too. */
newest = FwdReplyNewest(ssh, pend->entry);
if ((newest != NULL && newest->isCancel) ||
FwdPendingHasCancel(ssh, pend->entry))
continue;

/* An answer that arrived mid-send left the queue for the commit to
* apply, so no scan of it sees this request. The peer refused the
* bind, so nothing speaks for the forward and the commit is about to
* drop it. */
if (pend->reply != NULL && pend->reply->answered &&
!pend->reply->success)
continue;

return 1;
Comment thread
ejohnstown marked this conversation as resolved.
}

return 0;
Expand Down Expand Up @@ -11810,10 +11975,12 @@ static int DoChannelOpen(WOLFSSH* ssh,

/* Per RFC 4254 7.2, a forwarded-tcpip open answers a forward the
* client registered with tcpip-forward, so refuse one naming
* anything else before the policy callback sees it. Only a client
* that used wolfSSH_FwdRemoteSetup() has a list to check. */
* anything else before the policy callback sees it. A client that
* registered nothing has nothing an open can answer for, which is
* why an empty list refuses rather than admits. An application
* keeping its own list can say so with
* wolfSSH_SetFwdRemoteMatch(). */
if (ret == WS_SUCCESS && typeId == ID_CHANTYPE_TCPIP_FORWARD &&
ssh->fwdRemoteTracked &&
!FwdRemoteMatch(ssh, host, hostPort)) {
WLOG(WS_LOG_WARN, "Rejecting forwarded-tcpip channel open "
"for the unregistered forward %s:%u",
Expand Down
Loading
Loading