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
43 changes: 42 additions & 1 deletion src/wolfsftp.c
Original file line number Diff line number Diff line change
Expand Up @@ -6936,7 +6936,28 @@ static WS_SFTPNAME* wolfSSH_SFTP_DoName(WOLFSSH* ssh)

case SFTP_NAME_GETHEADER_PACKET:
maxSz = SFTP_GetHeader(ssh, &reqId, &type, &state->buffer);
if (maxSz <= 0 || ssh->error == WS_WANT_READ) {
if (maxSz <= 0) {
/* Non-positive size: a retryable notice keeps NAME state for
* resume; otherwise (including an int wrap above INT_MAX)
* record a size error if unset and clear state. */
if (!NoticeError(ssh)) {
if (ssh->error == WS_SUCCESS) {
ssh->error = WS_BUFFER_E;
}
wolfSSH_SFTP_ClearState(ssh, STATE_ID_NAME);
}
return NULL;
}

/* Bound the NAME size: the buffer is allocated at this size and a
* WS_SFTPNAME node is made per entry, so an over-large packet
* would amplify into several times that much live heap. */
if (maxSz > WOLFSSH_MAX_SFTP_NAME) {
WLOG(WS_LOG_SFTP,
"SFTP NAME packet size %d exceeds limit %d",
maxSz, (int)WOLFSSH_MAX_SFTP_NAME);
ssh->error = WS_BUFFER_E;
wolfSSH_SFTP_ClearState(ssh, STATE_ID_NAME);
return NULL;
}

Expand Down Expand Up @@ -7098,6 +7119,26 @@ static WS_SFTPNAME* wolfSSH_SFTP_DoName(WOLFSSH* ssh)
}


#ifdef WOLFSSH_TEST_INTERNAL
/* Drive wolfSSH_SFTP_DoName for unit testing. Frees any returned name list
* and reports ssh->error so the caller can check the NAME size bound. */
int wolfSSH_TestSftpDoName(WOLFSSH* ssh)
{
WS_SFTPNAME* name;

if (ssh == NULL) {
return WS_BAD_ARGUMENT;
}

name = wolfSSH_SFTP_DoName(ssh);
if (name != NULL) {
wolfSSH_SFTPNAME_list_free(name);
}
return ssh->error;
}
#endif


/* get the file handle from SSH stream
*
* handle buffer to hold result
Expand Down
110 changes: 110 additions & 0 deletions tests/unit.c
Original file line number Diff line number Diff line change
Expand Up @@ -5076,6 +5076,110 @@ static int test_Errors(void)
return result;
}

#if defined(WOLFSSH_SFTP) && defined(WOLFSSH_TEST_INTERNAL)
/* Inject a crafted SFTP NAME header declaring an on-wire payload length of
* 'wireLen' into a channel, drive wolfSSH_SFTP_DoName, and report ssh->error
* via outErr. Only the 9-byte header is needed: the NAME size bound is
* checked before the message body is read. Returns 0 on setup success. */
static int sftpDoNameInjectErr(word32 wireLen, int* outErr)
{
WOLFSSH_CTX* ctx = NULL;
WOLFSSH* ssh = NULL;
WOLFSSH_CHANNEL* ch = NULL;
byte hdr[LENGTH_SZ + MSG_ID_SZ + UINT32_SZ];
int result = 0;

ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL);
if (ctx == NULL)
return -560;
ssh = wolfSSH_new(ctx);
if (ssh == NULL) {
wolfSSH_CTX_free(ctx);
return -561;
}

ch = ChannelNew(ssh, ID_CHANTYPE_SESSION, 128, 128);
if (ch == NULL) {
result = -562;
goto done;
}
if (ChannelAppend(ssh, ch) != WS_SUCCESS) {
ChannelDelete(ch, ssh->ctx->heap);
result = -563;
goto done;
}

/* SFTP header: [uint32 length][byte type][uint32 reqId]. */
hdr[0] = (byte)(wireLen >> 24);
hdr[1] = (byte)(wireLen >> 16);
hdr[2] = (byte)(wireLen >> 8);
hdr[3] = (byte)(wireLen);
hdr[LENGTH_SZ] = WOLFSSH_FTP_NAME;
hdr[LENGTH_SZ + MSG_ID_SZ + 0] = 0;
hdr[LENGTH_SZ + MSG_ID_SZ + 1] = 0;
hdr[LENGTH_SZ + MSG_ID_SZ + 2] = 0;
hdr[LENGTH_SZ + MSG_ID_SZ + 3] = 0;

/* Leave reqId non-matching so an in-bound header exits at the request-id
* check without setting WS_BUFFER_E. */
ssh->reqId = 0xFFFFFFFF;
ssh->error = WS_SUCCESS;

if (wolfSSH_TestChannelPutData(ch, hdr, (word32)sizeof(hdr))
!= WS_SUCCESS) {
result = -564;
goto done;
}

*outErr = wolfSSH_TestSftpDoName(ssh);

done:
wolfSSH_free(ssh);
wolfSSH_CTX_free(ctx);
return result;
}

/* Verify wolfSSH_SFTP_DoName rejects a NAME message larger than
* WOLFSSH_MAX_SFTP_NAME and accepts one at the limit. SFTP_GetHeader
* returns the wire length minus the type and request-id fields, so the
* reported maxSz is wireLen - (UINT32_SZ + MSG_ID_SZ). */
static int test_SftpDoName_sizeBound(void)
{
word32 overhead = UINT32_SZ + MSG_ID_SZ;
int err = 0;
int result;

/* maxSz = WOLFSSH_MAX_SFTP_NAME + 1 -> over the bound, rejected. */
err = WS_SUCCESS;
result = sftpDoNameInjectErr(WOLFSSH_MAX_SFTP_NAME + overhead + 1, &err);
if (result != 0)
return result;
if (err != WS_BUFFER_E)
return -570;

/* maxSz = WOLFSSH_MAX_SFTP_NAME -> at the bound, not rejected (exits at
* the request-id check instead). */
err = WS_SUCCESS;
result = sftpDoNameInjectErr(WOLFSSH_MAX_SFTP_NAME + overhead, &err);
if (result != 0)
return result;
if (err == WS_BUFFER_E)
return -571;

/* A wire length above INT_MAX makes SFTP_GetHeader's int result wrap
* non-positive; this must be reported as a size error, not a silent
* NULL with WS_SUCCESS. */
err = WS_SUCCESS;
result = sftpDoNameInjectErr(0x80000000U + overhead, &err);
if (result != 0)
return result;
if (err != WS_BUFFER_E)
return -572;

return 0;
}
#endif /* WOLFSSH_SFTP && WOLFSSH_TEST_INTERNAL */

int wolfSSH_UnitTest(int argc, char** argv)
{
int testResult = 0, unitResult = 0;
Expand Down Expand Up @@ -5150,6 +5254,12 @@ int wolfSSH_UnitTest(int argc, char** argv)
unitResult = test_SendChannelData_eofTxd();
printf("SendChannelData_eofTxd: %s\n", (unitResult == 0 ? "SUCCESS" : "FAILED"));
testResult = testResult || unitResult;

#ifdef WOLFSSH_SFTP
unitResult = test_SftpDoName_sizeBound();
printf("SftpDoName_sizeBound: %s\n", (unitResult == 0 ? "SUCCESS" : "FAILED"));
testResult = testResult || unitResult;
#endif
#if !defined(WOLFSSH_NO_RSA)
unitResult = test_RsaVerify_BadDigest();
printf("RsaVerify_BadDigest: %s\n",
Expand Down
12 changes: 12 additions & 0 deletions wolfssh/wolfsftp.h
Original file line number Diff line number Diff line change
Expand Up @@ -167,13 +167,24 @@ struct WS_SFTPNAME {
* expects the amount requested to be sent, and that's 32kiB.
* WOLFSSH_MAX_SFTP_RECV: Used as a bounds check on a SFTP message's size.
* Is not used to allocate any buffers directly.
* WOLFSSH_MAX_SFTP_NAME: Upper bound on the size of a single SFTP NAME
* response (READDIR, REALPATH, LS). The client allocates one buffer of
* this size for the message and then one WS_SFTPNAME node per entry, so
* an unbounded NAME packet lets a malicious or MITM server amplify a
* modest packet into several times that much live heap. The default of
* 1 MiB leaves room for very large single-packet directory listings (the
* server sends a whole directory in one NAME packet) while bounding peak
* heap to a few MiB.
*/
#ifndef WOLFSSH_MAX_SFTP_RW
#define WOLFSSH_MAX_SFTP_RW 32768
#endif
#ifndef WOLFSSH_MAX_SFTP_RECV
#define WOLFSSH_MAX_SFTP_RECV 32768
#endif
#ifndef WOLFSSH_MAX_SFTP_NAME
#define WOLFSSH_MAX_SFTP_NAME (1024 * 1024)
#endif

/*
* WOLFSSH_MAX_SFTP_PACKET: Upper bound on the body size of an inbound SFTP
Expand Down Expand Up @@ -301,6 +312,7 @@ WOLFSSH_LOCAL void wolfSSH_SFTP_ShowSizes(void);
WOLFSSH_API int wolfSSH_TestSftpBufferSend(WOLFSSH* ssh,
byte* data, word32 sz, word32 idx);
WOLFSSH_API int wolfSSH_TestSftpRecvSizeCheck(int sz);
WOLFSSH_API int wolfSSH_TestSftpDoName(WOLFSSH* ssh);
WOLFSSH_API int wolfSSH_TestSftpSendCap(WOLFSSH* ssh, word32 cap);
WOLFSSH_API int wolfSSH_TestSftpStallPending(WOLFSSH* ssh, word32 count);
#if !defined(NO_WOLFSSH_SERVER) && !defined(USE_WINDOWS_API) && \
Expand Down
Loading