Преглед изворни кода

PuTTY 2017-02-02.f6c1c88

Source commit: e37a13cbd884bb23c6bc357e2f0b432f07e43cb2
Martin Prikryl пре 8 година
родитељ
комит
d895da422f

+ 45 - 17
source/putty/WINDOWS/winsecur.c

@@ -92,17 +92,17 @@ PSID get_user_sid(void)
     return ret;
 }
 
-int getsids(char *error)
+int getsids(char **error)
 {
     SID_IDENTIFIER_AUTHORITY world_auth = SECURITY_WORLD_SID_AUTHORITY;
     SID_IDENTIFIER_AUTHORITY nt_auth = SECURITY_NT_AUTHORITY;
     int ret;
 
-    error=NULL;
+    *error = NULL;
 
     if (!usersid) {
         if ((usersid = get_user_sid()) == NULL) {
-            error = dupprintf("unable to construct SID for current user: %s",
+            *error = dupprintf("unable to construct SID for current user: %s",
                                win_strerror(GetLastError()));
             goto cleanup;
         }
@@ -111,7 +111,7 @@ int getsids(char *error)
     if (!worldsid) {
         if (!AllocateAndInitializeSid(&world_auth, 1, SECURITY_WORLD_RID,
                                       0, 0, 0, 0, 0, 0, 0, &worldsid)) {
-            error = dupprintf("unable to construct SID for world: %s",
+            *error = dupprintf("unable to construct SID for world: %s",
                                win_strerror(GetLastError()));
             goto cleanup;
         }
@@ -120,20 +120,16 @@ int getsids(char *error)
     if (!networksid) {
         if (!AllocateAndInitializeSid(&nt_auth, 1, SECURITY_NETWORK_RID,
                                       0, 0, 0, 0, 0, 0, 0, &networksid)) {
-            error = dupprintf("unable to construct SID for "
+            *error = dupprintf("unable to construct SID for "
                                "local same-user access only: %s",
                                win_strerror(GetLastError()));
             goto cleanup;
         }
     }
 
-    ret=TRUE;
+    ret = TRUE;
 
  cleanup:
-    if (ret) {
-      sfree(error);
-      error = NULL;
-    }
     return ret;
 }
   
@@ -152,7 +148,7 @@ int make_private_security_descriptor(DWORD permissions,
     *acl = NULL;
     *error = NULL;
 
-    if (!getsids(*error))
+    if (!getsids(error))
       goto cleanup;
 
     memset(ea, 0, sizeof(ea));
@@ -224,7 +220,7 @@ int make_private_security_descriptor(DWORD permissions,
     return ret;
 }
 
-int setprocessacl(char *error)
+static int really_restrict_process_acl(char **error)
 {
     EXPLICIT_ACCESS ea[2];
     int acl_err;
@@ -260,8 +256,8 @@ int setprocessacl(char *error)
     acl_err = p_SetEntriesInAclA(2, ea, NULL, &acl);
 
     if (acl_err != ERROR_SUCCESS || acl == NULL) {
-	error = dupprintf("unable to construct ACL: %s",
-			  win_strerror(acl_err));
+	*error = dupprintf("unable to construct ACL: %s",
+                           win_strerror(acl_err));
         goto cleanup;
     }
 
@@ -269,8 +265,8 @@ int setprocessacl(char *error)
         (GetCurrentProcess(), SE_KERNEL_OBJECT,
          OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION,
          usersid, NULL, acl, NULL)) {
-	error=dupprintf("Unable to set process ACL: %s",
-			win_strerror(GetLastError()));
+	*error = dupprintf("Unable to set process ACL: %s",
+                           win_strerror(GetLastError()));
 	goto cleanup;
     }
 		      
@@ -285,5 +281,37 @@ int setprocessacl(char *error)
         }
     }
     return ret;
-}  
+}
 #endif /* !defined NO_SECURITY */
+
+/*
+ * Lock down our process's ACL, to present an obstacle to malware
+ * trying to write into its memory. This can't be a full defence,
+ * because well timed malware could attack us before this code runs -
+ * even if it was unconditionally run at the very start of main(),
+ * which we wouldn't want to do anyway because it turns out in practie
+ * that interfering with other processes in this way has significant
+ * non-infringing uses on Windows (e.g. screen reader software).
+ *
+ * If we've been requested to do this and are unsuccessful, bomb out
+ * via modalfatalbox rather than continue in a less protected mode.
+ *
+ * This function is intentionally outside the #ifndef NO_SECURITY that
+ * covers the rest of this file, because when PuTTY is compiled
+ * without the ability to restrict its ACL, we don't want it to
+ * silently pretend to honour the instruction to do so.
+ */
+void restrict_process_acl(void)
+{
+    char *error = NULL;
+    int ret;
+
+#if !defined NO_SECURITY
+    ret = really_restrict_process_acl(&error);
+#else
+    ret = FALSE;
+    error = dupstr("ACL restrictions not compiled into this binary");
+#endif
+    if (!ret)
+        modalfatalbox("Could not restrict process ACL: %s", error);
+}

+ 0 - 2
source/putty/WINDOWS/winsecur.h

@@ -56,6 +56,4 @@ int make_private_security_descriptor(DWORD permissions,
                                      PACL *acl,
                                      char **error);
 
-int setprocessacl(char *error);
-
 #endif

+ 9 - 0
source/putty/doc/config.but

@@ -934,6 +934,15 @@ setting you want if you have no better ideas.
 \dd PuTTY responds with the actual window title. This is dangerous for
 the reasons described above.
 
+\S{config-features-clearscroll} Disabling remote \i{scrollback clearing}
+
+\cfg{winhelp-topic}{features.clearscroll}
+
+PuTTY has the ability to clear the terminal's scrollback buffer in
+response to a command from the server. If you find PuTTY is doing this
+unexpectedly or inconveniently, you can tell PuTTY not to respond to
+that server command.
+
 \S{config-features-dbackspace} Disabling \i{destructive backspace}
 
 \cfg{winhelp-topic}{features.dbackspace}

+ 1 - 1
source/putty/doc/copy.but

@@ -1,5 +1,5 @@
 \# Generated by licence.pl from LICENCE.
 \# You should edit those files rather than editing this one.
 
-\define{shortcopyrightdetails} 1997-2016 Simon Tatham
+\define{shortcopyrightdetails} 1997-2017 Simon Tatham
 

+ 1 - 1
source/putty/doc/licence.but

@@ -3,7 +3,7 @@
 
 \A{licence} PuTTY \ii{Licence}
 
-PuTTY is \i{copyright} 1997-2016 Simon Tatham.
+PuTTY is \i{copyright} 1997-2017 Simon Tatham.
 
 Portions copyright Robert de Bath, Joris van Rantwijk, Delian Delchev, Andreas Schultz, Jeroen Massar, Wez Furlong, Nicolas Barry, Justin Bradford, Ben Harris, Malcolm Smith, Ahmad Khalifa, Markus Kuhn, Colin Watson, Christopher Staite, and CORE SDI S.A.
 

+ 21 - 0
source/putty/doc/using.but

@@ -1010,3 +1010,24 @@ connection. It expects a shell command string as an argument.
 
 See \k{config-proxy-type} for more information on this, and on other
 proxy settings.
+
+\S2{using-cmdline-restrict-acl} \i\c{-restrict-acl}: restrict the
+Windows process ACL
+
+This option (on Windows only) causes PuTTY to try to lock down the
+operating system's access control on its own process. If this
+succeeds, it should present an extra obstacle to malware that has
+managed to run under the same user id as the PuTTY process, by
+preventing it from attaching to PuTTY using the same interfaces
+debuggers use and either reading sensitive information out of its
+memory or hijacking its network session.
+
+This option is not enabled by default, because this form of
+interaction between Windows programs has many legitimate uses,
+including accessibility software such as screen readers. Also, it
+cannot provide full security against this class of attack in any case,
+because PuTTY can only lock down its own ACL \e{after} it has started
+up, and malware could still get in if it attacks the process between
+startup and lockdown. So it trades away noticeable convenience, and
+delivers less real security than you might want. However, if you do
+want to make that tradeoff anyway, the option is available.

+ 131 - 17
source/putty/misc.c

@@ -394,22 +394,16 @@ int toint(unsigned u)
  *    directive we don't know about, we should panic and die rather
  *    than run any risk.
  */
-char *dupprintf(const char *fmt, ...)
-{
-    char *ret;
-    va_list ap;
-    va_start(ap, fmt);
-    ret = dupvprintf(fmt, ap);
-    va_end(ap);
-    return ret;
-}
-char *dupvprintf(const char *fmt, va_list ap)
+static char *dupvprintf_inner(char *buf, int oldlen, int oldsize,
+                              const char *fmt, va_list ap)
 {
-    char *buf;
     int len, size;
 
-    buf = snewn(512, char);
-    size = 512;
+    size = oldsize - oldlen;
+    if (size == 0) {
+        size = 512;
+        buf = sresize(buf, oldlen + size, char);
+    }
 
     while (1) {
 #if defined _WINDOWS && _MSC_VER < 1900 /* 1900 == VS2015 has real snprintf */
@@ -420,7 +414,7 @@ char *dupvprintf(const char *fmt, va_list ap)
 	 * XXX some environments may have this as __va_copy() */
 	va_list aq;
 	va_copy(aq, ap);
-	len = vsnprintf(buf, size, fmt, aq);
+	len = vsnprintf(buf + oldlen, size, fmt, aq);
 	va_end(aq);
 #else
 	/* Ugh. No va_copy macro, so do something nasty.
@@ -431,7 +425,7 @@ char *dupvprintf(const char *fmt, va_list ap)
 	 * (indeed, it has been observed to).
 	 * XXX the autoconf manual suggests that using memcpy() will give
 	 *     "maximum portability". */
-	len = vsnprintf(buf, size, fmt, ap);
+	len = vsnprintf(buf + oldlen, size, fmt, ap);
 #endif
 	if (len >= 0 && len < size) {
 	    /* This is the C99-specified criterion for snprintf to have
@@ -446,10 +440,65 @@ char *dupvprintf(const char *fmt, va_list ap)
 	     * buffer wasn't big enough, so we enlarge it a bit and hope. */
 	    size += 512;
 	}
-	buf = sresize(buf, size, char);
+	buf = sresize(buf, oldlen + size, char);
     }
 }
 
+char *dupvprintf(const char *fmt, va_list ap)
+{
+    return dupvprintf_inner(NULL, 0, 0, fmt, ap);
+}
+char *dupprintf(const char *fmt, ...)
+{
+    char *ret;
+    va_list ap;
+    va_start(ap, fmt);
+    ret = dupvprintf(fmt, ap);
+    va_end(ap);
+    return ret;
+}
+
+struct strbuf {
+    char *s;
+    int len, size;
+};
+strbuf *strbuf_new(void)
+{
+    strbuf *buf = snew(strbuf);
+    buf->len = 0;
+    buf->size = 512;
+    buf->s = snewn(buf->size, char);
+    *buf->s = '\0';
+    return buf;
+}
+void strbuf_free(strbuf *buf)
+{
+    sfree(buf->s);
+    sfree(buf);
+}
+char *strbuf_str(strbuf *buf)
+{
+    return buf->s;
+}
+char *strbuf_to_str(strbuf *buf)
+{
+    char *ret = buf->s;
+    sfree(buf);
+    return ret;
+}
+void strbuf_catfv(strbuf *buf, const char *fmt, va_list ap)
+{
+    buf->s = dupvprintf_inner(buf->s, buf->len, buf->size, fmt, ap);
+    buf->len += strlen(buf->s + buf->len);
+}
+void strbuf_catf(strbuf *buf, const char *fmt, ...)
+{
+    va_list ap;
+    va_start(ap, fmt);
+    strbuf_catfv(buf, fmt, ap);
+    va_end(ap);
+}
+
 /*
  * Read an entire line of text from a file. Return a buffer
  * malloced to be as big as necessary (caller must free).
@@ -1069,7 +1118,7 @@ void *get_ssh_string(int *datalen, const void **data, int *stringlen)
     if (*datalen < 4)
         return NULL;
     len = GET_32BIT_MSB_FIRST((const unsigned char *)*data);
-    if (*datalen < len+4)
+    if (*datalen - 4 < len)
         return NULL;
     ret = (void *)((const char *)*data + 4);
     *datalen -= len + 4;
@@ -1098,3 +1147,68 @@ int strendswith(const char *s, const char *t)
     size_t slen = strlen(s), tlen = strlen(t);
     return slen >= tlen && !strcmp(s + (slen - tlen), t);
 }
+
+char *buildinfo(const char *newline)
+{
+    strbuf *buf = strbuf_new();
+    extern const char commitid[];      /* in commitid.c */
+
+    strbuf_catf(buf, "Build platform: %d-bit %s",
+                (int)(CHAR_BIT * sizeof(void *)),
+                BUILDINFO_PLATFORM);
+
+#ifdef __clang_version__
+    strbuf_catf(buf, "%sCompiler: clang %s", newline, __clang_version__);
+#elif defined __GNUC__ && defined __VERSION__
+    strbuf_catf(buf, "%sCompiler: gcc %s", newline, __VERSION__);
+#elif defined _MSC_VER
+    strbuf_catf(buf, "%sCompiler: Visual Studio", newline);
+#if _MSC_VER == 1900
+    strbuf_catf(buf, " 2015 / MSVC++ 14.0");
+#elif _MSC_VER == 1800
+    strbuf_catf(buf, " 2013 / MSVC++ 12.0");
+#elif _MSC_VER == 1700
+    strbuf_catf(buf, " 2012 / MSVC++ 11.0");
+#elif _MSC_VER == 1600
+    strbuf_catf(buf, " 2010 / MSVC++ 10.0");
+#elif  _MSC_VER == 1500
+    strbuf_catf(buf, " 2008 / MSVC++ 9.0");
+#elif  _MSC_VER == 1400
+    strbuf_catf(buf, " 2005 / MSVC++ 8.0");
+#elif  _MSC_VER == 1310
+    strbuf_catf(buf, " 2003 / MSVC++ 7.1");
+#else
+    strbuf_catf(buf, ", unrecognised version");
+#endif
+    strbuf_catf(buf, " (_MSC_VER=%d)", (int)_MSC_VER);
+#endif
+
+#ifdef NO_SECURITY
+    strbuf_catf(buf, "%sBuild option: NO_SECURITY", newline);
+#endif
+#ifdef NO_SECUREZEROMEMORY
+    strbuf_catf(buf, "%sBuild option: NO_SECUREZEROMEMORY", newline);
+#endif
+#ifdef NO_IPV6
+    strbuf_catf(buf, "%sBuild option: NO_IPV6", newline);
+#endif
+#ifdef NO_GSSAPI
+    strbuf_catf(buf, "%sBuild option: NO_GSSAPI", newline);
+#endif
+#ifdef STATIC_GSSAPI
+    strbuf_catf(buf, "%sBuild option: STATIC_GSSAPI", newline);
+#endif
+#ifdef UNPROTECT
+    strbuf_catf(buf, "%sBuild option: UNPROTECT", newline);
+#endif
+#ifdef FUZZING
+    strbuf_catf(buf, "%sBuild option: FUZZING", newline);
+#endif
+#ifdef DEBUG
+    strbuf_catf(buf, "%sBuild option: DEBUG", newline);
+#endif
+
+    strbuf_catf(buf, "%sSource commit: %s", newline, commitid);
+
+    return strbuf_to_str(buf);
+}

+ 9 - 0
source/putty/misc.h

@@ -38,6 +38,13 @@ char *dupprintf(const char *fmt, ...)
     ;
 char *dupvprintf(const char *fmt, va_list ap);
 void burnstr(char *string);
+typedef struct strbuf strbuf;
+strbuf *strbuf_new(void);
+void strbuf_free(strbuf *buf);
+char *strbuf_str(strbuf *buf);         /* does not free buf */
+char *strbuf_to_str(strbuf *buf); /* does free buf, but you must free result */
+void strbuf_catf(strbuf *buf, const char *fmt, ...);
+void strbuf_catfv(strbuf *buf, const char *fmt, va_list ap);
 
 /* String-to-Unicode converters that auto-allocate the destination and
  * work around the rather deficient interface of mb_to_wc.
@@ -111,6 +118,8 @@ int get_ssh_uint32(int *datalen, const void **data, unsigned *ret);
  * form, check if it equals an ordinary C zero-terminated string. */
 int match_ssh_id(int stringlen, const void *string, const char *id);
 
+char *buildinfo(const char *newline);
+
 /*
  * Debugging functions.
  *

+ 21 - 5
source/putty/putty.h

@@ -770,6 +770,7 @@ void cleanup_exit(int);
     X(INT, NONE, no_remote_resize) /* disable remote resizing */ \
     X(INT, NONE, no_alt_screen) /* disable alternate screen */ \
     X(INT, NONE, no_remote_wintitle) /* disable remote retitling */ \
+    X(INT, NONE, no_remote_clearscroll) /* disable ESC[3J */ \
     X(INT, NONE, no_dbackspace) /* disable destructive backspace */ \
     X(INT, NONE, no_remote_charset) /* disable remote charset config */ \
     X(INT, NONE, remote_qtitle_action) /* remote win title query action */ \
@@ -1199,17 +1200,32 @@ void crypto_wrapup();
 /*
  * Exports from pageantc.c.
  * 
- * agent_query returns 1 for here's-a-response, and 0 for query-in-
- * progress. In the latter case there will be a call to `callback'
- * at some future point, passing callback_ctx as the first
+ * agent_query returns NULL for here's-a-response, and non-NULL for
+ * query-in- progress. In the latter case there will be a call to
+ * `callback' at some future point, passing callback_ctx as the first
  * parameter and the actual reply data as the second and third.
  * 
  * The response may be a NULL pointer (in either of the synchronous
  * or asynchronous cases), which indicates failure to receive a
  * response.
+ *
+ * When the return from agent_query is not NULL, it identifies the
+ * in-progress query in case it needs to be cancelled. If
+ * agent_cancel_query is called, then the pending query is destroyed
+ * and the callback will not be called. (E.g. if you're going to throw
+ * away the thing you were using as callback_ctx.)
+ *
+ * Passing a null pointer as callback forces agent_query to behave
+ * synchronously, i.e. it will block if necessary, and guarantee to
+ * return NULL. The wrapper function agent_query_synchronous() makes
+ * this easier.
  */
-int agent_query(void *in, int inlen, void **out, int *outlen,
-		void (*callback)(void *, void *, int), void *callback_ctx);
+typedef struct agent_pending_query agent_pending_query;
+agent_pending_query *agent_query(
+    void *in, int inlen, void **out, int *outlen,
+    void (*callback)(void *, void *, int), void *callback_ctx);
+void agent_cancel_query(agent_pending_query *);
+void agent_query_synchronous(void *in, int inlen, void **out, int *outlen);
 int agent_exists(void);
 
 /*

+ 193 - 74
source/putty/ssh.c

@@ -10,6 +10,7 @@
 #include <signal.h>
 
 #include "putty.h"
+#include "pageant.h" /* for AGENT_MAX_MSGLEN */
 #include "tree234.h"
 #include "storage.h"
 #include "ssh.h"
@@ -573,10 +574,8 @@ struct ssh_channel {
     } v;
     union {
 	struct ssh_agent_channel {
-	    unsigned char *message;
-	    unsigned char msglen[4];
-	    unsigned lensofar, totallen;
-            int outstanding_requests;
+            bufchain inbuffer;
+            agent_pending_query *pending;
 	} a;
 	struct ssh_x11_channel {
 	    struct X11Connection *xconn;
@@ -985,6 +984,13 @@ struct ssh_tag {
      * with a newly cross-certified host key.
      */
     int cross_certifying;
+
+    /*
+     * Any asynchronous query to our SSH agent that we might have in
+     * flight from the main authentication loop. (Queries from
+     * agent-forwarding channels live in their channel structure.)
+     */
+    agent_pending_query *auth_agent_query;
 };
 
 static const char *ssh_pkt_type(Ssh ssh, int type)
@@ -3772,6 +3778,8 @@ static void ssh_throttle_conn(Ssh ssh, int adjust)
     }
 }
 
+static void ssh_agentf_try_forward(struct ssh_channel *c);
+
 /*
  * Throttle or unthrottle _all_ local data streams (for when sends
  * on the SSH connection itself back up).
@@ -3798,7 +3806,12 @@ static void ssh_throttle_all(Ssh ssh, int enable, int bufsize)
 	    x11_override_throttle(c->u.x11.xconn, enable);
 	    break;
 	  case CHAN_AGENT:
-	    /* Agent channels require no buffer management. */
+	    /* Agent forwarding channels are buffer-managed by
+             * checking ssh->throttled_all in ssh_agentf_try_forward.
+             * So at the moment we _un_throttle again, we must make an
+             * attempt to do something. */
+            if (!enable)
+                ssh_agentf_try_forward(c);
 	    break;
 	  case CHAN_SOCKDATA:
 	    pfd_override_throttle(c->u.pfd.pf, enable);
@@ -3811,6 +3824,8 @@ static void ssh_agent_callback(void *sshv, void *reply, int replylen)
 {
     Ssh ssh = (Ssh) sshv;
 
+    ssh->auth_agent_query = NULL;
+
     ssh->agent_response = reply;
     ssh->agent_response_len = replylen;
 
@@ -3838,28 +3853,139 @@ static void ssh_dialog_callback(void *sshv, int ret)
     ssh_process_queued_incoming_data(ssh);
 }
 
-static void ssh_agentf_callback(void *cv, void *reply, int replylen)
+static void ssh_agentf_got_response(struct ssh_channel *c,
+                                    void *reply, int replylen)
 {
-    struct ssh_channel *c = (struct ssh_channel *)cv;
-    const void *sentreply = reply;
+    c->u.a.pending = NULL;
 
-    c->u.a.outstanding_requests--;
-    if (!sentreply) {
-	/* Fake SSH_AGENT_FAILURE. */
-	sentreply = "\0\0\0\1\5";
+    assert(!(c->closes & CLOSES_SENT_EOF));
+
+    if (!reply) {
+	/* The real agent didn't send any kind of reply at all for
+         * some reason, so fake an SSH_AGENT_FAILURE. */
+	reply = "\0\0\0\1\5";
 	replylen = 5;
     }
-    ssh_send_channel_data(c, sentreply, replylen);
-    if (reply)
-	sfree(reply);
+
+    ssh_send_channel_data(c, reply, replylen);
+}
+
+static void ssh_agentf_callback(void *cv, void *reply, int replylen);
+
+static void ssh_agentf_try_forward(struct ssh_channel *c)
+{
+    unsigned datalen, lengthfield, messagelen;
+    unsigned char *message;
+    unsigned char msglen[4];
+    void *reply;
+    int replylen;
+
+    /*
+     * Don't try to parallelise agent requests. Wait for each one to
+     * return before attempting the next.
+     */
+    if (c->u.a.pending)
+        return;
+
+    /*
+     * If the outgoing side of the channel connection is currently
+     * throttled (for any reason, either that channel's window size or
+     * the entire SSH connection being throttled), don't submit any
+     * new forwarded requests to the real agent. This causes the input
+     * side of the agent forwarding not to be emptied, exerting the
+     * required back-pressure on the remote client, and encouraging it
+     * to read our responses before sending too many more requests.
+     */
+    if (c->ssh->throttled_all ||
+        (c->ssh->version == 2 && c->v.v2.remwindow == 0))
+        return;
+
+    if (c->closes & CLOSES_SENT_EOF) {
+        /*
+         * If we've already sent outgoing EOF, there's nothing we can
+         * do with incoming data except consume it and throw it away.
+         */
+        bufchain_clear(&c->u.a.inbuffer);
+        return;
+    }
+
+    while (1) {
+        /*
+         * Try to extract a complete message from the input buffer.
+         */
+        datalen = bufchain_size(&c->u.a.inbuffer);
+        if (datalen < 4)
+            break;         /* not even a length field available yet */
+
+        bufchain_fetch(&c->u.a.inbuffer, msglen, 4);
+        lengthfield = GET_32BIT(msglen);
+
+        if (lengthfield > AGENT_MAX_MSGLEN) {
+            /*
+             * If the remote has sent a message that's just _too_
+             * long, we should reject it in advance of seeing the rest
+             * of the incoming message, and also close the connection
+             * for good measure (which avoids us having to faff about
+             * with carefully ignoring just the right number of bytes
+             * from the overlong message).
+             */
+            ssh_agentf_got_response(c, NULL, 0);
+            sshfwd_write_eof(c);
+            return;
+        }
+
+        if (lengthfield > datalen - 4)
+            break;          /* a whole message is not yet available */
+
+        messagelen = lengthfield + 4;
+
+        message = snewn(messagelen, unsigned char);
+        bufchain_fetch(&c->u.a.inbuffer, message, messagelen);
+        bufchain_consume(&c->u.a.inbuffer, messagelen);
+        c->u.a.pending = agent_query(
+            message, messagelen, &reply, &replylen, ssh_agentf_callback, c);
+        sfree(message);
+
+        if (c->u.a.pending)
+            return;   /* agent_query promised to reply in due course */
+
+        /*
+         * If the agent gave us an answer immediately, pass it
+         * straight on and go round this loop again.
+         */
+        ssh_agentf_got_response(c, reply, replylen);
+    }
+
     /*
-     * If we've already seen an incoming EOF but haven't sent an
-     * outgoing one, this may be the moment to send it.
+     * If we get here (i.e. we left the above while loop via 'break'
+     * rather than 'return'), that means we've determined that the
+     * input buffer for the agent forwarding connection doesn't
+     * contain a complete request.
+     *
+     * So if there's potentially more data to come, we can return now,
+     * and wait for the remote client to send it. But if the remote
+     * has sent EOF, it would be a mistake to do that, because we'd be
+     * waiting a long time. So this is the moment to check for EOF,
+     * and respond appropriately.
      */
-    if (c->u.a.outstanding_requests == 0 && (c->closes & CLOSES_RCVD_EOF))
+    if (c->closes & CLOSES_RCVD_EOF)
         sshfwd_write_eof(c);
 }
 
+static void ssh_agentf_callback(void *cv, void *reply, int replylen)
+{
+    struct ssh_channel *c = (struct ssh_channel *)cv;
+
+    ssh_agentf_got_response(c, reply, replylen);
+    sfree(reply);
+
+    /*
+     * Now try to extract and send further messages from the channel's
+     * input-side buffer.
+     */
+    ssh_agentf_try_forward(c);
+}
+
 /*
  * Client-initiated disconnection. Send a DISCONNECT if `wire_reason'
  * non-NULL, otherwise just close the connection. `client_reason' == NULL
@@ -4362,8 +4488,9 @@ static int do_ssh1_login(Ssh ssh, const unsigned char *in, int inlen,
 	    /* Request the keys held by the agent. */
 	    PUT_32BIT(s->request, 1);
 	    s->request[4] = SSH1_AGENTC_REQUEST_RSA_IDENTITIES;
-	    if (!agent_query(s->request, 5, &r, &s->responselen,
-			     ssh_agent_callback, ssh)) {
+            ssh->auth_agent_query = agent_query(
+                s->request, 5, &r, &s->responselen, ssh_agent_callback, ssh);
+	    if (ssh->auth_agent_query) {
 		do {
 		    crReturn(0);
 		    if (pktin) {
@@ -4468,8 +4595,10 @@ static int do_ssh1_login(Ssh ssh, const unsigned char *in, int inlen,
 			memcpy(q, s->session_id, 16);
 			q += 16;
 			PUT_32BIT(q, 1);	/* response format */
-			if (!agent_query(agentreq, len + 4, &vret, &retlen,
-					 ssh_agent_callback, ssh)) {
+                        ssh->auth_agent_query = agent_query(
+                            agentreq, len + 4, &vret, &retlen,
+                            ssh_agent_callback, ssh);
+			if (ssh->auth_agent_query) {
 			    sfree(agentreq);
 			    do {
 				crReturn(0);
@@ -5539,9 +5668,8 @@ static void ssh1_smsg_agent_open(Ssh ssh, struct Packet *pktin)
 	c->remoteid = remoteid;
 	c->halfopen = FALSE;
 	c->type = CHAN_AGENT;	/* identify channel type */
-	c->u.a.lensofar = 0;
-	c->u.a.message = NULL;
-	c->u.a.outstanding_requests = 0;
+	c->u.a.pending = NULL;
+        bufchain_init(&c->u.a.inbuffer);
 	send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION,
 		    PKT_INT, c->remoteid, PKT_INT, c->localid,
 		    PKT_END);
@@ -5682,40 +5810,18 @@ static void ssh1_msg_channel_close(Ssh ssh, struct Packet *pktin)
 static int ssh_agent_channel_data(struct ssh_channel *c, char *data,
 				  int length)
 {
-    while (length > 0) {
-	if (c->u.a.lensofar < 4) {
-	    unsigned int l = min(4 - c->u.a.lensofar, (unsigned)length);
-	    memcpy(c->u.a.msglen + c->u.a.lensofar, data, l);
-	    data += l;
-	    length -= l;
-	    c->u.a.lensofar += l;
-	}
-	if (c->u.a.lensofar == 4) {
-	    c->u.a.totallen = 4 + GET_32BIT(c->u.a.msglen);
-	    c->u.a.message = snewn(c->u.a.totallen, unsigned char);
-	    memcpy(c->u.a.message, c->u.a.msglen, 4);
-	}
-	if (c->u.a.lensofar >= 4 && length > 0) {
-	    unsigned int l = min(c->u.a.totallen - c->u.a.lensofar,
-				 (unsigned)length);
-	    memcpy(c->u.a.message + c->u.a.lensofar, data, l);
-	    data += l;
-	    length -= l;
-	    c->u.a.lensofar += l;
-	}
-	if (c->u.a.lensofar == c->u.a.totallen) {
-	    void *reply;
-	    int replylen;
-            c->u.a.outstanding_requests++;
-	    if (agent_query(c->u.a.message, c->u.a.totallen, &reply, &replylen,
-			    ssh_agentf_callback, c))
-		ssh_agentf_callback(c, reply, replylen);
-	    sfree(c->u.a.message);
-            c->u.a.message = NULL;
-	    c->u.a.lensofar = 0;
-	}
-    }
-    return 0;   /* agent channels never back up */
+    bufchain_add(&c->u.a.inbuffer, data, length);
+    ssh_agentf_try_forward(c);
+
+    /*
+     * We exert back-pressure on an agent forwarding client if and
+     * only if we're waiting for the response to an asynchronous agent
+     * request. This prevents the client running out of window while
+     * receiving the _first_ message, but means that if any message
+     * takes time to process, the client will be discouraged from
+     * sending an endless stream of further ones after it.
+     */
+    return (c->u.a.pending ? bufchain_size(&c->u.a.inbuffer) : 0);
 }
 
 static int ssh_channel_data(struct ssh_channel *c, int is_stderr,
@@ -7323,6 +7429,7 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen,
         s->fingerprint = ssh2_fingerprint(ssh->hostkey, s->hkey);
         logevent("Storing additional host key for this host:");
         logevent(s->fingerprint);
+        sfree(s->fingerprint);
         store_host_key(ssh->savedhost, ssh->savedport,
                        ssh->hostkey->keytype, s->keystr);
         ssh->cross_certifying = FALSE;
@@ -7715,8 +7822,9 @@ static void ssh2_try_send_and_unthrottle(Ssh ssh, struct ssh_channel *c)
 	    x11_unthrottle(c->u.x11.xconn);
 	    break;
 	  case CHAN_AGENT:
-	    /* agent sockets are request/response and need no
-	     * buffer management */
+            /* Now that we've successfully sent all the outgoing
+             * replies we had, try to process more incoming data. */
+            ssh_agentf_try_forward(c);
 	    break;
 	  case CHAN_SOCKDATA:
 	    pfd_unthrottle(c->u.pfd.pf);
@@ -8140,7 +8248,10 @@ static void ssh_channel_close_local(struct ssh_channel *c, char const *reason)
         msg = "Forwarded X11 connection terminated";
         break;
       case CHAN_AGENT:
-        sfree(c->u.a.message);
+        if (c->u.a.pending)
+            agent_cancel_query(c->u.a.pending);
+        bufchain_clear(&c->u.a.inbuffer);
+	msg = "Agent-forwarding connection closed";
         break;
       case CHAN_SOCKDATA:
         assert(c->u.pfd.pf != NULL);
@@ -8228,10 +8339,10 @@ static void ssh_channel_got_eof(struct ssh_channel *c)
 	assert(c->u.x11.xconn != NULL);
 	x11_send_eof(c->u.x11.xconn);
     } else if (c->type == CHAN_AGENT) {
-        if (c->u.a.outstanding_requests == 0) {
-            /* Manufacture an outgoing EOF in response to the incoming one. */
-            sshfwd_write_eof(c);
-        }
+        /* Just call try_forward, which will respond to the EOF now if
+         * appropriate, or wait until the queue of outstanding
+         * requests is dealt with if not */
+        ssh_agentf_try_forward(c);
     } else if (c->type == CHAN_SOCKDATA) {
 	assert(c->u.pfd.pf != NULL);
 	pfd_send_eof(c->u.pfd.pf);
@@ -8785,9 +8896,8 @@ static void ssh2_msg_channel_open(Ssh ssh, struct Packet *pktin)
 	    error = "Agent forwarding is not enabled";
 	else {
 	    c->type = CHAN_AGENT;	/* identify channel type */
-	    c->u.a.lensofar = 0;
-            c->u.a.message = NULL;
-            c->u.a.outstanding_requests = 0;
+            bufchain_init(&c->u.a.inbuffer);
+            c->u.a.pending = NULL;
 	}
     } else {
 	error = "Unsupported channel type requested";
@@ -9290,8 +9400,10 @@ static void do_ssh2_authconn(Ssh ssh, const unsigned char *in, int inlen,
 	    /* Request the keys held by the agent. */
 	    PUT_32BIT(s->agent_request, 1);
 	    s->agent_request[4] = SSH2_AGENTC_REQUEST_IDENTITIES;
-	    if (!agent_query(s->agent_request, 5, &r, &s->agent_responselen,
-			     ssh_agent_callback, ssh)) {
+            ssh->auth_agent_query = agent_query(
+                s->agent_request, 5, &r, &s->agent_responselen,
+                ssh_agent_callback, ssh);
+	    if (ssh->auth_agent_query) {
 		do {
 		    crReturnV;
 		    if (pktin) {
@@ -9729,9 +9841,10 @@ static void do_ssh2_authconn(Ssh ssh, const unsigned char *in, int inlen,
 		    s->q += s->pktout->length - 5;
 		    /* And finally the (zero) flags word. */
 		    PUT_32BIT(s->q, 0);
-		    if (!agent_query(s->agentreq, s->len + 4,
-				     &vret, &s->retlen,
-				     ssh_agent_callback, ssh)) {
+                    ssh->auth_agent_query = agent_query(
+                        s->agentreq, s->len + 4, &vret, &s->retlen,
+                        ssh_agent_callback, ssh);
+                    if (ssh->auth_agent_query) {
 			do {
 			    crReturnV;
 			    if (pktin) {
@@ -11167,6 +11280,8 @@ static const char *ssh_init(void *frontend_handle, void **backend_handle,
 						      CONF_ssh_rekey_data));
     ssh->kex_in_progress = FALSE;
 
+    ssh->auth_agent_query = NULL;
+
 #ifndef NO_GSSAPI
     ssh->gsslibs = NULL;
 #endif
@@ -11282,6 +11397,10 @@ static void ssh_free(void *handle)
     bufchain_clear(&ssh->queued_incoming_data);
     sfree(ssh->username);
     conf_free(ssh->conf);
+
+    if (ssh->auth_agent_query)
+        agent_cancel_query(ssh->auth_agent_query);
+
 #ifndef NO_GSSAPI
     if (ssh->gsslibs)
 	ssh_gss_cleanup(ssh->gsslibs);

+ 1 - 0
source/putty/sshbn.c

@@ -1461,6 +1461,7 @@ Bignum bignum_from_decimal(const char *decimal)
 
         tmp = bigmul(result, Ten);
         tmp2 = bignum_from_long(*decimal - '0');
+        freebn(result);
         result = bigadd(tmp, tmp2);
         freebn(tmp);
         freebn(tmp2);

+ 3 - 3
source/putty/sshecc.c

@@ -2029,10 +2029,10 @@ static void *ed25519_openssh_createkey(const struct ssh_signkey *self,
     }
 
     getstring((const char**)blob, len, &q, &qlen);
-    if (!q)
-        return NULL;
-    if (qlen != 64)
+    if (!q || qlen != 64) {
+        ecdsa_freekey(ec);
         return NULL;
+    }
 
     ec->privateKey = bignum_from_bytes_le((const unsigned char *)q, 32);
 

+ 3 - 0
source/putty/sshpubk.c

@@ -309,6 +309,8 @@ int rsakey_pubblob(const Filename *filename, void **blob, int *bloblen,
             *commentptr = commentp ? dupstr(commentp) : NULL;
         *blob = rsa_public_blob(&key, bloblen);
         freersakey(&key);
+        sfree(line);
+        fclose(fp);
         return 1;
 
       not_public_either:
@@ -1089,6 +1091,7 @@ unsigned char *openssh_loadpub(FILE *fp, char **algorithm,
         *commentptr = comment;
     else
         sfree(comment);
+    sfree(line);
     return pubblob;
 
   error:

+ 1 - 1
source/putty/sshrand.c

@@ -240,7 +240,7 @@ void random_add_noise(void *noise, int length)
 	length -= HASHINPUT - pool.incomingpos;
 	SHATransform((word32 *) pool.incoming, (word32 *) pool.incomingb);
 	for (i = 0; i < HASHSIZE; i++) {
-	    pool.pool[pool.poolpos++] ^= pool.incomingb[i];
+	    pool.pool[pool.poolpos++] ^= pool.incoming[i];
 	    if (pool.poolpos >= POOLSIZE)
 		pool.poolpos = 0;
 	}

+ 4 - 3
source/putty/version.h

@@ -1,5 +1,6 @@
 /* Generated by automated build script */
 #define SNAPSHOT
-#define TEXTVER "Development snapshot 2016-07-19.9398d23"
-#define SSHVER "PuTTY-Snapshot-2016-07-19.9398d23"
-#define BINARY_VERSION 0,67,1141,0
+#define TEXTVER "Development snapshot 2017-02-02.f6c1c88"
+#define SSHVER "PuTTY-Snapshot-2017-02-02.f6c1c88"
+#define BINARY_VERSION 0,67,1339,0
+#define SOURCE_COMMIT "f6c1c8819b5d90a97124b62ee07b0e06d6bbb6c3"

+ 44 - 6
source/putty/windows/winnet.c

@@ -704,6 +704,35 @@ void sk_getaddr(SockAddr addr, char *buf, int buflen)
     }
 }
 
+/*
+ * This constructs a SockAddr that points at one specific sub-address
+ * of a parent SockAddr. The returned SockAddr does not own all its
+ * own memory: it points into the old one's data structures, so it
+ * MUST NOT be used after the old one is freed, and it MUST NOT be
+ * passed to sk_addr_free. (The latter is why it's returned by value
+ * rather than dynamically allocated - that should clue in anyone
+ * writing a call to it that something is weird about it.)
+ */
+static struct SockAddr_tag sk_extractaddr_tmp(
+    SockAddr addr, const SockAddrStep *step)
+{
+    struct SockAddr_tag toret;
+    toret = *addr;                    /* structure copy */
+    toret.refcount = 1;
+
+#ifndef NO_IPV6
+    toret.ais = step->ai;
+#endif
+    if (SOCKADDR_FAMILY(addr, *step) == AF_INET
+#ifndef NO_IPV6
+        && !toret.ais
+#endif
+        )
+        toret.addresses += step->curraddr;
+
+    return toret;
+}
+
 int sk_addr_needs_port(SockAddr addr)
 {
     return addr->namedpipe ? FALSE : TRUE;
@@ -947,7 +976,11 @@ static DWORD try_connect(Actual_Socket sock)
         p_closesocket(sock->s);
     }
 
-    plug_log(sock->plug, 0, sock->addr, sock->port, NULL, 0);
+    {
+        struct SockAddr_tag thisaddr = sk_extractaddr_tmp(
+            sock->addr, &sock->step);
+        plug_log(sock->plug, 0, &thisaddr, sock->port, NULL, 0);
+    }
 
     /*
      * Open socket.
@@ -1114,8 +1147,11 @@ static DWORD try_connect(Actual_Socket sock)
      */
     add234(sktree, sock);
 
-    if (err)
-	plug_log(sock->plug, 1, sock->addr, sock->port, sock->error, err);
+    if (err) {
+        struct SockAddr_tag thisaddr = sk_extractaddr_tmp(
+            sock->addr, &sock->step);
+	plug_log(sock->plug, 1, &thisaddr, sock->port, sock->error, err);
+    }
     return err;
 }
 
@@ -1578,9 +1614,11 @@ int select_result(WPARAM wParam, LPARAM lParam)
 	 * plug.
 	 */
 	if (s->addr) {
-	    plug_log(s->plug, 1, s->addr, s->port,
+            struct SockAddr_tag thisaddr = sk_extractaddr_tmp(
+                s->addr, &s->step);
+	    plug_log(s->plug, 1, &thisaddr, s->port,
 		     winsock_error_string(err), err);
-	    while (s->addr && sk_nextaddr(s->addr, &s->step)) {
+	    while (err && s->addr && sk_nextaddr(s->addr, &s->step)) {
 		err = try_connect(s);
 	    }
 	}
@@ -1761,9 +1799,9 @@ static char *sk_tcp_peer_info(Socket sock)
     struct sockaddr_in addr;
 #else
     struct sockaddr_storage addr;
+    char buf[INET6_ADDRSTRLEN];
 #endif
     int addrlen = sizeof(addr);
-    char buf[INET6_ADDRSTRLEN];
 
     if (p_getpeername(s->s, (struct sockaddr *)&addr, &addrlen) < 0)
         return NULL;

+ 10 - 74
source/putty/windows/winpgntc.c

@@ -4,15 +4,16 @@
 
 #include <stdio.h>
 #include <stdlib.h>
+#include <assert.h>
 
 #include "putty.h"
+#include "pageant.h" /* for AGENT_MAX_MSGLEN */
 
 #ifndef NO_SECURITY
 #include "winsecur.h"
 #endif
 
 #define AGENT_COPYDATA_ID 0x804e50ba   /* random goop */
-#define AGENT_MAX_MSGLEN  8192
 
 int agent_exists(void)
 {
@@ -24,54 +25,14 @@ int agent_exists(void)
 	return TRUE;
 }
 
-/*
- * Unfortunately, this asynchronous agent request mechanism doesn't
- * appear to work terribly well. I'm going to comment it out for
- * the moment, and see if I can come up with a better one :-/
- */
-#ifdef WINDOWS_ASYNC_AGENT
-
-struct agent_query_data {
-    COPYDATASTRUCT cds;
-    unsigned char *mapping;
-    HANDLE handle;
-    char *mapname;
-    HWND hwnd;
-    void (*callback)(void *, void *, int);
-    void *callback_ctx;
-};
-
-DWORD WINAPI agent_query_thread(LPVOID param)
+void agent_cancel_query(agent_pending_query *q)
 {
-    struct agent_query_data *data = (struct agent_query_data *)param;
-    unsigned char *ret;
-    int id, retlen;
-
-    id = SendMessage(data->hwnd, WM_COPYDATA, (WPARAM) NULL,
-		     (LPARAM) &data->cds);
-    ret = NULL;
-    if (id > 0) {
-	retlen = 4 + GET_32BIT(data->mapping);
-	ret = snewn(retlen, unsigned char);
-	if (ret) {
-	    memcpy(ret, data->mapping, retlen);
-	}
-    }
-    if (!ret)
-	retlen = 0;
-    UnmapViewOfFile(data->mapping);
-    CloseHandle(data->handle);
-    sfree(data->mapname);
-
-    agent_schedule_callback(data->callback, data->callback_ctx, ret, retlen);
-
-    return 0;
+    assert(0 && "Windows agent queries are never asynchronous!");
 }
 
-#endif
-
-int agent_query(void *in, int inlen, void **out, int *outlen,
-		void (*callback)(void *, void *, int), void *callback_ctx)
+agent_pending_query *agent_query(
+    void *in, int inlen, void **out, int *outlen,
+    void (*callback)(void *, void *, int), void *callback_ctx)
 {
     HWND hwnd;
     char *mapname;
@@ -88,7 +49,7 @@ int agent_query(void *in, int inlen, void **out, int *outlen,
 
     hwnd = FindWindow("Pageant", "Pageant");
     if (!hwnd)
-	return 1;		       /* *out == NULL, so failure */
+	return NULL;		       /* *out == NULL, so failure */
     mapname = dupprintf("PageantRequest%08x", (unsigned)GetCurrentThreadId());
 
     psa = NULL;
@@ -129,38 +90,13 @@ int agent_query(void *in, int inlen, void **out, int *outlen,
 				0, AGENT_MAX_MSGLEN, mapname);
     if (filemap == NULL || filemap == INVALID_HANDLE_VALUE) {
         sfree(mapname);
-	return 1;		       /* *out == NULL, so failure */
+	return NULL;		       /* *out == NULL, so failure */
     }
     p = MapViewOfFile(filemap, FILE_MAP_WRITE, 0, 0, 0);
     memcpy(p, in, inlen);
     cds.dwData = AGENT_COPYDATA_ID;
     cds.cbData = 1 + strlen(mapname);
     cds.lpData = mapname;
-#ifdef WINDOWS_ASYNC_AGENT
-    if (callback != NULL && !(flags & FLAG_SYNCAGENT)) {
-	/*
-	 * We need an asynchronous Pageant request. Since I know of
-	 * no way to stop SendMessage from blocking the thread it's
-	 * called in, I see no option but to start a fresh thread.
-	 * When we're done we'll PostMessage the result back to our
-	 * main window, so that the callback is done in the primary
-	 * thread to avoid concurrency.
-	 */
-	struct agent_query_data *data = snew(struct agent_query_data);
-	DWORD threadid;
-	data->mapping = p;
-	data->handle = filemap;
-	data->mapname = mapname;
-	data->callback = callback;
-	data->callback_ctx = callback_ctx;
-	data->cds = cds;	       /* structure copy */
-	data->hwnd = hwnd;
-	if (CreateThread(NULL, 0, agent_query_thread, data, 0, &threadid))
-	    return 0;
-	sfree(mapname);
-	sfree(data);
-    }
-#endif
 
     /*
      * The user either passed a null callback (indicating that the
@@ -182,5 +118,5 @@ int agent_query(void *in, int inlen, void **out, int *outlen,
     sfree(mapname);
     if (psd)
         LocalFree(psd);
-    return 1;
+    return NULL;
 }

+ 4 - 0
source/putty/windows/winstuff.h

@@ -27,6 +27,8 @@
 
 #include "winhelp.h"
 
+#define BUILDINFO_PLATFORM "Windows"
+
 struct Filename {
     char *path;
 };
@@ -482,6 +484,7 @@ void dll_hijacking_protection(void);
 BOOL init_winver(void);
 HMODULE load_system32_dll(const char *libname);
 const char *win_strerror(int error);
+void restrict_process_acl(void);
 
 /*
  * Exports from sizetip.c.
@@ -543,6 +546,7 @@ extern Backend serial_backend;
 void add_session_to_jumplist(const char * const sessionname);
 void remove_session_from_jumplist(const char * const sessionname);
 void clear_jumplist(void);
+BOOL set_explicit_app_user_model_id();
 
 /*
  * Extra functions in winstore.c over and above the interface in