Skip to content

Commit 5ae79b6

Browse files
wolfsftp: buffer the SFTP DATA length across partial reads
- STATE_SEND_READ_FTP_DATA trims state->buffer to UINT32_SZ with wolfSSH_SFTP_buffer_set_size(), reads the data string length into it with wolfSSH_SFTP_buffer_read(), and decodes it with wolfSSH_SFTP_buffer_rewind() and wolfSSH_SFTP_buffer_ato32(). - The outSz bound is applied to the decoded length before wolfSSH_SFTP_buffer_create() allocates for it, and that call's return is checked. - ssh->error is set to WS_BUFFER_E when the size or decode helper fails, to WS_RECV_OVERFLOW_E when the decoded length exceeds outSz, and to WS_MEMORY_E when the allocation fails. - The szFlat stack array is removed from wolfSSH_SFTP_SendReadPacket(). - tests/unit.c gains test_SftpSendReadPacketSplit() and test_SftpSendReadPacketOverflow(), both registered in wolfSSH_UnitTest(), with the SftpBuildData() and SftpClientDriveReadSplit() helpers. The first drives a DATA reply split at each of the four points across the length prefix; the second drives one whose string length exceeds the caller's buffer and checks for WS_RECV_OVERFLOW_E with no retained send read state. Issue: F-8828
1 parent 5336ee5 commit 5ae79b6

2 files changed

Lines changed: 192 additions & 6 deletions

File tree

src/wolfsftp.c

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8422,7 +8422,6 @@ int wolfSSH_SFTP_SendReadPacket(WOLFSSH* ssh, byte* handle, word32 handleSz,
84228422
const word32* ofst, byte* out, word32 outSz)
84238423
{
84248424
WS_SFTP_SEND_READ_STATE* state = NULL;
8425-
byte szFlat[UINT32_SZ];
84268425
int ret = WS_SUCCESS;
84278426
word32 sz;
84288427

@@ -8557,24 +8556,47 @@ int wolfSSH_SFTP_SendReadPacket(WOLFSSH* ssh, byte* handle, word32 handleSz,
85578556

85588557
case STATE_SEND_READ_FTP_DATA:
85598558
WLOG(WS_LOG_SFTP, "SFTP SEND_READ STATE: FTP_DATA");
8560-
/* get size of string and place it into out buffer */
8561-
ret = wolfSSH_stream_read(ssh, szFlat, UINT32_SZ);
8559+
/* Trim the buffer holding the header's payload down to the
8560+
* string length, which can arrive split over several reads. */
8561+
ret = wolfSSH_SFTP_buffer_set_size(&state->buffer, UINT32_SZ);
8562+
if (ret != WS_SUCCESS) {
8563+
ssh->error = WS_BUFFER_E;
8564+
state->state = STATE_SEND_READ_CLEANUP;
8565+
continue;
8566+
}
8567+
8568+
ret = wolfSSH_SFTP_buffer_read(ssh, &state->buffer, UINT32_SZ);
85628569
if (ret < 0) {
85638570
if (NoticeError(ssh)) {
85648571
return WS_FATAL_ERROR;
85658572
}
85668573
state->state = STATE_SEND_READ_CLEANUP;
85678574
continue;
85688575
}
8569-
ato32(szFlat, &sz);
8570-
wolfSSH_SFTP_buffer_create(ssh, &state->buffer, sz);
8571-
if (wolfSSH_SFTP_buffer_size(&state->buffer) > outSz) {
8576+
8577+
/* get size of the data string */
8578+
wolfSSH_SFTP_buffer_rewind(&state->buffer);
8579+
ret = wolfSSH_SFTP_buffer_ato32(&state->buffer, &sz);
8580+
if (ret != WS_SUCCESS) {
8581+
ssh->error = WS_BUFFER_E;
8582+
state->state = STATE_SEND_READ_CLEANUP;
8583+
continue;
8584+
}
8585+
if (sz > outSz) {
85728586
WLOG(WS_LOG_SFTP, "Server sent more data then expected");
8587+
ssh->error = WS_RECV_OVERFLOW_E;
85738588
ret = WS_FATAL_ERROR;
85748589
state->state = STATE_SEND_READ_CLEANUP;
85758590
continue;
85768591
}
85778592

8593+
ret = wolfSSH_SFTP_buffer_create(ssh, &state->buffer, sz);
8594+
if (ret != WS_SUCCESS) {
8595+
ssh->error = WS_MEMORY_E;
8596+
state->state = STATE_SEND_READ_CLEANUP;
8597+
continue;
8598+
}
8599+
85788600
state->state = STATE_SEND_READ_REMAINDER;
85798601
FALL_THROUGH;
85808602

tests/unit.c

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16165,6 +16165,160 @@ static int test_SftpClientPutWriteStatusFail(void)
1616516165
return 0;
1616616166
}
1616716167
#endif /* WOLFSSH_TEST_SFTP_PUT */
16168+
16169+
/* Builds an SFTP DATA reply in "out": the 9 byte header carrying the request
16170+
* id where a VERSION message carries the version, then the data string as the
16171+
* trailing bytes. Returns the size written, or 0 if it does not fit. */
16172+
static word32 SftpBuildData(byte* out, word32 outSz, word32 reqId,
16173+
word32 dataSz)
16174+
{
16175+
word32 msgSz;
16176+
16177+
msgSz = SftpBuildVersion(out, outSz,
16178+
MSG_ID_SZ + UINT32_SZ + UINT32_SZ + dataSz, WOLFSSH_FTP_DATA,
16179+
reqId, UINT32_SZ + dataSz);
16180+
if (msgSz > 0) {
16181+
/* the trailing bytes open with the data string length */
16182+
PutU32BE(out + WOLFSSH_SFTP_HEADER, dataSz);
16183+
}
16184+
16185+
return msgSz;
16186+
}
16187+
16188+
16189+
/* Drives wolfSSH_SFTP_SendReadPacket() over a DATA reply delivered in two
16190+
* pieces, split after "split" bytes, with RecvAlwaysWantRead standing in for a
16191+
* non-blocking socket. Returns 0, or a negative sentinel on a setup failure. */
16192+
static int SftpClientDriveReadSplit(word32 dataSz, word32 split,
16193+
int* firstRet, int* firstErr, int* stateKept, int* secondRet,
16194+
byte* out, word32 outSz)
16195+
{
16196+
WOLFSSH_CTX* ctx = NULL;
16197+
WOLFSSH* ssh = NULL;
16198+
byte msg[WOLFSSH_SFTP_HEADER + UINT32_SZ + 32];
16199+
byte handle[4];
16200+
word32 ofst[2];
16201+
word32 msgSz;
16202+
int result;
16203+
16204+
*firstRet = WS_SUCCESS;
16205+
*firstErr = WS_SUCCESS;
16206+
*stateKept = 0;
16207+
*secondRet = WS_SUCCESS;
16208+
16209+
WMEMSET(handle, 'h', sizeof(handle));
16210+
WMEMSET(out, 0, outSz);
16211+
ofst[0] = 0;
16212+
ofst[1] = 0;
16213+
16214+
result = SftpClientNewSession(&ctx, &ssh);
16215+
if (result == 0) {
16216+
msgSz = SftpBuildData(msg, (word32)sizeof(msg), ssh->reqId, dataSz);
16217+
if (msgSz == 0 || split >= msgSz) {
16218+
result = -1019;
16219+
}
16220+
}
16221+
if (result == 0) {
16222+
/* ChannelNew leaves the peer window at zero, which would fail the
16223+
* READ request before any reply is read */
16224+
ssh->channelList->peerWindowSz = 1024;
16225+
ssh->channelList->peerMaxPacketSz = 1024;
16226+
16227+
if (wolfSSH_TestChannelPutData(ssh->channelList, msg, split)
16228+
!= WS_SUCCESS) {
16229+
result = -1020;
16230+
}
16231+
}
16232+
if (result == 0) {
16233+
*firstRet = wolfSSH_SFTP_SendReadPacket(ssh, handle,
16234+
(word32)sizeof(handle), ofst, out, outSz);
16235+
*firstErr = wolfSSH_get_error(ssh);
16236+
*stateKept = (ssh->sendReadState != NULL);
16237+
16238+
if (wolfSSH_TestChannelPutData(ssh->channelList, msg + split,
16239+
msgSz - split) != WS_SUCCESS) {
16240+
result = -1021;
16241+
}
16242+
}
16243+
if (result == 0) {
16244+
*secondRet = wolfSSH_SFTP_SendReadPacket(ssh, handle,
16245+
(word32)sizeof(handle), ofst, out, outSz);
16246+
}
16247+
16248+
wolfSSH_free(ssh);
16249+
wolfSSH_CTX_free(ctx);
16250+
return result;
16251+
}
16252+
16253+
16254+
/* Regression for the SFTP DATA reply losing bytes when its four byte string
16255+
* length arrives split: the read must report WS_WANT_READ, keep the send read
16256+
* state, then complete with the payload intact on the retry. */
16257+
static int test_SftpSendReadPacketSplit(void)
16258+
{
16259+
static const word32 splits[4] = { WOLFSSH_SFTP_HEADER,
16260+
WOLFSSH_SFTP_HEADER + 1,
16261+
WOLFSSH_SFTP_HEADER + 2,
16262+
WOLFSSH_SFTP_HEADER + 3 };
16263+
byte out[16];
16264+
word32 dataSz = 8;
16265+
word32 i;
16266+
word32 j;
16267+
int rc;
16268+
int firstRet;
16269+
int firstErr;
16270+
int stateKept;
16271+
int secondRet;
16272+
16273+
for (i = 0; i < (word32)(sizeof(splits) / sizeof(splits[0])); i++) {
16274+
rc = SftpClientDriveReadSplit(dataSz, splits[i], &firstRet, &firstErr,
16275+
&stateKept, &secondRet, out, (word32)sizeof(out));
16276+
if (rc != 0)
16277+
return rc;
16278+
if (firstRet != WS_FATAL_ERROR)
16279+
return -981;
16280+
if (firstErr != WS_WANT_READ)
16281+
return -982;
16282+
if (!stateKept)
16283+
return -983;
16284+
if (secondRet != (int)dataSz)
16285+
return -984;
16286+
for (j = 0; j < dataSz; j++) {
16287+
if (out[j] != (byte)(UINT32_SZ + j))
16288+
return -985;
16289+
}
16290+
}
16291+
16292+
return 0;
16293+
}
16294+
16295+
16296+
/* A DATA reply whose string length exceeds the caller's buffer is rejected
16297+
* before the payload is allocated for, reporting WS_RECV_OVERFLOW_E. */
16298+
static int test_SftpSendReadPacketOverflow(void)
16299+
{
16300+
byte out[4];
16301+
word32 dataSz = 8;
16302+
int rc;
16303+
int firstRet;
16304+
int firstErr;
16305+
int stateKept;
16306+
int secondRet;
16307+
16308+
rc = SftpClientDriveReadSplit(dataSz, WOLFSSH_SFTP_HEADER + UINT32_SZ,
16309+
&firstRet, &firstErr, &stateKept, &secondRet, out,
16310+
(word32)sizeof(out));
16311+
if (rc != 0)
16312+
return rc;
16313+
if (firstRet != WS_FATAL_ERROR)
16314+
return -986;
16315+
if (firstErr != WS_RECV_OVERFLOW_E)
16316+
return -987;
16317+
if (stateKept)
16318+
return -988;
16319+
16320+
return 0;
16321+
}
1616816322
#endif /* NO_WOLFSSH_CLIENT */
1616916323
#endif /* WOLFSSH_SFTP */
1617016324

@@ -17318,6 +17472,16 @@ int wolfSSH_UnitTest(int argc, char** argv)
1731817472
(unitResult == 0 ? "SUCCESS" : "FAILED"));
1731917473
testResult = testResult || unitResult;
1732017474
#endif
17475+
17476+
unitResult = test_SftpSendReadPacketSplit();
17477+
printf("SftpSendReadPacketSplit: %s\n",
17478+
(unitResult == 0 ? "SUCCESS" : "FAILED"));
17479+
testResult = testResult || unitResult;
17480+
17481+
unitResult = test_SftpSendReadPacketOverflow();
17482+
printf("SftpSendReadPacketOverflow: %s\n",
17483+
(unitResult == 0 ? "SUCCESS" : "FAILED"));
17484+
testResult = testResult || unitResult;
1732117485
#endif
1732217486
#endif
1732317487

0 commit comments

Comments
 (0)