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
24 changes: 9 additions & 15 deletions src/agent.c
Original file line number Diff line number Diff line change
Expand Up @@ -1392,24 +1392,18 @@ static int DoMessage(WOLFSSH_AGENT_CTX* agent,
if (ret == WS_SUCCESS) {
WLOG(WS_LOG_AGENT, "len = %u, idx = %u", len, *idx);
begin = *idx;
if (begin > len) {
ret = WS_OVERFLOW_E;
}
}

if (ret == WS_SUCCESS) {
if (LENGTH_SZ + MSG_ID_SZ + begin > len) {
/* GetSize bounds the prefix and the payload; a nonzero payloadSz
* then covers the msg id read below. Reject 0: payloadSz - 1 is
* used as a length. */
if (GetSize(&payloadSz, buf, len, &begin) != WS_SUCCESS) {
ret = WS_OVERFLOW_E;
}
}

if (ret == WS_SUCCESS) {
ato32(buf + begin, &payloadSz);
WLOG(WS_LOG_AGENT, "payloadSz = %u", payloadSz);
begin += LENGTH_SZ;
/* reject 0: payloadSz - 1 is used as a length below */
if (payloadSz == 0 || payloadSz > len - begin) {
ret = WS_OVERFLOW_E;
else {
WLOG(WS_LOG_AGENT, "payloadSz = %u", payloadSz);
if (payloadSz == 0) {
ret = WS_OVERFLOW_E;
}
}
}

Expand Down
114 changes: 63 additions & 51 deletions src/internal.c
Original file line number Diff line number Diff line change
Expand Up @@ -3606,6 +3606,12 @@ WOLFSSH_CHANNEL* ChannelNew(WOLFSSH* ssh, byte channelType,
WMEMSET(newChannel, 0, sizeof(WOLFSSH_CHANNEL));
newChannel->ssh = ssh;
newChannel->channelType = channelType;
/* Skip channel ids already in use to avoid collisions when
* nextChannel (word32) wraps around. */
while (ChannelFind(ssh, ssh->nextChannel,
WS_CHANNEL_ID_SELF) != NULL) {
ssh->nextChannel++;
}
newChannel->channel = ssh->nextChannel++;
WLOG(WS_LOG_DEBUG, "New channel id = %u", newChannel->channel);
newChannel->windowSz = initialWindowSz;
Expand Down Expand Up @@ -4552,11 +4558,7 @@ static int GetNameList(byte* idList, word32* idListSz,
*/

if (ret == WS_SUCCESS) {
if (*idx >= len || *idx + 4 >= len)
ret = WS_BUFFER_E;
}

if (ret == WS_SUCCESS) {
/* GetStringRef bounds the length prefix and the list. */
ret = GetStringRef(&nameListSz, &nameList, buf, len, idx);
}

Expand Down Expand Up @@ -6934,7 +6936,6 @@ static int DoKexDhReply(WOLFSSH* ssh, byte* buf, word32 len, word32* idx)
word32 pubKeySz;
word32 fSz;
word32 sigSz;
word32 scratch;
word32 begin;
int ret = WS_SUCCESS;
enum wc_HashType hashId;
Expand Down Expand Up @@ -7176,47 +7177,36 @@ static int DoKexDhReply(WOLFSSH* ssh, byte* buf, word32 len, word32* idx)

/* Verify h with the server's public key. */
if (ret == WS_SUCCESS) {
#ifndef WOLFSSH_NO_RSA
int tmpIdx = begin - sigSz;
#endif
const char* expectedSigName =
IdToName(SigTypeForId(ssh->handshake->pubKeyId));
word32 expectedSigNameSz = (word32)WSTRLEN(expectedSigName);
const byte* sigName = NULL;
word32 sigNameSz = 0;
word32 sigBlobSz = 0;

begin = 0;
ret = GetUint32(&scratch, sig, sigSz, &begin);
if (ret == WS_SUCCESS) {
/* Check that scratch isn't larger than the remainder of the
* sig buffer and leaves enough room for another length. */
if (scratch > sigSz - begin - LENGTH_SZ) {
WLOG(WS_LOG_DEBUG, "sig name size is too large");
ret = WS_PARSE_E;
}
}
ret = GetStringRef(&sigNameSz, &sigName, sig, sigSz, &begin);
if (ret == WS_SUCCESS) {
if (scratch != expectedSigNameSz ||
WMEMCMP(sig + begin, expectedSigName, scratch) != 0) {
/* expectedSigName is never empty, so a null sigName fails
* on size first. */
if (sigNameSz != expectedSigNameSz ||
WMEMCMP(sigName, expectedSigName, sigNameSz) != 0) {
WLOG(WS_LOG_DEBUG,
"signature name %.*s did not match negotiated %s",
(int)scratch, (const char*)(sig + begin),
(int)sigNameSz,
(sigName != NULL) ? (const char*)sigName : "",
expectedSigName);
ret = WS_PARSE_E;
}
}
if (ret == WS_SUCCESS) {
begin += scratch;
ret = GetUint32(&scratch, sig, sigSz, &begin);
}
if (ret == WS_SUCCESS) {
if (scratch > sigSz - begin) {
WLOG(WS_LOG_DEBUG, "sig name size is too large");
ret = WS_PARSE_E;
}
/* GetSize leaves begin at the blob, and sig non-null when
* the blob is empty. */
ret = GetSize(&sigBlobSz, sig, sigSz, &begin);
}
if (ret == WS_SUCCESS) {
sig = sig + begin;
/* In the fuzz, sigSz ends up 1 and it has issues. */
sigSz = scratch;
sigSz = sigBlobSz;

if (sigKeyBlock_ptr->useRsa) {
#ifndef WOLFSSH_NO_RSA
Expand All @@ -7225,12 +7215,6 @@ static int DoKexDhReply(WOLFSSH* ssh, byte* buf, word32 len, word32* idx)
ret = WS_RSA_E;
}

if (sigSz + begin + tmpIdx > len) {
WLOG(WS_LOG_DEBUG,
"Signature size found would result in error 2");
ret = WS_BUFFER_E;
}

if (ret == WS_SUCCESS) {
ret = wc_SignatureVerify(
HashForId(ssh->handshake->pubKeyId),
Expand Down Expand Up @@ -12771,6 +12755,14 @@ static int BuildNameList(char* buf, word32 bufSz,

idx = 0;

if (srcSz == 0) {
/* Terminate: callers measure buf with WSTRLEN. */
if (buf != NULL && bufSz > 0) {
buf[0] = '\0';
}
return 0;
}

do {
name = IdToName(*src);
nameSz = (int)WSTRLEN(name);
Expand Down Expand Up @@ -19431,15 +19423,22 @@ int SendChannelData(WOLFSSH* ssh, word32 channelId,
word32 bound = min(channel->peerWindowSz, channel->peerMaxPacketSz);
bound = min(bound, channel->maxPacketSz);

if (dataSz > bound) {
WLOG(WS_LOG_DEBUG,
"Trying to send %u, client will only accept %u, limiting",
dataSz, bound);
dataSz = bound;
if (bound == 0 && dataSz != 0) {
WLOG(WS_LOG_DEBUG, "peer max packet size is zero");
ssh->error = WS_WINDOW_FULL;
ret = WS_WINDOW_FULL;
}
else {
if (dataSz > bound) {
WLOG(WS_LOG_DEBUG,
"Trying to send %u, client will only accept %u, limiting",
dataSz, bound);
dataSz = bound;
}

ret = PreparePacket(ssh,
MSG_ID_SZ + UINT32_SZ + LENGTH_SZ + dataSz);
ret = PreparePacket(ssh,
MSG_ID_SZ + UINT32_SZ + LENGTH_SZ + dataSz);
}
}

if (ret == WS_SUCCESS) {
Expand Down Expand Up @@ -19544,15 +19543,22 @@ int SendChannelExtendedData(WOLFSSH* ssh, word32 channelId,
word32 bound = min(channel->peerWindowSz, channel->peerMaxPacketSz);
bound = min(bound, channel->maxPacketSz);

if (dataSz > bound) {
WLOG(WS_LOG_DEBUG,
"Trying to send %u, client will only accept %u, limiting",
dataSz, bound);
dataSz = bound;
if (bound == 0 && dataSz != 0) {
WLOG(WS_LOG_DEBUG, "peer max packet size is zero");
ssh->error = WS_WINDOW_FULL;
ret = WS_WINDOW_FULL;
}
else {
if (dataSz > bound) {
WLOG(WS_LOG_DEBUG,
"Trying to send %u, client will only accept %u, limiting",
dataSz, bound);
dataSz = bound;
}

ret = PreparePacket(ssh,
MSG_ID_SZ + UINT32_SZ + UINT32_SZ + LENGTH_SZ + dataSz);
ret = PreparePacket(ssh,
MSG_ID_SZ + UINT32_SZ + UINT32_SZ + LENGTH_SZ + dataSz);
}
}

if (ret == WS_SUCCESS) {
Expand Down Expand Up @@ -20625,6 +20631,12 @@ int wolfSSH_TestChannelPutData(WOLFSSH_CHANNEL* channel, byte* data,
return ChannelPutData(channel, data, dataSz);
}

int wolfSSH_TestBuildNameList(char* buf, word32 bufSz,
const byte* src, word32 srcSz)
{
return BuildNameList(buf, bufSz, src, srcSz);
}

int wolfSSH_TestDoChannelSuccess(WOLFSSH* ssh, byte* buf, word32 len,
word32* idx)
{
Expand Down
8 changes: 8 additions & 0 deletions src/wolfsftp.c
Original file line number Diff line number Diff line change
Expand Up @@ -6636,6 +6636,14 @@ static int wolfSSH_SFTP_DoStatus(WOLFSSH* ssh, word32 reqId,
return WS_FATAL_ERROR;
}

/* status is a small enumerated value (0..WOLFSSH_FTP_UNSUPPORTED). An
* out-of-range value from a malicious or broken server must not be
* returned as a negative int, where it would alias an internal WS_*
* error code and bypass the WOLFSSH_FTP_* classification in callers. */
if (status > (word32)WOLFSSH_FTP_UNSUPPORTED) {
status = WOLFSSH_FTP_FAILURE;
}

/* read error message */
if (GetStringRef(&sz, &str, buf, maxIdx, &localIdx) != WS_SUCCESS) {
return WS_FATAL_ERROR;
Expand Down
31 changes: 31 additions & 0 deletions tests/regress.c
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,7 @@ static word32 LoadFileBuffer(const char* path, byte* buf, word32 bufSz)
/* KEXDH_REPLY mutation modes for the duplex mutator. */
#define REGRESS_MUTATE_SIG_NAME 0
#define REGRESS_MUTATE_SIG_DATA 1
#define REGRESS_MUTATE_SIG_NAME_OVERRUN 2

typedef struct {
byte data[REGRESS_DUPLEX_QUEUE_SZ];
Expand Down Expand Up @@ -676,6 +677,12 @@ static int RewriteSingleKexDhReplyPacket(const byte* packet, word32 packetSz,
}
innerSig[flipIdx] ^= 0xFF;
}
else if (mode == REGRESS_MUTATE_SIG_NAME_OVERRUN) {
/* Nothing but a name length prefix, so the name it claims runs off
* the end of the blob. */
innerSigSz = AppendUint32(innerSig, sizeof(innerSig), innerSigSz,
sigNameSz);
}
else {
innerSigSz = AppendString(innerSig, sizeof(innerSig), innerSigSz,
replacement);
Expand Down Expand Up @@ -1149,6 +1156,29 @@ static void TestKexDhReplyRejectsEd25519CorruptSig(void)
}
#endif

/* A signature blob holding only a name length prefix. The bounded read
* rejects it before the name is compared, whatever the host key type. */
static void TestKexDhReplyRejectsSigNameOverrun(void)
{
KexReplyHarness harness;
KexReplyRunResult result;

InitKexReplyHarnessEx(&harness, REGRESS_DEFAULT_KEY_ALGO,
REGRESS_DEFAULT_KEY_PATH, 1,
REGRESS_MUTATE_SIG_NAME_OVERRUN, NULL, 0);
RunKexReplyHandshake(&harness, &result);

AssertIntEQ(harness.mutator.parseError, 0);
AssertIntEQ(harness.mutator.matchedPackets, 1);
AssertIntEQ(harness.mutator.mutatedPackets, 1);
AssertFalse(result.clientSuccess);
AssertFalse(harness.client->connectState >= CONNECT_KEYED);
AssertTrue(result.clientRet == WS_FATAL_ERROR);
AssertIntEQ(result.clientErr, WS_BUFFER_E);

FreeKexReplyHarness(&harness);
}

#endif /* KEXDH_REPLY_REGRESS_KEX_ALGO */

static word32 ParseChannelOpenFailRecipient(const byte* pkt, word32 sz)
Expand Down Expand Up @@ -5175,6 +5205,7 @@ int main(int argc, char** argv)
#ifndef WOLFSSH_NO_ED25519
TestKexDhReplyRejectsEd25519CorruptSig();
#endif
TestKexDhReplyRejectsSigNameOverrun();
#endif

#ifdef WOLFSSH_SFTP
Expand Down
Loading
Loading