Skip to content
Draft
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
282 changes: 186 additions & 96 deletions src/internal.c
Original file line number Diff line number Diff line change
Expand Up @@ -4173,6 +4173,18 @@ static void NotifyFwdLocalCleanup(WOLFSSH_CHANNEL* channel)
#endif /* WOLFSSH_FWD */


/* Wipe a command line, which can carry credentials, and free it. */
static void FreeChannelCommand(void* heap, char* command, word32 commandSz)
{
WOLFSSH_UNUSED(heap);

if (command != NULL) {
WS_FORCEZERO(command, commandSz);
WFREE(command, heap, DYNTYPE_STRING);
}
}


void ChannelDelete(WOLFSSH_CHANNEL* channel, void* heap)
{
WOLFSSH_UNUSED(heap);
Expand Down Expand Up @@ -4200,11 +4212,7 @@ void ChannelDelete(WOLFSSH_CHANNEL* channel, void* heap)
channel->channel);
}
ShrinkBuffer(&channel->extDataBuffer, 1);
/* Scrub the peer's command line, which can carry credentials. */
if (channel->command != NULL) {
WS_FORCEZERO(channel->command, channel->commandSz);
WFREE(channel->command, heap, DYNTYPE_STRING);
}
FreeChannelCommand(heap, channel->command, channel->commandSz);
WFREE(channel, heap, DYNTYPE_CHANNEL);
}
}
Expand Down Expand Up @@ -12178,6 +12186,56 @@ static int DoGlobalRequestFwd(WOLFSSH* ssh,
}
#endif

/* Puts a global request to the generic callback, which sees the name and
* the type-specific part to parse itself. Returns 1 when the callback
* settled the request, with any wanted reply sent and *ret carrying the
* result, or 0 to leave it to the built-in handling. */
static int DoGlobalRequestAny(WOLFSSH* ssh, const char* name, int globReqId,
byte* buf, word32 len, word32 begin, byte wantReply, int* ret)
{
int decision, success;

if (ssh->ctx->globalReqAnyCb == NULL) {
return 0;
}

decision = ssh->ctx->globalReqAnyCb(ssh, name, buf + begin, len - begin,
wantReply, ssh->globalReqCtx);
if (decision != WOLFSSH_REQ_ACCEPT && decision != WOLFSSH_REQ_REJECT) {
return 0;
}
success = (decision == WOLFSSH_REQ_ACCEPT);

#ifdef WOLFSSH_FWD
/* RFC 4254 7.1: a port-0 request is answered with the port bound,
* which only the forward callback can report. */
if (success && globReqId == ID_GLOBREQ_TCPIP_FWD) {
const byte* bindAddr;
word32 bindAddrSz, bindPort = 0, peek = begin;

if (GetStringRef(&bindAddrSz, &bindAddr, buf, len, &peek)
!= WS_SUCCESS
|| GetUint32(&bindPort, buf, len, &peek) != WS_SUCCESS
|| bindPort == 0) {
WLOG(WS_LOG_WARN, "DGR: a port-0 forward needs the forward "
"callback to bind it; rejecting");
success = 0;
}
}
#else
(void)globReqId;
#endif

WLOG(WS_LOG_DEBUG, "DGR: global request callback %s",
success ? "granted" : "refused");
if (wantReply) {
*ret = SendRequestSuccess(ssh, success);
}

return 1;
}


static int DoGlobalRequest(WOLFSSH* ssh,
byte* buf, word32 len, word32* idx)
{
Expand Down Expand Up @@ -12224,31 +12282,37 @@ static int DoGlobalRequest(WOLFSSH* ssh,
}
else
#endif
switch (globReqId) {
if (!DoGlobalRequestAny(ssh, name, globReqId, buf, len, begin,
wantReply, &ret)) {
switch (globReqId) {
Comment on lines +12285 to +12287
#ifdef WOLFSSH_FWD
case ID_GLOBREQ_TCPIP_FWD:
ret = DoGlobalRequestFwd(ssh, buf, len, &begin, wantReply, 0);
wantReply = 0;
break;
case ID_GLOBREQ_TCPIP_FWD_CANCEL:
ret = DoGlobalRequestFwd(ssh, buf, len, &begin, wantReply, 1);
wantReply = 0;
break;
case ID_GLOBREQ_TCPIP_FWD:
ret = DoGlobalRequestFwd(ssh, buf, len, &begin,
wantReply, 0);
wantReply = 0;
break;
case ID_GLOBREQ_TCPIP_FWD_CANCEL:
ret = DoGlobalRequestFwd(ssh, buf, len, &begin,
wantReply, 1);
wantReply = 0;
break;
#endif
default:
if (ssh->ctx->globalReqCb != NULL) {
ret = ssh->ctx->globalReqCb(ssh, name, nameSz, wantReply,
(void *)ssh->globalReqCtx);
default:
if (ssh->ctx->globalReqCb != NULL) {
ret = ssh->ctx->globalReqCb(ssh, name, nameSz,
wantReply, (void *)ssh->globalReqCtx);

if (wantReply) {
ret = SendRequestSuccess(ssh, (ret == WS_SUCCESS));
if (wantReply) {
ret = SendRequestSuccess(ssh,
(ret == WS_SUCCESS));
}
}
}
else if (wantReply)
ret = SendRequestSuccess(ssh, 0);
/* response SSH_MSG_REQUEST_FAILURE to Keep-Alive.
* IETF:draft-ssh-global-requests */
break;
else if (wantReply)
ret = SendRequestSuccess(ssh, 0);
/* response SSH_MSG_REQUEST_FAILURE to Keep-Alive.
* IETF:draft-ssh-global-requests */
break;
}
}
}

Expand Down Expand Up @@ -13090,14 +13154,74 @@ static void SetTerminalSize(WOLFSSH* ssh, word32 widthChar, word32 heightRows,
#endif /* WOLFSSH_TERM */


/* Wipe the old command ahead of the GetStringAlloc() that frees it, so a
* repeat request leaves no credentials behind in the freed block. */
static void ScrubChannelCommand(WOLFSSH_CHANNEL* channel)
/* Answers a shell, exec, or subsystem request. Sets the session type and
* command for the callback to read, and keeps them only if it accepts.
* A request the generic callback already granted asks no callback. */
static int DoChannelRequestSession(WOLFSSH* ssh, word32 channelId,
WOLFSSH_CHANNEL* channel, byte sessionType, WS_CallbackChannelReq cb,
int granted, byte* buf, word32 len, word32* idx, int* rej)
{
if (channel->command != NULL) {
WS_FORCEZERO(channel->command, channel->commandSz);
channel->commandSz = 0;
void* heap = ssh->ctx->heap;
byte prevType = channel->sessionType;
byte hasCommand = (sessionType != WOLFSSH_SESSION_SHELL);
char* prevCommand = NULL;
word32 prevCommandSz = 0;
char* command = NULL;
word32 commandSz = 0;
int ret = WS_SUCCESS;

/* A shell request carries no command, so it leaves the old one alone.
* The others read into a local, so the old survives a refusal. */
if (hasCommand) {
prevCommand = channel->command;
prevCommandSz = channel->commandSz;

ret = GetStringAlloc(heap, &command, &commandSz, buf, len, idx);
if (ret == WS_SUCCESS)
WLOG(WS_LOG_DEBUG, " command = %s", command);
else
WLOG(WS_LOG_DEBUG, " command = %s", "<bad value>");
}

if (ret == WS_SUCCESS) {
if (hasCommand) {
channel->command = command;
channel->commandSz = commandSz;
}
channel->sessionType = sessionType;

if (granted)
*rej = 0;
else if (cb != NULL)
*rej = cb(channel, ssh->channelReqCtx);
else
*rej = ssh->appChannels;

/* A callback may free its own channel, so look it up again. */
channel = ChannelFind(ssh, channelId, WS_CHANNEL_ID_SELF);
if (channel == NULL) {
/* The new command went with it. */
FreeChannelCommand(heap, prevCommand, prevCommandSz);
return ret;
}
}

if (ret == WS_SUCCESS && !*rej) {
FreeChannelCommand(heap, prevCommand, prevCommandSz);
channel->sessionGranted = 1;
ssh->clientState = CLIENT_DONE;
}
else {
/* A refusal changes nothing, so an earlier grant still stands. */
if (hasCommand) {
FreeChannelCommand(heap, command, commandSz);
channel->command = prevCommand;
channel->commandSz = prevCommandSz;
}
channel->sessionType = prevType;
}

return ret;
}


Expand All @@ -13110,7 +13234,7 @@ static int DoChannelRequest(WOLFSSH* ssh,
word32 typeSz;
char type[32];
byte wantReply;
int ret, rej = 0, sessionReq = 0;
int ret, rej = 0, granted = 0;

WLOG(WS_LOG_DEBUG, "Entering DoChannelRequest()");

Expand Down Expand Up @@ -13138,6 +13262,23 @@ static int DoChannelRequest(WOLFSSH* ssh,
WLOG(WS_LOG_DEBUG, " type = %s", type);
WLOG(WS_LOG_DEBUG, " wantReply = %u", wantReply);

/* The generic callback sees every request first, with the
* type-specific part to parse itself. A refusal skips the handling
* below; a grant runs it with the decision already made. */
if (ssh->ctx->channelReqAnyCb != NULL) {
int decision = ssh->ctx->channelReqAnyCb(channel, type,
buf + begin, len - begin, ssh->channelReqCtx);
if (decision == WOLFSSH_REQ_REJECT) {
WLOG(WS_LOG_DEBUG, " channel request callback refused.");
rej = 1;
}
else if (decision == WOLFSSH_REQ_ACCEPT) {
granted = 1;
}
}
}

if (ret == WS_SUCCESS && !rej) {
if (ChannelRequestIs(type, typeSz, "env")) {
char name[WOLFSSH_MAX_NAMESZ];
word32 nameSz;
Expand All @@ -13160,59 +13301,19 @@ static int DoChannelRequest(WOLFSSH* ssh,
}
}
else if (ChannelRequestIs(type, typeSz, "shell")) {
channel->sessionType = WOLFSSH_SESSION_SHELL;
if (ssh->ctx->channelReqShellCb) {
rej = ssh->ctx->channelReqShellCb(channel, ssh->channelReqCtx);
}
else {
rej = ssh->appChannels;
}
sessionReq = 1;
ssh->clientState = CLIENT_DONE;
ret = DoChannelRequestSession(ssh, channelId, channel,
WOLFSSH_SESSION_SHELL, ssh->ctx->channelReqShellCb,
granted, buf, len, &begin, &rej);
}
else if (ChannelRequestIs(type, typeSz, "exec")) {
ScrubChannelCommand(channel);
ret = GetStringAlloc(ssh->ctx->heap,
&channel->command, &channel->commandSz,
buf, len, &begin);
if (ret == WS_SUCCESS)
WLOG(WS_LOG_DEBUG, " command = %s", channel->command);
else
WLOG(WS_LOG_DEBUG, " command = %s", "<bad value>");
if (ret == WS_SUCCESS) {
channel->sessionType = WOLFSSH_SESSION_EXEC;
if (ssh->ctx->channelReqExecCb) {
rej = ssh->ctx->channelReqExecCb(channel,
ssh->channelReqCtx);
}
else {
rej = ssh->appChannels;
}
}
sessionReq = 1;
ssh->clientState = CLIENT_DONE;
ret = DoChannelRequestSession(ssh, channelId, channel,
WOLFSSH_SESSION_EXEC, ssh->ctx->channelReqExecCb,
granted, buf, len, &begin, &rej);
}
else if (ChannelRequestIs(type, typeSz, "subsystem")) {
ScrubChannelCommand(channel);
ret = GetStringAlloc(ssh->ctx->heap,
&channel->command, &channel->commandSz,
buf, len, &begin);
if (ret == WS_SUCCESS)
WLOG(WS_LOG_DEBUG, " subsystem = %s", channel->command);
else
WLOG(WS_LOG_DEBUG, " subsystem = %s", "<bad value>");
if (ret == WS_SUCCESS) {
channel->sessionType = WOLFSSH_SESSION_SUBSYSTEM;
if (ssh->ctx->channelReqSubsysCb) {
rej = ssh->ctx->channelReqSubsysCb(channel,
ssh->channelReqCtx);
}
else {
rej = ssh->appChannels;
}
}
sessionReq = 1;
ssh->clientState = CLIENT_DONE;
ret = DoChannelRequestSession(ssh, channelId, channel,
WOLFSSH_SESSION_SUBSYSTEM, ssh->ctx->channelReqSubsysCb,
granted, buf, len, &begin, &rej);
}
#ifdef WOLFSSH_TERM
else if (ChannelRequestIs(type, typeSz, "pty-req")) {
Expand Down Expand Up @@ -13337,6 +13438,9 @@ static int DoChannelRequest(WOLFSSH* ssh,
WLOG(WS_LOG_AGENT, "Agent callback not set, not using.");
}
#endif /* WOLFSSH_AGENT */
else if (granted) {
WLOG(WS_LOG_DEBUG, " unknown channel request type, granted.");
}
else {
WLOG(WS_LOG_DEBUG, " unknown channel request type, rejecting.");
rej = 1;
Expand All @@ -13347,20 +13451,6 @@ static int DoChannelRequest(WOLFSSH* ssh,
*idx = len;
}

/* Record the answer, not the ask: sessionType and command are set before
* the reject decision and stay set on a refusal, so they cannot say
* whether the session was granted. Set even without a wantReply, which
* changes only whether the peer is told.
*
* Look the channel up again rather than reusing the pointer from
* before the callback. A callback may close its own channel, and
* wolfSSH_ChannelFree() frees it, so the old pointer can be dead. */
if (sessionReq) {
channel = ChannelFind(ssh, channelId, WS_CHANNEL_ID_SELF);
if (channel != NULL)
channel->sessionGranted = (ret == WS_SUCCESS && !rej);
}

if (wantReply) {
int replyRet;

Expand Down
Loading
Loading