|
63 | 63 | #include <wolfssh/certman.h> |
64 | 64 | #endif |
65 | 65 |
|
| 66 | +#ifdef WOLFSSH_SFTP |
| 67 | +#include <wolfssh/wolfsftp.h> |
| 68 | +#endif |
| 69 | + |
66 | 70 | #if defined(WOLFSSH_SCP) && !defined(WOLFSSH_SCP_USER_CALLBACKS) && \ |
67 | 71 | !defined(NO_FILESYSTEM) && !defined(WOLFSSL_NUCLEUS) && \ |
68 | 72 | !defined(_WIN32) && !defined(WOLFSSH_ZEPHYR) |
@@ -4362,6 +4366,203 @@ static int test_DoUserAuthRequestRsaCert(void) |
4362 | 4366 |
|
4363 | 4367 | #endif /* !WOLFSSH_NO_RSA && !WOLFSSH_NO_SSH_RSA_SHA1 */ |
4364 | 4368 |
|
| 4369 | +#ifdef WOLFSSH_SFTP |
| 4370 | +/* Property test for the server-side received-packet size bound applied in |
| 4371 | + * wolfSSH_SFTP_read(). A non-positive size or one above the largest legal |
| 4372 | + * inbound SFTP message must be rejected before any buffer is allocated; |
| 4373 | + * in-range sizes are accepted. */ |
| 4374 | +static int test_SftpRecvSizeBound(void) |
| 4375 | +{ |
| 4376 | + int testVals[7]; |
| 4377 | + int expectOk; |
| 4378 | + int ret; |
| 4379 | + int i; |
| 4380 | + int maxWriteBody; |
| 4381 | + |
| 4382 | + testVals[0] = -1; /* underflow / error sentinel */ |
| 4383 | + testVals[1] = 0; /* empty body */ |
| 4384 | + testVals[2] = 1; /* smallest accepted body */ |
| 4385 | + testVals[3] = WOLFSSH_MAX_SFTP_PACKET; /* upper bound, accepted */ |
| 4386 | + testVals[4] = WOLFSSH_MAX_SFTP_PACKET + 1; /* just over the bound */ |
| 4387 | + testVals[5] = 0x40000000; /* the ~1 GB attack value */ |
| 4388 | + testVals[6] = 0x7FFFFFFF; /* INT32_MAX */ |
| 4389 | + |
| 4390 | + for (i = 0; i < (int)(sizeof(testVals) / sizeof(testVals[0])); i++) { |
| 4391 | + ret = wolfSSH_TestSftpRecvSizeCheck(testVals[i]); |
| 4392 | + |
| 4393 | + expectOk = (testVals[i] > 0 && |
| 4394 | + testVals[i] <= WOLFSSH_MAX_SFTP_PACKET); |
| 4395 | + |
| 4396 | + if (expectOk) { |
| 4397 | + if (ret != WS_SUCCESS) |
| 4398 | + return -900 - i; |
| 4399 | + } |
| 4400 | + else { |
| 4401 | + if (ret == WS_SUCCESS) |
| 4402 | + return -920 - i; |
| 4403 | + } |
| 4404 | + } |
| 4405 | + |
| 4406 | + /* The bound value must be large enough to admit a real maximum-size WRITE, |
| 4407 | + * otherwise legitimate large writes would be silently rejected. A WRITE |
| 4408 | + * body is a handle string (length prefix + up to WOLFSSH_MAX_HANDLE), an |
| 4409 | + * 8-byte file offset, then a data string (length prefix + up to |
| 4410 | + * WOLFSSH_MAX_SFTP_RW). Guards against a future shrink of the bound (e.g. |
| 4411 | + * to WOLFSSH_MAX_SFTP_RECV alone). */ |
| 4412 | + maxWriteBody = UINT32_SZ + WOLFSSH_MAX_HANDLE /* handle string */ |
| 4413 | + + (2 * UINT32_SZ) /* 64-bit offset */ |
| 4414 | + + UINT32_SZ + WOLFSSH_MAX_SFTP_RW; /* data string */ |
| 4415 | + if (wolfSSH_TestSftpRecvSizeCheck(maxWriteBody) != WS_SUCCESS) |
| 4416 | + return -930; |
| 4417 | + |
| 4418 | + return 0; |
| 4419 | +} |
| 4420 | + |
| 4421 | +/* IORecv mock that reports no data is available yet. ReceiveData maps this to |
| 4422 | + * WS_WANT_READ, letting the receive loop reach its body-read state without a |
| 4423 | + * live socket. */ |
| 4424 | +static int RecvAlwaysWantRead(WOLFSSH* ssh, void* data, word32 sz, void* ctx) |
| 4425 | +{ |
| 4426 | + WOLFSSH_UNUSED(ssh); |
| 4427 | + WOLFSSH_UNUSED(data); |
| 4428 | + WOLFSSH_UNUSED(sz); |
| 4429 | + WOLFSSH_UNUSED(ctx); |
| 4430 | + return WS_CBIO_ERR_WANT_READ; |
| 4431 | +} |
| 4432 | + |
| 4433 | +/* Drives a single crafted SFTP request header through wolfSSH_SFTP_read() on a |
| 4434 | + * fresh server session. The on-wire length field is set to len and the message |
| 4435 | + * type to WRITE; only the 9-byte header is staged in the channel input buffer, |
| 4436 | + * never a body. ioRecv, when non-NULL, stands in for the socket if the loop is |
| 4437 | + * admitted and reaches its body-read state. On success fills *outRet with the |
| 4438 | + * wolfSSH_SFTP_read() return and *outErr with ssh->error, and returns 0; on a |
| 4439 | + * setup failure returns a negative sentinel. DiscardIoSend absorbs the |
| 4440 | + * window-adjust emitted after the header is consumed, and acceptState is |
| 4441 | + * advanced so that adjust is allowed on the bare session. */ |
| 4442 | +static int SftpRecvDriveHeader(word32 len, WS_CallbackIORecv ioRecv, |
| 4443 | + int* outRet, int* outErr) |
| 4444 | +{ |
| 4445 | + WOLFSSH_CTX* ctx = NULL; |
| 4446 | + WOLFSSH* ssh = NULL; |
| 4447 | + WOLFSSH_CHANNEL* ch = NULL; |
| 4448 | + int result = 0; |
| 4449 | + byte header[WOLFSSH_SFTP_HEADER]; |
| 4450 | + |
| 4451 | + *outRet = WS_SUCCESS; |
| 4452 | + *outErr = WS_SUCCESS; |
| 4453 | + |
| 4454 | + header[0] = (byte)((len >> 24) & 0xFF); |
| 4455 | + header[1] = (byte)((len >> 16) & 0xFF); |
| 4456 | + header[2] = (byte)((len >> 8) & 0xFF); |
| 4457 | + header[3] = (byte)( len & 0xFF); |
| 4458 | + header[LENGTH_SZ] = WOLFSSH_FTP_WRITE; /* message type */ |
| 4459 | + header[LENGTH_SZ + MSG_ID_SZ + 0] = 0x00; /* request id = 1 */ |
| 4460 | + header[LENGTH_SZ + MSG_ID_SZ + 1] = 0x00; |
| 4461 | + header[LENGTH_SZ + MSG_ID_SZ + 2] = 0x00; |
| 4462 | + header[LENGTH_SZ + MSG_ID_SZ + 3] = 0x01; |
| 4463 | + |
| 4464 | + ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL); |
| 4465 | + if (ctx == NULL) |
| 4466 | + return -1000; |
| 4467 | + wolfSSH_SetIOSend(ctx, DiscardIoSend); |
| 4468 | + if (ioRecv != NULL) |
| 4469 | + wolfSSH_SetIORecv(ctx, ioRecv); |
| 4470 | + |
| 4471 | + ssh = wolfSSH_new(ctx); |
| 4472 | + if (ssh == NULL) { result = -1001; goto done; } |
| 4473 | + /* Allow MSGID_CHANNEL_WINDOW_ADJUST on this bare session. */ |
| 4474 | + ssh->acceptState = ACCEPT_SERVER_USERAUTH_SENT; |
| 4475 | + |
| 4476 | + ch = ChannelNew(ssh, ID_CHANTYPE_SESSION, 1024, 1024); |
| 4477 | + if (ch == NULL) { result = -1002; goto done; } |
| 4478 | + if (ChannelAppend(ssh, ch) != WS_SUCCESS) { |
| 4479 | + ChannelDelete(ch, ssh->ctx->heap); |
| 4480 | + result = -1003; |
| 4481 | + goto done; |
| 4482 | + } |
| 4483 | + |
| 4484 | + if (wolfSSH_TestChannelPutData(ssh->channelList, header, |
| 4485 | + (word32)sizeof(header)) != WS_SUCCESS) { |
| 4486 | + result = -1004; |
| 4487 | + goto done; |
| 4488 | + } |
| 4489 | + |
| 4490 | + *outRet = wolfSSH_SFTP_read(ssh); |
| 4491 | + *outErr = wolfSSH_get_error(ssh); |
| 4492 | + |
| 4493 | +done: |
| 4494 | + wolfSSH_free(ssh); |
| 4495 | + wolfSSH_CTX_free(ctx); |
| 4496 | + return result; |
| 4497 | +} |
| 4498 | + |
| 4499 | +/* End-to-end check that the receive loop itself rejects an invalid declared |
| 4500 | + * inbound length, not just the SFTP_CheckRecvSz helper in isolation. Drives two |
| 4501 | + * crafted headers whose decoded body length the bound must refuse: one well |
| 4502 | + * past WOLFSSH_MAX_SFTP_PACKET and one of zero. Each must return WS_FATAL_ERROR |
| 4503 | + * with ssh->error == WS_BUFFER_E and allocate no body buffer. The zero case |
| 4504 | + * also guards against ssh->error being left at 0, which a caller could misread |
| 4505 | + * as a clean channel close. */ |
| 4506 | +static int test_SftpRecvSizeBoundIntegration(void) |
| 4507 | +{ |
| 4508 | + word32 lens[2]; |
| 4509 | + int rc; |
| 4510 | + int ret; |
| 4511 | + int err; |
| 4512 | + int i; |
| 4513 | + |
| 4514 | + /* On-wire length field counts the type byte, request id, and body. */ |
| 4515 | + lens[0] = (word32)WOLFSSH_MAX_SFTP_PACKET + 100 + MSG_ID_SZ + UINT32_SZ; |
| 4516 | + lens[1] = (word32)(MSG_ID_SZ + UINT32_SZ); /* decoded body length 0 */ |
| 4517 | + |
| 4518 | + for (i = 0; i < (int)(sizeof(lens) / sizeof(lens[0])); i++) { |
| 4519 | + rc = SftpRecvDriveHeader(lens[i], NULL, &ret, &err); |
| 4520 | + if (rc != 0) |
| 4521 | + return rc; |
| 4522 | + if (ret != WS_FATAL_ERROR) |
| 4523 | + return -945 - (i * 10); |
| 4524 | + if (err != WS_BUFFER_E) |
| 4525 | + return -946 - (i * 10); |
| 4526 | + } |
| 4527 | + |
| 4528 | + return 0; |
| 4529 | +} |
| 4530 | + |
| 4531 | +/* End-to-end check that a legitimate maximum-size WRITE is NOT rejected by the |
| 4532 | + * server bound. The decoded body length equals the largest real WRITE body |
| 4533 | + * (handle + 8-byte offset + max data). The bound must admit it: the loop |
| 4534 | + * allocates the body buffer and then asks for the body that has not arrived, so |
| 4535 | + * the call returns WS_FATAL_ERROR with ssh->error == WS_WANT_READ (a benign |
| 4536 | + * retry) rather than WS_BUFFER_E (a bound rejection). RecvAlwaysWantRead stands |
| 4537 | + * in for a non-blocking socket with no data yet. */ |
| 4538 | +static int test_SftpRecvSizeBoundAccept(void) |
| 4539 | +{ |
| 4540 | + word32 len; |
| 4541 | + int rc; |
| 4542 | + int ret; |
| 4543 | + int err; |
| 4544 | + |
| 4545 | + /* Largest legitimate WRITE body, matching test_SftpRecvSizeBound, plus the |
| 4546 | + * type byte and request id the on-wire length field carries. */ |
| 4547 | + len = (word32)(UINT32_SZ + WOLFSSH_MAX_HANDLE |
| 4548 | + + (2 * UINT32_SZ) |
| 4549 | + + UINT32_SZ + WOLFSSH_MAX_SFTP_RW) |
| 4550 | + + MSG_ID_SZ + UINT32_SZ; |
| 4551 | + |
| 4552 | + rc = SftpRecvDriveHeader(len, RecvAlwaysWantRead, &ret, &err); |
| 4553 | + if (rc != 0) |
| 4554 | + return rc; |
| 4555 | + if (ret != WS_FATAL_ERROR) |
| 4556 | + return -955; |
| 4557 | + /* WS_WANT_READ confirms the bound admitted the body and the loop is waiting |
| 4558 | + * on it; WS_BUFFER_E here would mean a legitimate max WRITE was rejected. */ |
| 4559 | + if (err != WS_WANT_READ) |
| 4560 | + return -956; |
| 4561 | + |
| 4562 | + return 0; |
| 4563 | +} |
| 4564 | +#endif /* WOLFSSH_SFTP */ |
| 4565 | + |
4365 | 4566 | #endif /* WOLFSSH_TEST_INTERNAL */ |
4366 | 4567 |
|
4367 | 4568 | /* Error Code And Message Test */ |
@@ -4540,6 +4741,22 @@ int wolfSSH_UnitTest(int argc, char** argv) |
4540 | 4741 | printf("IdentifyAsn1Key: %s\n", (unitResult == 0 ? "SUCCESS" : "FAILED")); |
4541 | 4742 | testResult = testResult || unitResult; |
4542 | 4743 |
|
| 4744 | +#ifdef WOLFSSH_SFTP |
| 4745 | + unitResult = test_SftpRecvSizeBound(); |
| 4746 | + printf("SftpRecvSizeBound: %s\n", (unitResult == 0 ? "SUCCESS" : "FAILED")); |
| 4747 | + testResult = testResult || unitResult; |
| 4748 | + |
| 4749 | + unitResult = test_SftpRecvSizeBoundIntegration(); |
| 4750 | + printf("SftpRecvSizeBoundIntegration: %s\n", |
| 4751 | + (unitResult == 0 ? "SUCCESS" : "FAILED")); |
| 4752 | + testResult = testResult || unitResult; |
| 4753 | + |
| 4754 | + unitResult = test_SftpRecvSizeBoundAccept(); |
| 4755 | + printf("SftpRecvSizeBoundAccept: %s\n", |
| 4756 | + (unitResult == 0 ? "SUCCESS" : "FAILED")); |
| 4757 | + testResult = testResult || unitResult; |
| 4758 | +#endif |
| 4759 | + |
4543 | 4760 | #if defined(WOLFSSH_SCP) && !defined(WOLFSSH_SCP_USER_CALLBACKS) && \ |
4544 | 4761 | !defined(NO_FILESYSTEM) && !defined(WOLFSSL_NUCLEUS) && \ |
4545 | 4762 | !defined(_WIN32) && !defined(WOLFSSH_ZEPHYR) |
|
0 commit comments