@@ -17459,6 +17459,158 @@ static void TestSftpOpenDirAllocFailure(void)
1745917459#endif /* !WOLFSSL_STATIC_MEMORY && !WOLFSSL_DEBUG_MEMORY */
1746017460#endif /* NO_WOLFSSH_DIR */
1746117461
17462+ #ifdef WOLFSSH_SFTP_ERRNO
17463+ /* FXP_REMOVE, FXP_STAT, FXP_LSTAT or FXP_OPENDIR path, by type. */
17464+ static int SftpPathRequest(WOLFSSH* ssh, byte type, int reqId,
17465+ const char* path)
17466+ {
17467+ byte pkt[WOLFSSH_MAX_FILENAME + UINT32_SZ];
17468+ word32 idx = 0;
17469+ word32 pathSz = (word32)WSTRLEN(path);
17470+
17471+ SftpPutU32(pathSz, pkt + idx); idx += UINT32_SZ;
17472+ WMEMCPY(pkt + idx, path, pathSz); idx += pathSz;
17473+
17474+ switch (type) {
17475+ case WOLFSSH_FTP_REMOVE:
17476+ return wolfSSH_SFTP_RecvRemove(ssh, reqId, pkt, idx);
17477+ case WOLFSSH_FTP_STAT:
17478+ return wolfSSH_SFTP_RecvSTAT(ssh, reqId, pkt, idx);
17479+ case WOLFSSH_FTP_LSTAT:
17480+ return wolfSSH_SFTP_RecvLSTAT(ssh, reqId, pkt, idx);
17481+ #ifndef NO_WOLFSSH_DIR
17482+ case WOLFSSH_FTP_OPENDIR:
17483+ return wolfSSH_SFTP_RecvOpenDir(ssh, reqId, pkt, idx);
17484+ #endif
17485+ default:
17486+ return WS_BAD_ARGUMENT;
17487+ }
17488+ }
17489+
17490+ /* FXP_RENAME from to. */
17491+ static int SftpRenamePath(WOLFSSH* ssh, int reqId, const char* from,
17492+ const char* to)
17493+ {
17494+ byte pkt[2 * (WOLFSSH_MAX_FILENAME + UINT32_SZ)];
17495+ word32 idx = 0;
17496+ word32 sz;
17497+
17498+ sz = (word32)WSTRLEN(from);
17499+ SftpPutU32(sz, pkt + idx); idx += UINT32_SZ;
17500+ WMEMCPY(pkt + idx, from, sz); idx += sz;
17501+ sz = (word32)WSTRLEN(to);
17502+ SftpPutU32(sz, pkt + idx); idx += UINT32_SZ;
17503+ WMEMCPY(pkt + idx, to, sz); idx += sz;
17504+
17505+ return wolfSSH_SFTP_RecvRename(ssh, reqId, pkt, idx);
17506+ }
17507+
17508+ /* A request naming a file that does not exist answers NO_SUCH_FILE, one the
17509+ * server may not reach answers PERMISSION_DENIED, and anything else stays
17510+ * FAILURE (draft-ietf-secsh-filexfer-02 Section 7). The cause comes from
17511+ * errno, so this runs on the POSIX file system only. */
17512+ static void TestSftpMissingFileStatus(void)
17513+ {
17514+ static const byte pathTypes[] = {
17515+ WOLFSSH_FTP_STAT, WOLFSSH_FTP_LSTAT, WOLFSSH_FTP_REMOVE,
17516+ #ifndef NO_WOLFSSH_DIR
17517+ WOLFSSH_FTP_OPENDIR,
17518+ #endif
17519+ };
17520+ WOLFSSH_CTX* ctx;
17521+ WOLFSSH* ssh;
17522+ int rid = 1000;
17523+ int reqId;
17524+ word32 i;
17525+ byte handle[WOLFSSH_HANDLE_ID_SZ];
17526+ char cwd[WOLFSSH_MAX_FILENAME];
17527+ char dir[64];
17528+ char missing[96];
17529+ char locked[96];
17530+ char present[96];
17531+ char inLocked[128];
17532+ FILE* f;
17533+
17534+ ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL);
17535+ AssertNotNull(ctx);
17536+ ssh = wolfSSH_new(ctx);
17537+ AssertNotNull(ssh);
17538+ AssertIntEQ(wolfSSH_SFTP_TestRecvStateInit(ssh), WS_SUCCESS);
17539+
17540+ WMEMSET(cwd, 0, sizeof(cwd));
17541+ AssertNotNull(WGETCWD(ssh->fs, cwd, sizeof(cwd) - 1));
17542+ AssertIntEQ(wolfSSH_SFTP_SetDefaultPath(ssh, cwd), WS_SUCCESS);
17543+ AssertIntEQ(wolfSSH_SFTP_SetConfinePath(ssh, cwd), WS_SUCCESS);
17544+
17545+ WSNPRINTF(dir, sizeof(dir), "wolfssh_nofile_%d", (int)getpid());
17546+ WSNPRINTF(missing, sizeof(missing), "%s/missing", dir);
17547+ WSNPRINTF(present, sizeof(present), "%s/present", dir);
17548+ WSNPRINTF(locked, sizeof(locked), "%s/locked", dir);
17549+ WSNPRINTF(inLocked, sizeof(inLocked), "%s/locked/x", dir);
17550+ AssertIntEQ(mkdir(dir, 0700), 0);
17551+ AssertIntEQ(mkdir(locked, 0700), 0);
17552+ f = fopen(present, "wb");
17553+ AssertNotNull(f);
17554+ fclose(f);
17555+
17556+ /* missing file */
17557+ for (i = 0; i < sizeof(pathTypes); i++) {
17558+ reqId = rid++;
17559+ AssertTrue(SftpPathRequest(ssh, pathTypes[i], reqId, missing)
17560+ != WS_SUCCESS);
17561+ AssertSftpStatusReply(ssh, reqId, WOLFSSH_FTP_NOFILE);
17562+ }
17563+ reqId = rid++;
17564+ AssertTrue(SftpOpenPath(ssh, reqId, missing, WOLFSSH_FXF_READ, handle)
17565+ != WS_SUCCESS);
17566+ AssertSftpStatusReply(ssh, reqId, WOLFSSH_FTP_NOFILE);
17567+ reqId = rid++;
17568+ AssertTrue(SftpRenamePath(ssh, reqId, missing, present) != WS_SUCCESS);
17569+ AssertSftpStatusReply(ssh, reqId, WOLFSSH_FTP_NOFILE);
17570+
17571+ #ifndef NO_WOLFSSH_DIR
17572+ /* ENOTDIR counts as missing, as in OpenSSH's sftp-server */
17573+ reqId = rid++;
17574+ AssertTrue(SftpPathRequest(ssh, WOLFSSH_FTP_OPENDIR, reqId, present)
17575+ != WS_SUCCESS);
17576+ AssertSftpStatusReply(ssh, reqId, WOLFSSH_FTP_NOFILE);
17577+ #endif
17578+ /* present but not removable, a non-empty directory: FAILURE */
17579+ reqId = rid++;
17580+ AssertTrue(SftpPathRequest(ssh, WOLFSSH_FTP_REMOVE, reqId, dir)
17581+ != WS_SUCCESS);
17582+ AssertSftpStatusReply(ssh, reqId, WOLFSSH_FTP_FAILURE);
17583+
17584+ /* a search-permission failure on the way: PERMISSION_DENIED. Root
17585+ * passes the permission check, so only an unprivileged run sees it. */
17586+ AssertIntEQ(chmod(locked, 0), 0);
17587+ if (geteuid() != 0) {
17588+ for (i = 0; i < sizeof(pathTypes); i++) {
17589+ reqId = rid++;
17590+ AssertTrue(SftpPathRequest(ssh, pathTypes[i], reqId, inLocked)
17591+ != WS_SUCCESS);
17592+ AssertSftpStatusReply(ssh, reqId, WOLFSSH_FTP_PERMISSION);
17593+ }
17594+ reqId = rid++;
17595+ AssertTrue(SftpOpenPath(ssh, reqId, inLocked, WOLFSSH_FXF_READ,
17596+ handle) != WS_SUCCESS);
17597+ AssertSftpStatusReply(ssh, reqId, WOLFSSH_FTP_PERMISSION);
17598+ reqId = rid++;
17599+ AssertTrue(SftpRenamePath(ssh, reqId, inLocked, present)
17600+ != WS_SUCCESS);
17601+ AssertSftpStatusReply(ssh, reqId, WOLFSSH_FTP_PERMISSION);
17602+ }
17603+ AssertIntEQ(chmod(locked, 0700), 0);
17604+
17605+ (void)remove(present);
17606+ (void)rmdir(locked);
17607+ (void)rmdir(dir);
17608+ wolfSSH_SFTP_TestRecvStateFree(ssh);
17609+ wolfSSH_free(ssh);
17610+ wolfSSH_CTX_free(ctx);
17611+ }
17612+ #endif /* WOLFSSH_SFTP_ERRNO */
17613+
1746217614/* A failed close() must still drop the handle from the session tracking list;
1746317615 * otherwise the stale descriptor lingers and is closed a second time when the
1746417616 * session is torn down. Open a file, invalidate its descriptor out of band so
@@ -21712,6 +21864,10 @@ int main(int argc, char** argv)
2171221864 TestSftpOpenDirAllocFailure();
2171321865 #endif
2171421866 #endif
21867+ #ifdef WOLFSSH_SFTP_ERRNO
21868+ /* a missing file answers NO_SUCH_FILE, a blocked one PERMISSION_DENIED */
21869+ TestSftpMissingFileStatus();
21870+ #endif
2171521871 /* a failed close still drops the handle from the tracking list */
2171621872 TestSftpCloseFailureRemovesHandle();
2171721873 /* confinement follows the confine root, not the start path */
0 commit comments