Skip to content

Commit 59cd208

Browse files
committed
scp: reuse GetScpFileName buffer instead of realloc
- Track the scpFileName allocation size in a new scpFileNameCap field, so GetScpFileName() and ScpCheckForRename() reuse the buffer whenever the name plus its terminator fits, instead of testing against the previous name length - Pass scpFileNameCap to the send callback, which writes into scpFileName and needs the capacity; scpFileNameSz is now the name length on every path, including the source path - Free and reallocate the transfer buffer in ScpSourceInit(), clearing the size fields alongside the pointers they describe - Wrap a long line in ScpProcessEntry() - Add test_ScpGetFileName covering the reuse-vs-realloc branch, the exact-fit boundary, a grow-by-one that catches an off-by-one in the reuse condition, and a source-path buffer holding no name yet - Add test_wolfSSH_SCP_RecursiveTwoFiles, a real "scp -r" transfer of two files, covering the scpFileHeaderSent reset on the recursive path added in the duplicate-header fix; it clears leftovers from an aborted run up front and bounds its recv so a regression fails instead of hanging
1 parent 8f6a8e7 commit 59cd208

5 files changed

Lines changed: 374 additions & 13 deletions

File tree

src/internal.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1605,6 +1605,7 @@ WOLFSSH* SshInit(WOLFSSH* ssh, WOLFSSH_CTX* ctx)
16051605
ssh->scpFileBufferSz = 0;
16061606
ssh->scpFileName = NULL;
16071607
ssh->scpFileNameSz = 0;
1608+
ssh->scpFileNameCap = 0;
16081609
ssh->scpTimestamp = 0;
16091610
ssh->scpATime = 0;
16101611
ssh->scpMTime = 0;
@@ -1706,6 +1707,7 @@ void SshResourceFree(WOLFSSH* ssh, void* heap)
17061707
WFREE(ssh->scpFileName, heap, DYNTYPE_STRING);
17071708
ssh->scpFileName = NULL;
17081709
ssh->scpFileNameSz = 0;
1710+
ssh->scpFileNameCap = 0;
17091711
}
17101712
if (ssh->scpRecvMsg) {
17111713
WFREE(ssh->scpRecvMsg, heap, DYNTYPE_STRING);

src/wolfscp.c

Lines changed: 48 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -366,24 +366,36 @@ static int ScpSourceInit(WOLFSSH* ssh)
366366
WFREE(ssh->scpFileName, ssh->ctx->heap, DYNTYPE_STRING);
367367
ssh->scpFileName = NULL;
368368
ssh->scpFileNameSz = 0;
369+
ssh->scpFileNameCap = 0;
369370
}
370371

371372
ssh->scpFileName = (char*)WMALLOC(DEFAULT_SCP_FILE_NAME_SZ, ssh->ctx->heap,
372373
DYNTYPE_STRING);
373374
if (ssh->scpFileName == NULL)
374375
return WS_MEMORY_E;
375376

376-
ssh->scpFileNameSz = DEFAULT_SCP_FILE_NAME_SZ;
377+
/* The source path uses scpFileName as a fixed-size scratch buffer that the
378+
* send callback fills in, so there is no name in it yet. */
379+
ssh->scpFileNameCap = DEFAULT_SCP_FILE_NAME_SZ;
380+
ssh->scpFileNameSz = 0;
377381
WMEMSET(ssh->scpFileName, 0, DEFAULT_SCP_FILE_NAME_SZ);
378382

379383
/* file buffer */
384+
if (ssh->scpFileBuffer != NULL) {
385+
WFREE(ssh->scpFileBuffer, ssh->ctx->heap, DYNTYPE_BUFFER);
386+
ssh->scpFileBuffer = NULL;
387+
ssh->scpFileBufferSz = 0;
388+
}
389+
380390
ssh->scpFileBuffer = (byte*)WMALLOC(DEFAULT_SCP_BUFFER_SZ, ssh->ctx->heap,
381391
DYNTYPE_BUFFER);
382392
if (ssh->scpFileBuffer == NULL) {
383393
WFREE(ssh->scpFileName, ssh->ctx->heap, DYNTYPE_STRING);
384394
ssh->scpFileName = NULL;
395+
ssh->scpFileNameCap = 0;
385396
return WS_MEMORY_E;
386397
}
398+
387399
ssh->scpFileBufferSz = DEFAULT_SCP_BUFFER_SZ;
388400
WMEMSET(ssh->scpFileBuffer, 0, DEFAULT_SCP_BUFFER_SZ);
389401

@@ -633,9 +645,12 @@ int DoScpSource(WOLFSSH* ssh)
633645
case SCP_TRANSFER:
634646
WLOG(WS_LOG_DEBUG, scpState, "SCP_TRANSFER");
635647

648+
/* the callback writes the name into scpFileName, so it needs
649+
* the buffer capacity, not the current name length */
636650
ssh->scpConfirm = ssh->ctx->scpSendCb(ssh,
637651
ssh->scpRequestType, ssh->scpBasePath,
638-
ssh->scpFileName, ssh->scpFileNameSz, &(ssh->scpMTime),
652+
ssh->scpFileName, ssh->scpFileNameCap,
653+
&(ssh->scpMTime),
639654
&(ssh->scpATime), &(ssh->scpFileMode),
640655
ssh->scpFileOffset, &(ssh->scpFileSz),
641656
ssh->scpFileBuffer + ssh->scpBufferedSz,
@@ -1258,16 +1273,24 @@ static int GetScpFileName(WOLFSSH* ssh, byte* buf, word32 bufSz,
12581273
}
12591274
}
12601275

1261-
if (ssh->scpFileName != NULL) {
1262-
WFREE(ssh->scpFileName, ssh->ctx->heap, DYNTYPE_STRING);
1263-
ssh->scpFileName = NULL;
1264-
ssh->scpFileNameSz = 0;
1265-
}
1276+
/* reuse the existing allocation when the name plus its terminator
1277+
* fits; scpFileNameCap is the allocation size, so this is correct no
1278+
* matter what the last name length was */
1279+
if (ssh->scpFileName == NULL || ssh->scpFileNameCap <= len) {
1280+
if (ssh->scpFileName != NULL) {
1281+
WFREE(ssh->scpFileName, ssh->ctx->heap, DYNTYPE_STRING);
1282+
ssh->scpFileName = NULL;
1283+
ssh->scpFileNameSz = 0;
1284+
ssh->scpFileNameCap = 0;
1285+
}
12661286

1267-
ssh->scpFileName = (char*)WMALLOC(len + 1, ssh->ctx->heap,
1268-
DYNTYPE_STRING);
1269-
if (ssh->scpFileName == NULL)
1270-
ret = WS_MEMORY_E;
1287+
ssh->scpFileName = (char*)WMALLOC(len + 1, ssh->ctx->heap,
1288+
DYNTYPE_STRING);
1289+
if (ssh->scpFileName == NULL)
1290+
ret = WS_MEMORY_E;
1291+
else
1292+
ssh->scpFileNameCap = len + 1;
1293+
}
12711294

12721295
if (ret == WS_SUCCESS) {
12731296
WMEMCPY(ssh->scpFileName, buf + idx, len);
@@ -1281,6 +1304,14 @@ static int GetScpFileName(WOLFSSH* ssh, byte* buf, word32 bufSz,
12811304
return ret;
12821305
}
12831306

1307+
#ifdef WOLFSSH_TEST_INTERNAL
1308+
int wolfSSH_TestScpGetFileName(WOLFSSH* ssh, byte* buf, word32 bufSz,
1309+
word32* inOutIdx)
1310+
{
1311+
return GetScpFileName(ssh, buf, bufSz, inOutIdx);
1312+
}
1313+
#endif /* WOLFSSH_TEST_INTERNAL */
1314+
12841315
/* Reads timestamp information (access, modification) from beginning
12851316
* of string, expects space to be after each time value:
12861317
*
@@ -1443,10 +1474,12 @@ static int ScpCheckForRename(WOLFSSH* ssh)
14431474
}
14441475

14451476
sz = sz - idx; /* size of file name */
1446-
if (ssh->scpFileNameSz < (word32)sz || ssh->scpFileName == NULL) {
1477+
if (ssh->scpFileName == NULL || ssh->scpFileNameCap <= (word32)sz) {
14471478
if (ssh->scpFileName != NULL) {
14481479
WFREE(ssh->scpFileName, ssh->ctx->heap, DYNTYPE_STRING);
1480+
ssh->scpFileName = NULL;
14491481
ssh->scpFileNameSz = 0;
1482+
ssh->scpFileNameCap = 0;
14501483
}
14511484
ssh->scpFileName = (char*)WMALLOC(sz + 1, ssh->ctx->heap,
14521485
DYNTYPE_STRING);
@@ -1456,6 +1489,7 @@ static int ScpCheckForRename(WOLFSSH* ssh)
14561489
ssh->scpBasePath = NULL;
14571490
return WS_MEMORY_E;
14581491
}
1492+
ssh->scpFileNameCap = sz + 1;
14591493
ssh->scpFileName[0] = '\0'; /* make sure null terminated for check */
14601494
}
14611495

@@ -3057,7 +3091,8 @@ static int ScpProcessEntry(WOLFSSH* ssh, char* fileName, word64* mTime,
30573091
DEFAULT_SCP_FILE_NAME_SZ);
30583092
WSTRNCPY(fileName, sendCtx->currentDir->dir.lfname,
30593093
DEFAULT_SCP_FILE_NAME_SZ);
3060-
if (wolfSSH_CleanPath(ssh, filePath, DEFAULT_SCP_FILE_NAME_SZ) < 0) {
3094+
if (wolfSSH_CleanPath(ssh, filePath,
3095+
DEFAULT_SCP_FILE_NAME_SZ) < 0) {
30613096
ret = WS_SCP_ABORT;
30623097
}
30633098
#elif defined(USE_WINDOWS_API)

tests/api.c

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4082,6 +4082,128 @@ static int scpSendZeroFirst(WOLFSSH* ssh, int state, const char* peerRequest,
40824082
}
40834083
}
40844084

4085+
/* Drives a real "scp -r" of a directory holding two files through the
4086+
* default filesystem send/recv callbacks. The header-dedup fix gates
4087+
* sending a file's header on a scpFileHeaderSent flag that gets reset when
4088+
* SCP_SEND_FILE loops back to SCP_TRANSFER for the next file in a recursive
4089+
* copy; this confirms that reset lets the second file get its own header
4090+
* instead of it being duplicated or skipped. */
4091+
static void test_wolfSSH_SCP_RecursiveTwoFiles(void)
4092+
{
4093+
func_args ser;
4094+
tcp_ready ready;
4095+
int argsCount;
4096+
int ret;
4097+
word32 i;
4098+
WS_SOCKET_T clientFd;
4099+
#ifdef USE_WINDOWS_API
4100+
DWORD rcvTimeout = 20000;
4101+
#else
4102+
struct timeval rcvTimeout;
4103+
#endif
4104+
const char* args[6];
4105+
WOLFSSH_CTX* ctx = NULL;
4106+
WOLFSSH* ssh = NULL;
4107+
const char* dstDir = "./scp_recur_dst";
4108+
const char* out1 = "./scp_recur_dst/a_short.txt";
4109+
const char* out2 = "./scp_recur_dst/b_longer_name.txt";
4110+
byte data1[300];
4111+
byte data2[700];
4112+
char cmd[300];
4113+
char cwdBuf[200];
4114+
char file1[256];
4115+
char file2[256];
4116+
/* wolfSSH_SCP_from() mutates its src/dst buffers in place (e.g.
4117+
* ScpCheckForRename() writes a NUL into the path), so these cannot be
4118+
* string literals. The client thread chdir()s into dstDir while
4119+
* receiving a directory; since the client and server here share one
4120+
* process (and thus one cwd), srcBuf must be absolute so the server
4121+
* thread's concurrent directory walk does not resolve relative to
4122+
* whatever directory the client just chdir()ed into. */
4123+
char srcBuf[256];
4124+
char dstBuf[32];
4125+
THREAD_TYPE serThread;
4126+
4127+
for (i = 0; i < sizeof(data1); i++)
4128+
data1[i] = (byte)((i * 3 + 1) & 0xff);
4129+
for (i = 0; i < sizeof(data2); i++)
4130+
data2[i] = (byte)((i * 5 + 2) & 0xff);
4131+
4132+
AssertNotNull(WGETCWD(NULL, cwdBuf, sizeof(cwdBuf)));
4133+
WSNPRINTF(srcBuf, sizeof(srcBuf), "%s/scp_recur_src", cwdBuf);
4134+
WSTRNCPY(dstBuf, dstDir, sizeof(dstBuf));
4135+
WSNPRINTF(file1, sizeof(file1), "%s/a_short.txt", srcBuf);
4136+
WSNPRINTF(file2, sizeof(file2), "%s/b_longer_name.txt", srcBuf);
4137+
4138+
/* full teardown first: a run that aborted mid-transfer leaves these
4139+
* behind, and then WMKDIR below fails with EEXIST, masking the real
4140+
* failure with a setup error */
4141+
WREMOVE(NULL, file1);
4142+
WREMOVE(NULL, file2);
4143+
WRMDIR(NULL, srcBuf);
4144+
WREMOVE(NULL, out1);
4145+
WREMOVE(NULL, out2);
4146+
WRMDIR(NULL, dstDir);
4147+
4148+
AssertIntEQ(WMKDIR(NULL, srcBuf, 0700), 0);
4149+
AssertIntEQ(scpWriteTestFile(file1, data1, sizeof(data1)), 0);
4150+
AssertIntEQ(scpWriteTestFile(file2, data2, sizeof(data2)), 0);
4151+
4152+
WMEMSET(&ser, 0, sizeof(func_args));
4153+
argsCount = 0;
4154+
args[argsCount++] = ".";
4155+
args[argsCount++] = "-1";
4156+
args[argsCount++] = "-p";
4157+
args[argsCount++] = "0";
4158+
ser.argv = (char**)args;
4159+
ser.argc = argsCount;
4160+
ser.signal = &ready;
4161+
InitTcpReady(ser.signal);
4162+
ThreadStart(echoserver_test, (void*)&ser, &serThread);
4163+
WaitTcpReady(&ready);
4164+
4165+
WSNPRINTF(cmd, sizeof(cmd), "scp -r -f %s", srcBuf);
4166+
scp_client_connect(&ctx, &ssh, ready.port, cmd);
4167+
AssertNotNull(ctx);
4168+
AssertNotNull(ssh);
4169+
4170+
/* bound the recv so a regression fails the match assert below, not CI */
4171+
clientFd = wolfSSH_get_fd(ssh);
4172+
#ifdef USE_WINDOWS_API
4173+
(void)setsockopt(clientFd, SOL_SOCKET, SO_RCVTIMEO,
4174+
(const char*)&rcvTimeout, sizeof(rcvTimeout));
4175+
#else
4176+
rcvTimeout.tv_sec = 20;
4177+
rcvTimeout.tv_usec = 0;
4178+
(void)setsockopt(clientFd, SOL_SOCKET, SO_RCVTIMEO,
4179+
&rcvTimeout, sizeof(rcvTimeout));
4180+
#endif
4181+
4182+
ret = wolfSSH_SCP_from(ssh, srcBuf, dstBuf);
4183+
AssertIntEQ(ret, WS_SUCCESS);
4184+
4185+
ret = wolfSSH_shutdown(ssh);
4186+
(void)ret;
4187+
4188+
WCLOSESOCKET(wolfSSH_get_fd(ssh));
4189+
wolfSSH_free(ssh);
4190+
wolfSSH_CTX_free(ctx);
4191+
ThreadJoin(serThread);
4192+
FreeTcpReady(&ready);
4193+
4194+
/* a duplicated or skipped header on the second file corrupts the byte
4195+
* stream; an exact match on both files proves each got its own header */
4196+
AssertIntEQ(scpFilesMatch(out1, data1, sizeof(data1)), 0);
4197+
AssertIntEQ(scpFilesMatch(out2, data2, sizeof(data2)), 0);
4198+
4199+
WREMOVE(NULL, file1);
4200+
WREMOVE(NULL, file2);
4201+
WRMDIR(NULL, srcBuf);
4202+
WREMOVE(NULL, out1);
4203+
WREMOVE(NULL, out2);
4204+
WRMDIR(NULL, dstDir);
4205+
}
4206+
40854207
static void test_wolfSSH_SCP_SendZeroFirst(void)
40864208
{
40874209
func_args ser;
@@ -4175,6 +4297,7 @@ static void test_wolfSSH_SCP_ReKey_NonBlock(void) { ; }
41754297
static void test_wolfSSH_SCP_ReKey_ToServer(void) { ; }
41764298
static void test_wolfSSH_SCP_ReKey_ToServer_NonBlock(void) { ; }
41774299
static void test_wolfSSH_SCP_SendZeroFirst(void) { ; }
4300+
static void test_wolfSSH_SCP_RecursiveTwoFiles(void) { ; }
41784301
#endif
41794302

41804303

@@ -5377,6 +5500,7 @@ int wolfSSH_ApiTest(int argc, char** argv)
53775500
test_wolfSSH_SCP_ReKey_ToServer();
53785501
test_wolfSSH_SCP_ReKey_ToServer_NonBlock();
53795502
test_wolfSSH_SCP_SendZeroFirst();
5503+
test_wolfSSH_SCP_RecursiveTwoFiles();
53805504

53815505
/* SFTP tests */
53825506
test_wolfSSH_SFTP_SendReadPacket();

0 commit comments

Comments
 (0)