Several sftp(1) commands will fail if the remote pwd contains glob(3)
meta characters. This is due to glob being called on the concatenated
pwd and command argument in order to generate a file list to operate
on. However, the pwd is not escaped before calling glob, so glob
will offer no useful results and sftp will error "not found".

The following commands will not work in some or all conditions when
called from one of these meta character directories: get, rm, chmod,
chown, chgrp, ls, and tab-completions.

The diff below extends the make_absolute() function to optionally
add escape characters to the pwd before generating the absolute
path version of the argument which will be sent to glob. I'd be
happy to rewrite if a different solution is preferable.

--
Christian


Index: sftp.c
===================================================================
RCS file: /cvs/src/usr.bin/ssh/sftp.c,v
retrieving revision 1.171
diff -u -p -r1.171 sftp.c
--- sftp.c      20 Aug 2015 22:32:42 -0000      1.171
+++ sftp.c      27 Dec 2015 21:03:08 -0000
@@ -154,6 +154,10 @@ struct CMD {
 #define REMOTE 1
 #define LOCAL  2
 
+/* Options for handling glob meta chars in pwd */
+#define NOESCAPE_META  0
+#define ESCAPE_META    1
+
 static const struct CMD cmds[] = {
        { "bye",        I_QUIT,         NOARGS  },
        { "cd",         I_CHDIR,        REMOTE  },
@@ -330,14 +334,63 @@ path_strip(char *path, char *strip)
        return (xstrdup(path));
 }
 
+/* Escape any glob(3) meta characters in the pwd */
 static char *
-make_absolute(char *p, char *pwd)
+escape_glob_meta(char *pwd)
 {
-       char *abs_str;
+       size_t i, j, expand_len, len;
+       char *esc_pwd;
+
+       len = strlen(pwd);
+
+       expand_len = 0;
+       for (i = 0; i < len; i++) {
+               switch (pwd[i]) {
+               case '*':
+               case '?':
+               case '[':
+               case '\\':
+                       expand_len++;
+                       break;
+               }
+       }
+
+       esc_pwd = xmalloc(len + expand_len + 1);
+
+       for (i = j = 0; i < len; i++) {
+               switch (pwd[i]) {
+               case '*':
+               case '?':
+               case '[':
+               case '\\':
+                       esc_pwd[j++] = '\\';
+                       /* FALLTHROUGH */
+               default:
+                       esc_pwd[j++] = pwd[i];
+                       break;
+               }
+       }
+       esc_pwd[j] = 0;
+
+       return esc_pwd;
+}
+
+static char *
+make_absolute(char *p, char *pwd, int escopt)
+{
+       char *abs_str, *tmp_pwd;
 
        /* Derelativise */
        if (p && p[0] != '/') {
-               abs_str = path_append(pwd, p);
+               if(escopt == ESCAPE_META)
+                       tmp_pwd = escape_glob_meta(pwd);
+               else
+                       tmp_pwd = pwd;
+
+               abs_str = path_append(tmp_pwd, p);
+
+               if(escopt == ESCAPE_META)
+                       free(tmp_pwd);
                free(p);
                return(abs_str);
        } else
@@ -573,7 +626,7 @@ process_get(struct sftp_conn *conn, char
        int i, r, err = 0;
 
        abs_src = xstrdup(src);
-       abs_src = make_absolute(abs_src, pwd);
+       abs_src = make_absolute(abs_src, pwd, ESCAPE_META);
        memset(&g, 0, sizeof(g));
 
        debug3("Looking up %s", abs_src);
@@ -660,7 +713,7 @@ process_put(struct sftp_conn *conn, char
 
        if (dst) {
                tmp_dst = xstrdup(dst);
-               tmp_dst = make_absolute(tmp_dst, pwd);
+               tmp_dst = make_absolute(tmp_dst, pwd, NOESCAPE_META);
        }
 
        memset(&g, 0, sizeof(g));
@@ -707,7 +760,8 @@ process_put(struct sftp_conn *conn, char
                } else if (tmp_dst) {
                        abs_dst = path_append(tmp_dst, filename);
                } else {
-                       abs_dst = make_absolute(xstrdup(filename), pwd);
+                       abs_dst = make_absolute(xstrdup(filename), pwd,
+                           NOESCAPE_META);
                }
                free(tmp);
 
@@ -1417,20 +1471,20 @@ parse_dispatch_command(struct sftp_conn 
                    rflag, aflag, fflag);
                break;
        case I_RENAME:
-               path1 = make_absolute(path1, *pwd);
-               path2 = make_absolute(path2, *pwd);
+               path1 = make_absolute(path1, *pwd, NOESCAPE_META);
+               path2 = make_absolute(path2, *pwd, NOESCAPE_META);
                err = do_rename(conn, path1, path2, lflag);
                break;
        case I_SYMLINK:
                sflag = 1;
        case I_LINK:
                if (!sflag)
-                       path1 = make_absolute(path1, *pwd);
-               path2 = make_absolute(path2, *pwd);
+                       path1 = make_absolute(path1, *pwd, NOESCAPE_META);
+               path2 = make_absolute(path2, *pwd, NOESCAPE_META);
                err = (sflag ? do_symlink : do_hardlink)(conn, path1, path2);
                break;
        case I_RM:
-               path1 = make_absolute(path1, *pwd);
+               path1 = make_absolute(path1, *pwd, ESCAPE_META);
                remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
                for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
                        if (!quiet)
@@ -1441,18 +1495,18 @@ parse_dispatch_command(struct sftp_conn 
                }
                break;
        case I_MKDIR:
-               path1 = make_absolute(path1, *pwd);
+               path1 = make_absolute(path1, *pwd, NOESCAPE_META);
                attrib_clear(&a);
                a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
                a.perm = 0777;
                err = do_mkdir(conn, path1, &a, 1);
                break;
        case I_RMDIR:
-               path1 = make_absolute(path1, *pwd);
+               path1 = make_absolute(path1, *pwd, NOESCAPE_META);
                err = do_rmdir(conn, path1);
                break;
        case I_CHDIR:
-               path1 = make_absolute(path1, *pwd);
+               path1 = make_absolute(path1, *pwd, NOESCAPE_META);
                if ((tmp = do_realpath(conn, path1)) == NULL) {
                        err = 1;
                        break;
@@ -1489,14 +1543,14 @@ parse_dispatch_command(struct sftp_conn 
                if (*path1 != '/')
                        tmp = *pwd;
 
-               path1 = make_absolute(path1, *pwd);
+               path1 = make_absolute(path1, *pwd, ESCAPE_META);
                err = do_globbed_ls(conn, path1, tmp, lflag);
                break;
        case I_DF:
                /* Default to current directory if no path specified */
                if (path1 == NULL)
                        path1 = xstrdup(*pwd);
-               path1 = make_absolute(path1, *pwd);
+               path1 = make_absolute(path1, *pwd, NOESCAPE_META);
                err = do_df(conn, path1, hflag, iflag);
                break;
        case I_LCHDIR:
@@ -1527,7 +1581,7 @@ parse_dispatch_command(struct sftp_conn 
                printf("Local umask: %03lo\n", n_arg);
                break;
        case I_CHMOD:
-               path1 = make_absolute(path1, *pwd);
+               path1 = make_absolute(path1, *pwd, ESCAPE_META);
                attrib_clear(&a);
                a.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS;
                a.perm = n_arg;
@@ -1542,7 +1596,7 @@ parse_dispatch_command(struct sftp_conn 
                break;
        case I_CHOWN:
        case I_CHGRP:
-               path1 = make_absolute(path1, *pwd);
+               path1 = make_absolute(path1, *pwd, ESCAPE_META);
                remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
                for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
                        if (!(aa = do_stat(conn, g.gl_pathv[i], 0))) {
@@ -1814,7 +1868,7 @@ complete_match(EditLine *el, struct sftp
 
        memset(&g, 0, sizeof(g));
        if (remote != LOCAL) {
-               tmp = make_absolute(tmp, remote_path);
+               tmp = make_absolute(tmp, remote_path, ESCAPE_META);
                remote_glob(conn, tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
        } else
                glob(tmp, GLOB_DOOFFS|GLOB_MARK, NULL, &g);
@@ -2032,7 +2086,7 @@ interactive_loop(struct sftp_conn *conn,
 
        if (file1 != NULL) {
                dir = xstrdup(file1);
-               dir = make_absolute(dir, remote_path);
+               dir = make_absolute(dir, remote_path, NOESCAPE_META);
 
                if (remote_is_dir(conn, dir) && file2 == NULL) {
                        if (!quiet)
@@ -2046,6 +2100,10 @@ interactive_loop(struct sftp_conn *conn,
                                return (-1);
                        }
                } else {
+                       free(dir);
+                       dir = xstrdup(file1);
+                       dir = make_absolute(dir, remote_path, ESCAPE_META);
+
                        /* XXX this is wrong wrt quoting */
                        snprintf(cmd, sizeof cmd, "get%s %s%s%s",
                            global_aflag ? " -a" : "", dir,

Reply via email to