Hi,

in various situations, including directory listings, progress and
diagnostic messages, the sftp(1) and scp(1) programs print untrusted
strings to the local terminal, often strings that were received
from a remote system over the wire, often containing whatever random
filenames on the remote system may contain, or in general whatever
the remote server sends.  That can screw up terminal state, for
example hang the local terminal, change key bindings, change fonts,
encodings, windows titles and many other terminal properties.  With
xterm(1), i did not find any vulnerabilities that could execute
arbitrary code injected remotely.  It is unclear what other terminals
and terminal emulators may do.

Here are a few examples of file names you can use for testing:

  touch `printf "bel\a."`
  touch `printf "esc\033[4munder"`
  touch `printf "wt\033]0;rogue_title\a."`

Try listing the directory containing such files in sftp(1) interactive
mode remotely or downloading them with sftp(1) or scp(1), in
particular using wildcards.  Typically, filename display silently
lacks some of the bytes contained in the filename, but the terminal
executes control codes instead (like beeping, changing fonts,
changing titles, or whatever the controls do).

The following patch sanitizes such strings before sending them to the
terminal.  I'm posting it here for wider review because deraadt@
suggested that public feedback is needed, in particular from people
who know a bit about multibyte character handling.

Note that the problems are not related to the following areas:
 - Portable OpenSSH.  They also affect OpenBSD-current to OpenBSD-current
   connections.
 - Line editing with libedit / editline(3).  They also affect
   non-interactive mode.
 - Multibyte characters.  They also occur when all relevant locales
   are set to C/POSIX and only seven-bit bytes are sent.
 - Character display widths.  The patch below does not attempt
   to fix output columnation but only output corruption involving
   terminal control codes.  While some of the new functions are
   already instrumented for width calculation (it doesn't complicate
   the code since wcwidth(3) validates printability of a character
   just like iswprint(3) does and isn't any harder to use), the
   calling code doesn't use that information yet, except in the
   progressmeter, where is it easily used without complicating the
   code.

Some general remarks:
 * The function names in utf8.c are composed from "mbs" for "multibyte
   string" and standard printf(3) function names, and they follow the
   spirit of the standard functions as closely as they can while still
   providing the additional sanitation required.  See the comments in
   utf8.c for details.  The general style of the functions resembles
   the style we developed for our ls(1) and similar programs, but as
   we already experienced, not two such programs needed exactly the
   same helper functions so far, but all have subtly different needs.
 * For a few of the strings printed with the new sanitizing functions,
   it may not be possible that they contain remote data.  But even
   local data may pose risks, for example when root copies around
   user home directories for backup or migration purposes.  So i'd
   prefer to err to the side of caution.
 * At first, i intended to do this in two steps:  First implement
   safe handling of US-ASCII, then in a second step add multibyte
   character handling.  But unfortunately, that doesn't work.
   US-ASCII sanitation would break display of filenames containing
   multibyte characters, and even though there isn't much explicit
   code in the programs to handle such characters yet, they partially
   work because most of the code just didn't care in the past what
   bytes in file names represented and just passed everything through
   verbatim, which is exactly where the problems come from, but
   OpenSSH developers pointed out to me that some users may rely
   on that, so they don't want to break the existing partial multibyte
   support, not even to improve ASCII terminal sanitation.  So,
   unfortunately, introducing the first bits of explicit multibyte
   support and introducing the first bits of terminal sanitation
   must be done in a single, rather big step.
 * Even this patch isn't perfect yet.  It still allows printing
   '\r' and '\n' to the terminal at too many places, for example
   when remote filenames contain these characters.  But the patch
   is already big, so fixing that should probably be kept seperate.
 * As noted in my recent undeadly posting, protecting the terminal
   is only possible if either (a) the terminal is UTF-8 *and* the
   remote side is either UTF-8 or US-ASCII, or (b) the *user*
   makes sure that the local locale(1) agrees with the remote one.
   If the remote side uses a non-UTF-8 and non-ASCII locale *or*
   you don't set a matching locale(1) before starting sftp(1) or
   scp(1), these programs can still screw up your terminal even
   with this patch, and i don't think there is any way to protect
   you from that.
 * On non-OpenBSD systems, users may legitimately set both the
   remote and the local locale(1) to the same encoding that is
   neither UTF-8 nor US-ASCII, in the worst case a state-dependent
   SHIFT encoding, and i do want to protect users doing that.
   However, in state dependent locales, one must NEVER attempt to
   recover from encoding errors because those invalidate state
   information, so to continue parsing would be utterly unsafe.
   Given an arbitrary locale on an arbitrary operating system,
   figuring out whether it is safely possible to recover from a
   particular error would be very complicated and fragile, which
   would be inadequate for security software like OpenSSH.
   Consequently, whenever encoding errors or non-printable characters
   are encountered in any non-UTF-8, non-ASCII locale, the code
   immediately aborts parsing and truncates the string to be displayed
   at the point of the first error.

Some comments on specific hunks of the patch:
 * progressmeter.c @@ -117,14 +119,14 @@ char buf
   The larger buffer will always be large enough on OpenBSD.  On
   systems with state-dependent encodings, if strings switch state
   back and forth a lot, the buffer may get exhausted before the
   display width limit is reached, so it is important to always
   check the buffer size before adding more bytes to prevent buffer
   overruns.
 * progressmeter.c @@ -155,17 +157,16 @@ buf[0]
   buf[0] = '\r' must be set up front.  mbssnprintf() cannot accept
   '\r' in the mode with a width limit because that character does
   not have a width.
 * progressmeter.c @@ -173,18 +174,18 @@ and subsequent
   win_size can no longer be used for overflow checks.
   Using sizeof(buf) seems safer anyway.
 * scp.c @@ -362,6 +364,8 @@ setlocale(LC_CTYPE, "");
   Adding #include <locale.h> and setlocale(LC_CTYPE, "") is
   necessary.  Running in POSIX mode when the terminal is set
   to an non-UTF-8 multibyte locale is NOT safe.

Feedback is welcome, and so are OKs.
  Ingo


Index: progressmeter.c
===================================================================
RCS file: /cvs/src/usr.bin/ssh/progressmeter.c,v
retrieving revision 1.42
diff -u -p -r1.42 progressmeter.c
--- progressmeter.c     2 Mar 2016 22:42:40 -0000       1.42
+++ progressmeter.c     15 Mar 2016 15:47:37 -0000
@@ -29,6 +29,7 @@
 
 #include <errno.h>
 #include <signal.h>
+#include <stdarg.h>
 #include <stdio.h>
 #include <string.h>
 #include <time.h>
@@ -37,6 +38,7 @@
 #include "progressmeter.h"
 #include "atomicio.h"
 #include "misc.h"
+#include "utf8.h"
 
 #define DEFAULT_WINSIZE 80
 #define MAX_WINSIZE 512
@@ -117,14 +119,14 @@ format_size(char *buf, int size, off_t b
 void
 refresh_progress_meter(void)
 {
-       char buf[MAX_WINSIZE + 1];
+       char buf[MAX_WINSIZE * 4 + 1];
        off_t transferred;
        double elapsed, now;
        int percent;
        off_t bytes_left;
        int cur_speed;
        int hours, minutes, seconds;
-       int i, len;
+       size_t i;
        int file_len;
 
        transferred = *counter - (cur_pos ? cur_pos : start_pos);
@@ -155,17 +157,16 @@ refresh_progress_meter(void)
                bytes_per_second = cur_speed;
 
        /* filename */
-       buf[0] = '\0';
+       buf[0] = '\r';
+       buf[1] = '\0';
        file_len = win_size - 35;
        if (file_len > 0) {
-               len = snprintf(buf, file_len + 1, "\r%s", file);
-               if (len < 0)
-                       len = 0;
-               if (len >= file_len + 1)
-                       len = file_len;
-               for (i = len; i < file_len; i++)
-                       buf[i] = ' ';
-               buf[file_len] = '\0';
+               (void) mbssnprintf(buf + 1, sizeof(buf) - 1 - 35,
+                   &file_len, "%s", file);
+               i = strlen(buf);
+               while (file_len++ < win_size - 35 && i + 1 < sizeof(buf))
+                       buf[i++] = ' ';
+               buf[i] = '\0';
        }
 
        /* percent of transfer done */
@@ -173,18 +174,18 @@ refresh_progress_meter(void)
                percent = ((float)cur_pos / end_pos) * 100;
        else
                percent = 100;
-       snprintf(buf + strlen(buf), win_size - strlen(buf),
+       snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
            " %3d%% ", percent);
 
        /* amount transferred */
-       format_size(buf + strlen(buf), win_size - strlen(buf),
+       format_size(buf + strlen(buf), sizeof(buf) - strlen(buf),
            cur_pos);
-       strlcat(buf, " ", win_size);
+       strlcat(buf, " ", sizeof(buf));
 
        /* bandwidth usage */
-       format_rate(buf + strlen(buf), win_size - strlen(buf),
+       format_rate(buf + strlen(buf), sizeof(buf) - strlen(buf),
            (off_t)bytes_per_second);
-       strlcat(buf, "/s ", win_size);
+       strlcat(buf, "/s ", sizeof(buf));
 
        /* ETA */
        if (!transferred)
@@ -193,9 +194,9 @@ refresh_progress_meter(void)
                stalled = 0;
 
        if (stalled >= STALL_TIME)
-               strlcat(buf, "- stalled -", win_size);
+               strlcat(buf, "- stalled -", sizeof(buf));
        else if (bytes_per_second == 0 && bytes_left)
-               strlcat(buf, "  --:-- ETA", win_size);
+               strlcat(buf, "  --:-- ETA", sizeof(buf));
        else {
                if (bytes_left > 0)
                        seconds = bytes_left / bytes_per_second;
@@ -208,19 +209,19 @@ refresh_progress_meter(void)
                seconds -= minutes * 60;
 
                if (hours != 0)
-                       snprintf(buf + strlen(buf), win_size - strlen(buf),
+                       snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
                            "%d:%02d:%02d", hours, minutes, seconds);
                else
-                       snprintf(buf + strlen(buf), win_size - strlen(buf),
+                       snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
                            "  %02d:%02d", minutes, seconds);
 
                if (bytes_left > 0)
-                       strlcat(buf, " ETA", win_size);
+                       strlcat(buf, " ETA", sizeof(buf));
                else
-                       strlcat(buf, "    ", win_size);
+                       strlcat(buf, "    ", sizeof(buf));
        }
 
-       atomicio(vwrite, STDOUT_FILENO, buf, win_size - 1);
+       atomicio(vwrite, STDOUT_FILENO, buf, strlen(buf));
        last_update = now;
 }
 
Index: scp.c
===================================================================
RCS file: /cvs/src/usr.bin/ssh/scp.c,v
retrieving revision 1.185
diff -u -p -r1.185 scp.c
--- scp.c       2 Mar 2016 22:43:52 -0000       1.185
+++ scp.c       15 Mar 2016 15:47:38 -0000
@@ -83,6 +83,7 @@
 #include <dirent.h>
 #include <errno.h>
 #include <fcntl.h>
+#include <locale.h>
 #include <pwd.h>
 #include <signal.h>
 #include <stdarg.h>
@@ -100,6 +101,7 @@
 #include "log.h"
 #include "misc.h"
 #include "progressmeter.h"
+#include "utf8.h"
 
 #define COPY_BUFLEN    16384
 
@@ -175,7 +177,7 @@ do_local_cmd(arglist *a)
        if (verbose_mode) {
                fprintf(stderr, "Executing:");
                for (i = 0; i < a->num; i++)
-                       fprintf(stderr, " %s", a->list[i]);
+                       mbsfprintf(stderr, " %s", a->list[i]);
                fprintf(stderr, "\n");
        }
        if ((pid = fork()) == -1)
@@ -216,7 +218,7 @@ do_cmd(char *host, char *remuser, char *
        int pin[2], pout[2], reserved[2];
 
        if (verbose_mode)
-               fprintf(stderr,
+               mbsfprintf(stderr,
                    "Executing: program %s host %s, user %s, command %s\n",
                    ssh_program, host,
                    remuser ? remuser : "(unspecified)", cmd);
@@ -291,7 +293,7 @@ do_cmd2(char *host, char *remuser, char 
        int status;
 
        if (verbose_mode)
-               fprintf(stderr,
+               mbsfprintf(stderr,
                    "Executing: 2nd program %s host %s, user %s, command %s\n",
                    ssh_program, host,
                    remuser ? remuser : "(unspecified)", cmd);
@@ -362,6 +364,8 @@ main(int argc, char **argv)
        /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
        sanitise_stdfd();
 
+       setlocale(LC_CTYPE, "");
+
        /* Copy argv, because we modify it */
        newargv = xcalloc(MAX(argc + 1, 1), sizeof(*newargv));
        for (n = 0; n < argc; n++)
@@ -789,9 +793,8 @@ syserr:                     run_err("%s: %s", name, strerr
                snprintf(buf, sizeof buf, "C%04o %lld %s\n",
                    (u_int) (stb.st_mode & FILEMODEMASK),
                    (long long)stb.st_size, last);
-               if (verbose_mode) {
-                       fprintf(stderr, "Sending file modes: %s", buf);
-               }
+               if (verbose_mode)
+                       mbsfprintf(stderr, "Sending file modes: %s", buf);
                (void) atomicio(vwrite, remout, buf, strlen(buf));
                if (response() < 0)
                        goto next;
@@ -868,7 +871,7 @@ rsource(char *name, struct stat *statp)
        (void) snprintf(path, sizeof path, "D%04o %d %.1024s\n",
            (u_int) (statp->st_mode & FILEMODEMASK), 0, last);
        if (verbose_mode)
-               fprintf(stderr, "Entering directory: %s", path);
+               mbsfprintf(stderr, "Entering directory: %s", path);
        (void) atomicio(vwrite, remout, path, strlen(path));
        if (response() < 0) {
                closedir(dirp);
@@ -908,7 +911,7 @@ sink(int argc, char **argv)
        off_t size, statbytes;
        unsigned long long ull;
        int setimes, targisdir, wrerrno = 0;
-       char ch, *cp, *np, *targ, *why, *vect[1], buf[2048];
+       char ch, *cp, *np, *targ, *why, *vect[1], buf[2048], visbuf[2048];
        struct timeval tv[2];
 
 #define        atime   tv[0]
@@ -943,12 +946,15 @@ sink(int argc, char **argv)
                } while (cp < &buf[sizeof(buf) - 1] && ch != '\n');
                *cp = 0;
                if (verbose_mode)
-                       fprintf(stderr, "Sink: %s", buf);
+                       mbsfprintf(stderr, "Sink: %s", buf);
 
                if (buf[0] == '\01' || buf[0] == '\02') {
-                       if (iamremote == 0)
+                       if (iamremote == 0) {
+                               (void) mbssnprintf(visbuf, sizeof(visbuf),
+                                   NULL, "%s", buf + 1);
                                (void) atomicio(vwrite, STDERR_FILENO,
-                                   buf + 1, strlen(buf + 1));
+                                   visbuf, strlen(visbuf));
+                       }
                        if (buf[0] == '\02')
                                exit(1);
                        ++errs;
@@ -1183,7 +1189,7 @@ screwup:
 int
 response(void)
 {
-       char ch, *cp, resp, rbuf[2048];
+       char ch, *cp, resp, rbuf[2048], visbuf[2048];
 
        if (atomicio(read, remin, &resp, sizeof(resp)) != sizeof(resp))
                lostconn(0);
@@ -1203,8 +1209,13 @@ response(void)
                        *cp++ = ch;
                } while (cp < &rbuf[sizeof(rbuf) - 1] && ch != '\n');
 
-               if (!iamremote)
-                       (void) atomicio(vwrite, STDERR_FILENO, rbuf, cp - rbuf);
+               if (!iamremote) {
+                       cp[-1] = '\0';
+                       (void) mbssnprintf(visbuf, sizeof(visbuf),
+                           NULL, "%s\n", rbuf);
+                       (void) atomicio(vwrite, STDERR_FILENO,
+                           visbuf, strlen(visbuf));
+               }
                ++errs;
                if (resp == 1)
                        return (-1);
@@ -1242,7 +1253,7 @@ run_err(const char *fmt,...)
 
        if (!iamremote) {
                va_start(ap, fmt);
-               vfprintf(stderr, fmt, ap);
+               mbsvfprintf(stderr, fmt, ap);
                va_end(ap);
                fprintf(stderr, "\n");
        }
@@ -1288,7 +1299,7 @@ okname(char *cp0)
        } while (*++cp);
        return (1);
 
-bad:   fprintf(stderr, "%s: invalid user name\n", cp0);
+bad:   mbsfprintf(stderr, "%s: invalid user name\n", cp0);
        return (0);
 }
 
Index: sftp-client.c
===================================================================
RCS file: /cvs/src/usr.bin/ssh/sftp-client.c,v
retrieving revision 1.121
diff -u -p -r1.121 sftp-client.c
--- sftp-client.c       11 Feb 2016 02:21:34 -0000      1.121
+++ sftp-client.c       15 Mar 2016 15:47:38 -0000
@@ -46,6 +46,7 @@
 #include "atomicio.h"
 #include "progressmeter.h"
 #include "misc.h"
+#include "utf8.h"
 
 #include "sftp.h"
 #include "sftp-common.h"
@@ -604,7 +605,7 @@ do_lsreaddir(struct sftp_conn *conn, con
                        }
 
                        if (print_flag)
-                               printf("%s\n", longname);
+                               mbsprintf("%s\n", longname);
 
                        /*
                         * Directory entries should never contain '/'
@@ -1450,7 +1451,7 @@ download_dir_internal(struct sftp_conn *
                return -1;
        }
        if (print_flag)
-               printf("Retrieving %s\n", src);
+               mbsprintf("Retrieving %s\n", src);
 
        if (dirattrib->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)
                mode = dirattrib->perm & 01777;
@@ -1782,7 +1783,7 @@ upload_dir_internal(struct sftp_conn *co
                return -1;
        }
        if (print_flag)
-               printf("Entering %s\n", src);
+               mbsprintf("Entering %s\n", src);
 
        attrib_clear(&a);
        stat_to_attrib(&sb, &a);
Index: sftp.c
===================================================================
RCS file: /cvs/src/usr.bin/ssh/sftp.c,v
retrieving revision 1.172
diff -u -p -r1.172 sftp.c
--- sftp.c      15 Feb 2016 09:47:49 -0000      1.172
+++ sftp.c      15 Mar 2016 15:47:39 -0000
@@ -31,6 +31,7 @@
 #include <libgen.h>
 #include <locale.h>
 #include <signal.h>
+#include <stdarg.h>
 #include <stdlib.h>
 #include <stdio.h>
 #include <string.h>
@@ -43,6 +44,7 @@
 #include "log.h"
 #include "pathnames.h"
 #include "misc.h"
+#include "utf8.h"
 
 #include "sftp.h"
 #include "ssherr.h"
@@ -622,9 +624,11 @@ process_get(struct sftp_conn *conn, char
 
                resume |= global_aflag;
                if (!quiet && resume)
-                       printf("Resuming %s to %s\n", g.gl_pathv[i], abs_dst);
+                       mbsprintf("Resuming %s to %s\n",
+                           g.gl_pathv[i], abs_dst);
                else if (!quiet && !resume)
-                       printf("Fetching %s to %s\n", g.gl_pathv[i], abs_dst);
+                       mbsprintf("Fetching %s to %s\n",
+                           g.gl_pathv[i], abs_dst);
                if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
                        if (download_dir(conn, g.gl_pathv[i], abs_dst, NULL,
                            pflag || global_pflag, 1, resume,
@@ -713,10 +717,11 @@ process_put(struct sftp_conn *conn, char
 
                 resume |= global_aflag;
                if (!quiet && resume)
-                       printf("Resuming upload of %s to %s\n", g.gl_pathv[i], 
-                               abs_dst);
+                       mbsprintf("Resuming upload of %s to %s\n",
+                           g.gl_pathv[i], abs_dst);
                else if (!quiet && !resume)
-                       printf("Uploading %s to %s\n", g.gl_pathv[i], abs_dst);
+                       mbsprintf("Uploading %s to %s\n",
+                           g.gl_pathv[i], abs_dst);
                if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
                        if (upload_dir(conn, g.gl_pathv[i], abs_dst,
                            pflag || global_pflag, 1, resume,
@@ -817,12 +822,12 @@ do_ls_dir(struct sftp_conn *conn, char *
                                attrib_to_stat(&d[n]->a, &sb);
                                lname = ls_file(fname, &sb, 1,
                                    (lflag & LS_SI_UNITS));
-                               printf("%s\n", lname);
+                               mbsprintf("%s\n", lname);
                                free(lname);
                        } else
-                               printf("%s\n", d[n]->longname);
+                               mbsprintf("%s\n", d[n]->longname);
                } else {
-                       printf("%-*s", colspace, fname);
+                       mbsprintf("%-*s", colspace, fname);
                        if (c >= columns) {
                                printf("\n");
                                c = 1;
@@ -903,10 +908,10 @@ do_globbed_ls(struct sftp_conn *conn, ch
                        }
                        lname = ls_file(fname, g.gl_statv[i], 1,
                            (lflag & LS_SI_UNITS));
-                       printf("%s\n", lname);
+                       mbsprintf("%s\n", lname);
                        free(lname);
                } else {
-                       printf("%-*s", colspace, fname);
+                       mbsprintf("%-*s", colspace, fname);
                        if (c >= columns) {
                                printf("\n");
                                c = 1;
@@ -1434,7 +1439,7 @@ parse_dispatch_command(struct sftp_conn 
                remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
                for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
                        if (!quiet)
-                               printf("Removing %s\n", g.gl_pathv[i]);
+                               mbsprintf("Removing %s\n", g.gl_pathv[i]);
                        err = do_rm(conn, g.gl_pathv[i]);
                        if (err != 0 && err_abort)
                                break;
@@ -1534,7 +1539,8 @@ parse_dispatch_command(struct sftp_conn 
                remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
                for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
                        if (!quiet)
-                               printf("Changing mode on %s\n", g.gl_pathv[i]);
+                               mbsprintf("Changing mode on %s\n",
+                                   g.gl_pathv[i]);
                        err = do_setstat(conn, g.gl_pathv[i], &a);
                        if (err != 0 && err_abort)
                                break;
@@ -1564,12 +1570,12 @@ parse_dispatch_command(struct sftp_conn 
                        aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
                        if (cmdnum == I_CHOWN) {
                                if (!quiet)
-                                       printf("Changing owner on %s\n",
+                                       mbsprintf("Changing owner on %s\n",
                                            g.gl_pathv[i]);
                                aa->uid = n_arg;
                        } else {
                                if (!quiet)
-                                       printf("Changing group on %s\n",
+                                       mbsprintf("Changing group on %s\n",
                                            g.gl_pathv[i]);
                                aa->gid = n_arg;
                        }
@@ -1579,7 +1585,7 @@ parse_dispatch_command(struct sftp_conn 
                }
                break;
        case I_PWD:
-               printf("Remote working directory: %s\n", *pwd);
+               mbsprintf("Remote working directory: %s\n", *pwd);
                break;
        case I_LPWD:
                if (!getcwd(path_buf, sizeof(path_buf))) {
@@ -1587,7 +1593,7 @@ parse_dispatch_command(struct sftp_conn 
                        err = -1;
                        break;
                }
-               printf("Local working directory: %s\n", path_buf);
+               mbsprintf("Local working directory: %s\n", path_buf);
                break;
        case I_QUIT:
                /* Processed below */
@@ -1655,7 +1661,7 @@ complete_display(char **list, u_int len)
        for (y = 0; list[y]; y++) {
                llen = strlen(list[y]);
                tmp = llen > len ? list[y] + len : "";
-               printf("%-*s", colspace, tmp);
+               mbsprintf("%-*s", colspace, tmp);
                if (m >= columns) {
                        printf("\n");
                        m = 1;
@@ -2036,7 +2042,7 @@ interactive_loop(struct sftp_conn *conn,
 
                if (remote_is_dir(conn, dir) && file2 == NULL) {
                        if (!quiet)
-                               printf("Changing to: %s\n", dir);
+                               mbsprintf("Changing to: %s\n", dir);
                        snprintf(cmd, sizeof cmd, "cd \"%s\"", dir);
                        if (parse_dispatch_command(conn, cmd,
                            &remote_path, 1) != 0) {
@@ -2082,7 +2088,7 @@ interactive_loop(struct sftp_conn *conn,
                                break;
                        }
                        if (!interactive) { /* Echo command */
-                               printf("sftp> %s", cmd);
+                               mbsprintf("sftp> %s", cmd);
                                if (strlen(cmd) > 0 &&
                                    cmd[strlen(cmd) - 1] != '\n')
                                        printf("\n");
Index: utf8.c
===================================================================
RCS file: utf8.c
diff -N utf8.c
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ utf8.c      15 Mar 2016 15:47:39 -0000
@@ -0,0 +1,163 @@
+/* $OpenBSD$ */
+/*
+ * Copyright (c) 2016 Ingo Schwarze <[email protected]>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * Utility functions for multibyte-character handling,
+ * in particular to sanitize untrusted strings for terminal output.
+ */
+
+#include <sys/types.h>
+#include <langinfo.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "utf8.h"
+
+static int      dangerous_locale(void);
+static int      mbsvsnprintf(char *, size_t, int *, const char *, va_list);
+
+
+/*
+ * For US-ASCII and UTF-8 encodings, we can safely recover from
+ * encoding errors and from non-printable characters.  For any
+ * other encodings, err to the side of caution and abort parsing:
+ * For state-dependent encodings, recovery is impossible.
+ * For arbitrary encodings, replacement of non-printable
+ * characters would be non-trivial and too fragile.
+ */
+
+static int
+dangerous_locale(void) {
+       char    *loc;
+
+       loc = nl_langinfo(CODESET);
+       return strcmp(loc, "US-ASCII") && strcmp(loc, "UTF-8");
+}
+
+/*
+ * The following two functions limit the number of bytes written,
+ * including the terminating '\0', to sz.  Unless wp is NULL,
+ * they limit the number of display columns occupied to *wp.
+ * Whichever is reached first terminates the output string.
+ * To stay close to the standard interfaces, they return the number of
+ * non-NUL bytes that would have been written if both were unlimited.
+ * If wp is NULL, newline, carriage return, and tab are allowed;
+ * otherwise, the actual number of columns occupied by what was
+ * written is returned in *wp.
+ */
+
+static int
+mbsvsnprintf(char *str, size_t sz, int *wp, const char *fmt, va_list ap)
+{
+       char    *sp;
+       wchar_t  wc;
+       int      len, width, total_width, max_width, ret;
+
+       if ((ret = vsnprintf(str, sz, fmt, ap)) <= 0)
+               *str = '\0';
+
+       total_width = 0;
+       max_width = wp == NULL ? INT_MAX : *wp;
+       for (sp = str; *sp != '\0'; sp += len) {
+               if ((len = mbtowc(&wc, sp, MB_CUR_MAX)) == -1) {
+                       (void)mbtowc(NULL, NULL, MB_CUR_MAX);
+                       if (dangerous_locale()) {
+                               *sp = '\0';
+                               break;
+                       }
+                       *sp = '?';
+                       len = 1;
+                       width = 1;
+               } else if (wp == NULL &&
+                   (wc == L'\n' || wc == L'\r' || wc == L'\t')) {
+                       width = 0;
+               } else if ((width = wcwidth(wc)) == -1) {
+                       if (dangerous_locale()) {
+                               *sp = '\0';
+                               break;
+                       }
+                       memset(sp, '?', len);
+                       width = len;
+               }
+               if (total_width + width > max_width) {
+                       *sp = '\0';
+                       break;
+               }
+               total_width += width;
+       }
+       if (wp != NULL)
+               *wp = total_width;
+       return ret;
+}
+
+int
+mbssnprintf(char *str, size_t sz, int *wp, const char *fmt, ...)
+{
+       va_list  ap;
+       int      ret;
+       
+       va_start(ap, fmt);
+       ret = mbsvsnprintf(str, sz, wp, fmt, ap);
+       va_end(ap);
+       return ret;
+}
+
+/*
+ * To stay close to the standard interfaces, the following functions
+ * return the number of non-NUL bytes written.
+ * Truncation detection is not needed and not supported.
+ */
+
+int
+mbsvfprintf(FILE *stream, const char *fmt, va_list ap)
+{
+       char     msgbuf[2048];
+       int      ret;
+
+       ret = mbsvsnprintf(msgbuf, sizeof(msgbuf), NULL, fmt, ap);
+       if (ret >= sizeof(msgbuf))
+               ret = sizeof(msgbuf) - 1;
+       fputs(msgbuf, stream);
+       return ret;
+}
+
+int
+mbsfprintf(FILE *stream, const char *fmt, ...)
+{
+       va_list  ap;
+       int      ret;
+
+       va_start(ap, fmt);
+       ret = mbsvfprintf(stream, fmt, ap);
+       va_end(ap);
+       return ret;
+}
+
+int
+mbsprintf(const char *fmt, ...)
+{
+       va_list  ap;
+       int      ret;
+
+       va_start(ap, fmt);
+       ret = mbsvfprintf(stdout, fmt, ap);
+       va_end(ap);
+       return ret;
+}
Index: utf8.h
===================================================================
RCS file: utf8.h
diff -N utf8.h
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ utf8.h      15 Mar 2016 15:47:39 -0000
@@ -0,0 +1,24 @@
+/* $OpenBSD$ */
+/*
+ * Copyright (c) 2016 Ingo Schwarze <[email protected]>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+int     mbsprintf(const char *, ...)
+            __attribute__((format(printf, 1, 2)));
+int     mbsfprintf(FILE *, const char *, ...)
+            __attribute__((format(printf, 2, 3)));
+int     mbsvfprintf(FILE *, const char *, va_list);
+int     mbssnprintf(char *, size_t, int *, const char *, ...)
+            __attribute__((format(printf, 4, 5)));
Index: lib/Makefile
===================================================================
RCS file: /cvs/src/usr.bin/ssh/lib/Makefile,v
retrieving revision 1.84
diff -u -p -r1.84 Makefile
--- lib/Makefile        14 Jan 2016 16:17:40 -0000      1.84
+++ lib/Makefile        15 Mar 2016 15:47:39 -0000
@@ -28,7 +28,7 @@ SRCS= ${LIB_SRCS} \
        cleanup.c compat.c crc32.c deattack.c fatal.c \
        hostfile.c log.c match.c nchan.c packet.c opacket.c readpass.c \
        ttymodes.c xmalloc.c atomicio.c \
-       key.c dispatch.c kex.c mac.c uidswap.c uuencode.c misc.c \
+       key.c dispatch.c kex.c mac.c uidswap.c uuencode.c misc.c utf8.c \
        msg.c progressmeter.c dns.c \
        monitor_fdpass.c addrmatch.c \
        smult_curve25519_ref.c \

Reply via email to