Skip to content

Commit d1c2fc5

Browse files
Bound SFTP NAME response size on the client
1 parent 8ac0567 commit d1c2fc5

3 files changed

Lines changed: 168 additions & 1 deletion

File tree

src/wolfsftp.c

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6750,7 +6750,32 @@ static WS_SFTPNAME* wolfSSH_SFTP_DoName(WOLFSSH* ssh)
67506750

67516751
case SFTP_NAME_GETHEADER_PACKET:
67526752
maxSz = SFTP_GetHeader(ssh, &reqId, &type, &state->buffer);
6753-
if (maxSz <= 0 || ssh->error == WS_WANT_READ) {
6753+
if (maxSz <= 0) {
6754+
/* A non-positive size is either a retryable notice (want
6755+
* read/write, rekey, etc.), where state is kept so the read
6756+
* can resume, or a real failure. SFTP_GetHeader returns the
6757+
* wire length in an int, so a length above INT_MAX also lands
6758+
* here. On a non-notice case, record a size error if none was
6759+
* set and clear NAME state so the caller sees the failure. */
6760+
if (!NoticeError(ssh)) {
6761+
if (ssh->error == WS_SUCCESS) {
6762+
ssh->error = WS_BUFFER_E;
6763+
}
6764+
wolfSSH_SFTP_ClearState(ssh, STATE_ID_NAME);
6765+
}
6766+
return NULL;
6767+
}
6768+
6769+
/* Bound the NAME message size. The buffer is allocated at this
6770+
* size and one WS_SFTPNAME node is created per entry, so an
6771+
* over-large packet from a malicious or MITM server would amplify
6772+
* into several times that much live heap. */
6773+
if (maxSz > WOLFSSH_MAX_SFTP_NAME) {
6774+
WLOG(WS_LOG_SFTP,
6775+
"SFTP NAME packet size %d exceeds limit %d",
6776+
maxSz, (int)WOLFSSH_MAX_SFTP_NAME);
6777+
ssh->error = WS_BUFFER_E;
6778+
wolfSSH_SFTP_ClearState(ssh, STATE_ID_NAME);
67546779
return NULL;
67556780
}
67566781

@@ -6912,6 +6937,26 @@ static WS_SFTPNAME* wolfSSH_SFTP_DoName(WOLFSSH* ssh)
69126937
}
69136938

69146939

6940+
#ifdef WOLFSSH_TEST_INTERNAL
6941+
/* Drive wolfSSH_SFTP_DoName for unit testing. Frees any returned name list
6942+
* and reports ssh->error so the caller can check the NAME size bound. */
6943+
int wolfSSH_TestSftpDoName(WOLFSSH* ssh)
6944+
{
6945+
WS_SFTPNAME* name;
6946+
6947+
if (ssh == NULL) {
6948+
return WS_BAD_ARGUMENT;
6949+
}
6950+
6951+
name = wolfSSH_SFTP_DoName(ssh);
6952+
if (name != NULL) {
6953+
wolfSSH_SFTPNAME_list_free(name);
6954+
}
6955+
return ssh->error;
6956+
}
6957+
#endif
6958+
6959+
69156960
/* get the file handle from SSH stream
69166961
*
69176962
* handle buffer to hold result

tests/unit.c

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4983,6 +4983,110 @@ static int test_Errors(void)
49834983
return result;
49844984
}
49854985

4986+
#if defined(WOLFSSH_SFTP) && defined(WOLFSSH_TEST_INTERNAL)
4987+
/* Inject a crafted SFTP NAME header declaring an on-wire payload length of
4988+
* 'wireLen' into a channel, drive wolfSSH_SFTP_DoName, and report ssh->error
4989+
* via outErr. Only the 9-byte header is needed: the NAME size bound is
4990+
* checked before the message body is read. Returns 0 on setup success. */
4991+
static int sftpDoNameInjectErr(word32 wireLen, int* outErr)
4992+
{
4993+
WOLFSSH_CTX* ctx = NULL;
4994+
WOLFSSH* ssh = NULL;
4995+
WOLFSSH_CHANNEL* ch = NULL;
4996+
byte hdr[LENGTH_SZ + MSG_ID_SZ + UINT32_SZ];
4997+
int result = 0;
4998+
4999+
ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL);
5000+
if (ctx == NULL)
5001+
return -560;
5002+
ssh = wolfSSH_new(ctx);
5003+
if (ssh == NULL) {
5004+
wolfSSH_CTX_free(ctx);
5005+
return -561;
5006+
}
5007+
5008+
ch = ChannelNew(ssh, ID_CHANTYPE_SESSION, 128, 128);
5009+
if (ch == NULL) {
5010+
result = -562;
5011+
goto done;
5012+
}
5013+
if (ChannelAppend(ssh, ch) != WS_SUCCESS) {
5014+
ChannelDelete(ch, ssh->ctx->heap);
5015+
result = -563;
5016+
goto done;
5017+
}
5018+
5019+
/* SFTP header: [uint32 length][byte type][uint32 reqId]. */
5020+
hdr[0] = (byte)(wireLen >> 24);
5021+
hdr[1] = (byte)(wireLen >> 16);
5022+
hdr[2] = (byte)(wireLen >> 8);
5023+
hdr[3] = (byte)(wireLen);
5024+
hdr[LENGTH_SZ] = WOLFSSH_FTP_NAME;
5025+
hdr[LENGTH_SZ + MSG_ID_SZ + 0] = 0;
5026+
hdr[LENGTH_SZ + MSG_ID_SZ + 1] = 0;
5027+
hdr[LENGTH_SZ + MSG_ID_SZ + 2] = 0;
5028+
hdr[LENGTH_SZ + MSG_ID_SZ + 3] = 0;
5029+
5030+
/* Leave reqId non-matching so an in-bound header exits at the request-id
5031+
* check without setting WS_BUFFER_E. */
5032+
ssh->reqId = 0xFFFFFFFF;
5033+
ssh->error = WS_SUCCESS;
5034+
5035+
if (wolfSSH_TestChannelPutData(ch, hdr, (word32)sizeof(hdr))
5036+
!= WS_SUCCESS) {
5037+
result = -564;
5038+
goto done;
5039+
}
5040+
5041+
*outErr = wolfSSH_TestSftpDoName(ssh);
5042+
5043+
done:
5044+
wolfSSH_free(ssh);
5045+
wolfSSH_CTX_free(ctx);
5046+
return result;
5047+
}
5048+
5049+
/* Verify wolfSSH_SFTP_DoName rejects a NAME message larger than
5050+
* WOLFSSH_MAX_SFTP_NAME and accepts one at the limit. SFTP_GetHeader
5051+
* returns the wire length minus the type and request-id fields, so the
5052+
* reported maxSz is wireLen - (UINT32_SZ + MSG_ID_SZ). */
5053+
static int test_SftpDoName_sizeBound(void)
5054+
{
5055+
word32 overhead = UINT32_SZ + MSG_ID_SZ;
5056+
int err = 0;
5057+
int result;
5058+
5059+
/* maxSz = WOLFSSH_MAX_SFTP_NAME + 1 -> over the bound, rejected. */
5060+
err = WS_SUCCESS;
5061+
result = sftpDoNameInjectErr(WOLFSSH_MAX_SFTP_NAME + overhead + 1, &err);
5062+
if (result != 0)
5063+
return result;
5064+
if (err != WS_BUFFER_E)
5065+
return -570;
5066+
5067+
/* maxSz = WOLFSSH_MAX_SFTP_NAME -> at the bound, not rejected (exits at
5068+
* the request-id check instead). */
5069+
err = WS_SUCCESS;
5070+
result = sftpDoNameInjectErr(WOLFSSH_MAX_SFTP_NAME + overhead, &err);
5071+
if (result != 0)
5072+
return result;
5073+
if (err == WS_BUFFER_E)
5074+
return -571;
5075+
5076+
/* A wire length above INT_MAX makes SFTP_GetHeader's int result wrap
5077+
* non-positive; this must be reported as a size error, not a silent
5078+
* NULL with WS_SUCCESS. */
5079+
err = WS_SUCCESS;
5080+
result = sftpDoNameInjectErr(0x80000000U + overhead, &err);
5081+
if (result != 0)
5082+
return result;
5083+
if (err != WS_BUFFER_E)
5084+
return -572;
5085+
5086+
return 0;
5087+
}
5088+
#endif /* WOLFSSH_SFTP && WOLFSSH_TEST_INTERNAL */
5089+
49865090
int wolfSSH_UnitTest(int argc, char** argv)
49875091
{
49885092
int testResult = 0, unitResult = 0;
@@ -5057,6 +5161,12 @@ int wolfSSH_UnitTest(int argc, char** argv)
50575161
unitResult = test_SendChannelData_eofTxd();
50585162
printf("SendChannelData_eofTxd: %s\n", (unitResult == 0 ? "SUCCESS" : "FAILED"));
50595163
testResult = testResult || unitResult;
5164+
5165+
#ifdef WOLFSSH_SFTP
5166+
unitResult = test_SftpDoName_sizeBound();
5167+
printf("SftpDoName_sizeBound: %s\n", (unitResult == 0 ? "SUCCESS" : "FAILED"));
5168+
testResult = testResult || unitResult;
5169+
#endif
50605170
#if !defined(WOLFSSH_NO_RSA)
50615171
unitResult = test_RsaVerify_BadDigest();
50625172
printf("RsaVerify_BadDigest: %s\n",

wolfssh/wolfsftp.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,13 +167,24 @@ struct WS_SFTPNAME {
167167
* expects the amount requested to be sent, and that's 32kiB.
168168
* WOLFSSH_MAX_SFTP_RECV: Used as a bounds check on a SFTP message's size.
169169
* Is not used to allocate any buffers directly.
170+
* WOLFSSH_MAX_SFTP_NAME: Upper bound on the size of a single SFTP NAME
171+
* response (READDIR, REALPATH, LS). The client allocates one buffer of
172+
* this size for the message and then one WS_SFTPNAME node per entry, so
173+
* an unbounded NAME packet lets a malicious or MITM server amplify a
174+
* modest packet into several times that much live heap. The default of
175+
* 1 MiB leaves room for very large single-packet directory listings (the
176+
* server sends a whole directory in one NAME packet) while bounding peak
177+
* heap to a few MiB.
170178
*/
171179
#ifndef WOLFSSH_MAX_SFTP_RW
172180
#define WOLFSSH_MAX_SFTP_RW 32768
173181
#endif
174182
#ifndef WOLFSSH_MAX_SFTP_RECV
175183
#define WOLFSSH_MAX_SFTP_RECV 32768
176184
#endif
185+
#ifndef WOLFSSH_MAX_SFTP_NAME
186+
#define WOLFSSH_MAX_SFTP_NAME (1024 * 1024)
187+
#endif
177188
/*
178189
* WOLFSSH_MAX_SFTP_PACKET: Upper bound on the body size of an inbound SFTP
179190
* request the server accepts in its steady-state receive loop
@@ -302,6 +313,7 @@ WOLFSSH_LOCAL void wolfSSH_SFTP_ShowSizes(void);
302313
WOLFSSH_API int wolfSSH_TestSftpBufferSend(WOLFSSH* ssh,
303314
byte* data, word32 sz, word32 idx);
304315
WOLFSSH_API int wolfSSH_TestSftpRecvSizeCheck(int sz);
316+
WOLFSSH_API int wolfSSH_TestSftpDoName(WOLFSSH* ssh);
305317
#ifndef NO_WOLFSSH_SERVER
306318
WOLFSSH_API int wolfSSH_TestSftpValidateFileHandle(WOLFSSH* ssh,
307319
const byte* handle, word32 handleSz);

0 commit comments

Comments
 (0)