From 5039582a805ae859b830fadd27e3fc589ee0737b Mon Sep 17 00:00:00 2001 From: Manfred Kaiser Date: Sun, 12 Jul 2026 12:34:54 +0200 Subject: [PATCH] sftp: avoid re-parsing server-controlled path in cd fallback interactive_loop()'s directory-download case built a "cd \"%s\"" string from the server's realpath response and re-parsed it, the same pattern just fixed for the file-download case in 1b39f39657. A realpath containing a double quote breaks the parser, aborting the client. Extract the existing I_CHDIR logic into process_chdir() and call it directly from both places, avoiding the string round-trip entirely. --- sftp.c | 70 ++++++++++++++++++++++++++++++++++------------------------ 1 file changed, 41 insertions(+), 29 deletions(-) diff --git a/sftp.c b/sftp.c index 31d8fa494..bd8800a37 100644 --- a/sftp.c +++ b/sftp.c @@ -1552,6 +1552,44 @@ parse_args(const char **cpp, int *ignore_errors, int *disable_echo, int *aflag, return(cmdnum); } +/* + * Resolve *path1 (defaulting to startdir if empty) to an absolute, verified + * directory on the server and update *pwd to it. *path1 is updated in place + * via sftp_make_absolute() so the caller's normal cleanup of it still + * applies. Returns 0 on success, -1 on error (having already called error() + * if appropriate). + */ +static int +process_chdir(struct sftp_conn *conn, char **path1, char **pwd, + const char *startdir) +{ + char *tmp; + Attrib aa; + + if (*path1 == NULL || **path1 == '\0') + *path1 = xstrdup(startdir); + *path1 = sftp_make_absolute(*path1, *pwd); + if ((tmp = sftp_realpath(conn, *path1)) == NULL) + return -1; + if (sftp_stat(conn, tmp, 0, &aa) != 0) { + free(tmp); + return -1; + } + if (!(aa.flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) { + error("Can't change directory: Can't check target"); + free(tmp); + return -1; + } + if (!S_ISDIR(aa.perm)) { + error("Can't change directory: \"%s\" is not a directory", tmp); + free(tmp); + return -1; + } + free(*pwd); + *pwd = tmp; + return 0; +} + static int parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd, const char *startdir, int err_abort, int echo_command) @@ -1646,33 +1684,8 @@ parse_dispatch_command(struct sftp_conn *conn, const char *cmd, char **pwd, err = sftp_rmdir(conn, path1); break; case I_CHDIR: - if (path1 == NULL || *path1 == '\0') - path1 = xstrdup(startdir); - path1 = sftp_make_absolute(path1, *pwd); - if ((tmp = sftp_realpath(conn, path1)) == NULL) { + if (process_chdir(conn, &path1, pwd, startdir) != 0) err = 1; - break; - } - if (sftp_stat(conn, tmp, 0, &aa) != 0) { - free(tmp); - err = 1; - break; - } - if (!(aa.flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) { - error("Can't change directory: Can't check target"); - free(tmp); - err = 1; - break; - } - if (!S_ISDIR(aa.perm)) { - error("Can't change directory: \"%s\" is not " - "a directory", tmp); - free(tmp); - err = 1; - break; - } - free(*pwd); - *pwd = tmp; break; case I_LS: if (!path1) { @@ -2286,9 +2299,8 @@ interactive_loop(struct sftp_conn *conn, char *file1, char *file2) if (sftp_remote_is_dir(conn, dir) && file2 == NULL) { if (!quiet) mprintf("Changing to: %s\n", dir); - snprintf(cmd, sizeof cmd, "cd \"%s\"", dir); - if (parse_dispatch_command(conn, cmd, - &remote_path, startdir, 1, 0) != 0) { + if (process_chdir(conn, &dir, &remote_path, + startdir) != 0) { free(dir); free(startdir); free(remote_path); -- 2.54.0