[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Bug#924904: unblock: putty/0.70-6



Package: release.debian.org
Severity: normal
User: release.debian.org@packages.debian.org
Usertags: unblock

The EU recently funded a bug bounty for PuTTY, and PuTTY 0.71 was
released over the weekend including a large number of security fixes
many of which were found by that.  Since this is too late for buster,
the upstream maintainer kindly sent me a backported patch series which
he recommended that we apply to 0.70, and I uploaded that to unstable
yesterday.  I think we should have this in buster, so please unblock.

(When I last asked, no CVEs had been allocated for any of this yet.)

unblock putty/0.70-6

Thanks,

-- 
Colin Watson                                       [cjwatson@debian.org]
diff -Nru putty-0.70/debian/.git-dpm putty-0.70/debian/.git-dpm
--- putty-0.70/debian/.git-dpm	2018-10-28 17:18:52.000000000 +0000
+++ putty-0.70/debian/.git-dpm	2019-03-17 09:36:53.000000000 +0000
@@ -1,6 +1,6 @@
 # see git-dpm(1) from git-dpm package
-694018afd4da9c7e00c7247c275e44b3aab49d4b
-694018afd4da9c7e00c7247c275e44b3aab49d4b
+1ebfc3bc04d0bbde174da1999a922b491a0e90dd
+1ebfc3bc04d0bbde174da1999a922b491a0e90dd
 8d3b8df5deee84238c92dfa4b4c4e3a787d73b64
 8d3b8df5deee84238c92dfa4b4c4e3a787d73b64
 putty_0.70.orig.tar.gz
diff -Nru putty-0.70/debian/changelog putty-0.70/debian/changelog
--- putty-0.70/debian/changelog	2018-10-28 18:07:45.000000000 +0000
+++ putty-0.70/debian/changelog	2019-03-17 09:37:02.000000000 +0000
@@ -1,3 +1,22 @@
+putty (0.70-6) unstable; urgency=high
+
+  * Apply security patch series from upstream:
+    - New facility for removing pending toplevel callbacks.
+    - Fix one-byte buffer overrun in random_add_noise().
+    - uxnet: clean up callbacks when closing a NetSocket.
+    - sk_tcp_close: fix memory leak of output bufchain.
+    - Fix handling of bad RSA key with n=p=q=0.
+    - Sanity-check the 'Public-Lines' field in ppk files.
+    - Introduce an enum of the uxsel / select_result flags.
+    - Switch to using poll(2) in place of select(2).
+    - RSA kex: enforce the minimum key length.
+    - Fix crash on ESC#6 + combining chars + GTK + odd-width terminal.
+    - Limit the number of combining chars per terminal cell.
+    - minibidi: fix read past end of line in rule W5.
+    - Fix crash printing a width-2 char in a width-1 terminal.
+
+ -- Colin Watson <cjwatson@debian.org>  Sun, 17 Mar 2019 09:37:02 +0000
+
 putty (0.70-5) unstable; urgency=medium
 
   [ Colin Watson ]
diff -Nru putty-0.70/debian/patches/fix-bad-rsa-key-handling.patch putty-0.70/debian/patches/fix-bad-rsa-key-handling.patch
--- putty-0.70/debian/patches/fix-bad-rsa-key-handling.patch	1970-01-01 01:00:00.000000000 +0100
+++ putty-0.70/debian/patches/fix-bad-rsa-key-handling.patch	2019-03-17 09:36:52.000000000 +0000
@@ -0,0 +1,48 @@
+From 475366539d4bf768567b635782c577cdfde40026 Mon Sep 17 00:00:00 2001
+From: Simon Tatham <anakin@pobox.com>
+Date: Wed, 6 Feb 2019 21:09:29 +0000
+Subject: Fix handling of bad RSA key with n=p=q=0.
+
+In this situation, rsa_verify won't notice anything wrong until it
+gets to the point where decbn() tries to subtract 1 from p, and
+underruns the Bignum buffer.
+
+Just in case some other attack vector reaches that same problem point,
+I've also put a protective assertion in decbn() itself just before the
+memory overwrite would have happened.
+
+Last-Update: 2019-03-16
+
+Patch-Name: fix-bad-rsa-key-handling.patch
+---
+ sshbn.c  | 1 +
+ sshrsa.c | 4 ++++
+ 2 files changed, 5 insertions(+)
+
+diff --git a/sshbn.c b/sshbn.c
+index 6768204b..b21797f0 100644
+--- a/sshbn.c
++++ b/sshbn.c
+@@ -1400,6 +1400,7 @@ void decbn(Bignum bn)
+     int i = 1;
+     while (i < (int)bn[0] && bn[i] == 0)
+ 	bn[i++] = BIGNUM_INT_MASK;
++    assert(i < (int)bn[0]);
+     bn[i]--;
+ }
+ 
+diff --git a/sshrsa.c b/sshrsa.c
+index e565a64a..1dbf16bf 100644
+--- a/sshrsa.c
++++ b/sshrsa.c
+@@ -411,6 +411,10 @@ int rsa_verify(struct RSAKey *key)
+     Bignum n, ed, pm1, qm1;
+     int cmp;
+ 
++    /* n cannot be zero. */
++    if (!bignum_cmp(key->modulus, Zero))
++        return 0;
++
+     /* n must equal pq. */
+     n = bigmul(key->p, key->q);
+     cmp = bignum_cmp(n, key->modulus);
diff -Nru putty-0.70/debian/patches/fix-double-width-crash.patch putty-0.70/debian/patches/fix-double-width-crash.patch
--- putty-0.70/debian/patches/fix-double-width-crash.patch	1970-01-01 01:00:00.000000000 +0100
+++ putty-0.70/debian/patches/fix-double-width-crash.patch	2019-03-17 09:36:53.000000000 +0000
@@ -0,0 +1,46 @@
+From 1ebfc3bc04d0bbde174da1999a922b491a0e90dd Mon Sep 17 00:00:00 2001
+From: Simon Tatham <anakin@pobox.com>
+Date: Thu, 14 Mar 2019 18:13:01 +0000
+Subject: Fix crash printing a width-2 char in a width-1 terminal.
+
+If the terminal is one column wide, it's not possible to print a
+double-width CJK character at all - it won't fit. Replace it with
+U+FFFD to indicate that impossibility.
+
+The previous behaviour was to notice that we're in the rightmost
+column of the terminal, and invoke the LATTR_WRAPPED2 special case to
+wrap to the leftmost column on the next line. But in a width-1
+terminal, the rightmost column _is_ the leftmost column, so this would
+leave us no better off, and we would have fallen through into the next
+case while in exactly the situation we'd tried to rule out.
+
+Origin: upstream, https://git.tartarus.org/?p=simon/putty.git;a=commitdiff;h=03777723e553024e94d8bfcf182f3a2e92ffb914
+Last-Update: 2019-03-16
+
+Patch-Name: fix-double-width-crash.patch
+---
+ terminal.c | 11 +++++++++++
+ 1 file changed, 11 insertions(+)
+
+diff --git a/terminal.c b/terminal.c
+index 8f1ef075..fd725d4b 100644
+--- a/terminal.c
++++ b/terminal.c
+@@ -3207,6 +3207,17 @@ static void term_out(Terminal *term)
+ 			logtraffic(term->logctx, (unsigned char) c,
+ 				   LGTYP_ASCII);
+ 
++                    /*
++                     * Preliminary check: if the terminal is only one
++                     * character cell wide, then we cannot display any
++                     * double-width character at all. Substitute
++                     * single-width REPLACEMENT CHARACTER instead.
++                     */
++                    if (width == 2 && term->cols < 2) {
++                        width = 1;
++                        c = 0xFFFD;
++                    }
++
+ 		    switch (width) {
+ 		      case 2:
+ 			/*
diff -Nru putty-0.70/debian/patches/fix-esc6-combining-chars-crash.patch putty-0.70/debian/patches/fix-esc6-combining-chars-crash.patch
--- putty-0.70/debian/patches/fix-esc6-combining-chars-crash.patch	1970-01-01 01:00:00.000000000 +0100
+++ putty-0.70/debian/patches/fix-esc6-combining-chars-crash.patch	2019-03-17 09:36:53.000000000 +0000
@@ -0,0 +1,45 @@
+From 30099b60d0c5a50c95c38080a568f3a2054d274a Mon Sep 17 00:00:00 2001
+From: Simon Tatham <anakin@pobox.com>
+Date: Thu, 14 Feb 2019 18:05:22 +0000
+Subject: Fix crash on ESC#6 + combining chars + GTK + odd-width terminal.
+
+When we're displaying double-width text as a result of the VT100 ESC#6
+escape sequence or its friends, and the terminal width is an odd
+number of columns, we divide by 2 the number of characters we'll even
+try to display, and round _down_: if there's a rightmost odd column,
+it stays blank, and doesn't show the left half of a double-width char.
+
+In the GTK redraw function, that rounding-down can set the 'len'
+variable to zero. But when we're displaying a character with Unicode
+combining chars on top, that fails an assertion that len == 1, because
+at the top of the function we set it to 1.
+
+The fix is just to return early if len is reduced to zero by that
+rounding: if we're not displaying any characters, then we don't have
+to do anything at all.
+
+Origin: upstream, https://git.tartarus.org/?p=simon/putty.git;a=commitdiff;h=daf91ef8ae9780bb1dfb534afa79e4babb89ba26
+Last-Update: 2019-03-16
+
+Patch-Name: fix-esc6-combining-chars-crash.patch
+---
+ unix/gtkwin.c | 5 ++++-
+ 1 file changed, 4 insertions(+), 1 deletion(-)
+
+diff --git a/unix/gtkwin.c b/unix/gtkwin.c
+index bd57dc2e..61683d68 100644
+--- a/unix/gtkwin.c
++++ b/unix/gtkwin.c
+@@ -3305,8 +3305,11 @@ void do_text_internal(Context ctx, int x, int y, wchar_t *text, int len,
+ 	x *= 2;
+ 	if (x >= inst->term->cols)
+ 	    return;
+-	if (x + len*2*widefactor > inst->term->cols)
++	if (x + len*2*widefactor > inst->term->cols) {
+ 	    len = (inst->term->cols-x)/2/widefactor;/* trim to LH half */
++            if (len == 0)
++                return; /* rounded down half a double-width char to zero */
++        }
+ 	rlen = len * 2;
+     } else
+ 	rlen = len;
diff -Nru putty-0.70/debian/patches/limit-combining-chars-per-cell.patch putty-0.70/debian/patches/limit-combining-chars-per-cell.patch
--- putty-0.70/debian/patches/limit-combining-chars-per-cell.patch	1970-01-01 01:00:00.000000000 +0100
+++ putty-0.70/debian/patches/limit-combining-chars-per-cell.patch	2019-03-17 09:36:53.000000000 +0000
@@ -0,0 +1,126 @@
+From 471b1daee41d015a14e288cd927d00a9db6801b5 Mon Sep 17 00:00:00 2001
+From: Simon Tatham <anakin@pobox.com>
+Date: Fri, 1 Mar 2019 19:20:12 +0000
+Subject: Limit the number of combining chars per terminal cell.
+
+The previous unlimited system was nicely general, but unfortunately
+meant you could easily DoS a PuTTY-based terminal by sending a
+printing character followed by an endless stream of identical
+combining chars. (In fact, due to accidentally-quadratic linked list
+management, you'd DoS it by using up all the CPU even before you got
+the point of making it allocate all the RAM.)
+
+The new limit is chosen to be 32, more or less arbitrarily. Overlong
+sequences of combining characters are signalled by turning the whole
+character cell into U+FFFD REPLACEMENT CHARACTER.
+
+Origin: upstream, https://git.tartarus.org/?p=simon/putty.git;a=commitdiff;h=da1c8f15b1bc14c855f0027cf06ba7f1a9c36f3c
+Last-Update: 2019-03-16
+
+Patch-Name: limit-combining-chars-per-cell.patch
+---
+ terminal.c | 52 +++++++++++++++++++++++++++++++++++++++++++++-------
+ terminal.h | 15 +++++++++++++++
+ 2 files changed, 60 insertions(+), 7 deletions(-)
+
+diff --git a/terminal.c b/terminal.c
+index ba9dd617..8f1ef075 100644
+--- a/terminal.c
++++ b/terminal.c
+@@ -196,6 +196,8 @@ static void cc_check(termline *line)
+ }
+ #endif
+ 
++static void clear_cc(termline *line, int col);
++
+ /*
+  * Add a combining character to a character cell.
+  */
+@@ -206,7 +208,49 @@ static void add_cc(termline *line, int col, unsigned long chr)
+     assert(col >= 0 && col < line->cols);
+ 
+     /*
+-     * Start by extending the cols array if the free list is empty.
++     * Don't add combining characters at all to U+FFFD REPLACEMENT
++     * CHARACTER. (Partly it's a slightly incoherent idea in the first
++     * place; mostly, U+FFFD is what we generate if a cell already has
++     * too many ccs, in which case we want it to be a fixed point when
++     * further ccs are added.)
++     */
++    if (line->chars[col].chr == 0xFFFD)
++        return;
++
++    /*
++     * Walk the cc list of the cell in question to find its current
++     * end point.
++     */
++    size_t ncc = 0;
++    int origcol = col;
++    while (line->chars[col].cc_next) {
++	col += line->chars[col].cc_next;
++        if (++ncc >= CC_LIMIT) {
++            /*
++             * There are already too many combining characters in this
++             * character cell. Change strategy: throw out the entire
++             * chain and replace the main character with U+FFFD.
++             *
++             * (Rationale: extrapolating from UTR #36 section 3.6.2
++             * suggests the principle that it's better to substitute
++             * U+FFFD than to _ignore_ input completely. Also, if the
++             * user copies and pastes an overcombined character cell,
++             * this way it will clearly indicate that we haven't
++             * reproduced the writer's original intentions, instead of
++             * looking as if it was the _writer's_ fault that the 33rd
++             * cc is missing.)
++             *
++             * Per the code above, this will also prevent any further
++             * ccs from being added to this cell.
++             */
++            clear_cc(line, origcol);
++            line->chars[origcol].chr = 0xFFFD;
++            return;
++        }
++    }
++
++    /*
++     * Extend the cols array if the free list is empty.
+      */
+     if (!line->cc_free) {
+ 	int n = line->size;
+@@ -222,12 +266,6 @@ static void add_cc(termline *line, int col, unsigned long chr)
+ 	}
+     }
+ 
+-    /*
+-     * Now walk the cc list of the cell in question.
+-     */
+-    while (line->chars[col].cc_next)
+-	col += line->chars[col].cc_next;
+-
+     /*
+      * `col' now points at the last cc currently in this cell; so
+      * we simply add another one.
+diff --git a/terminal.h b/terminal.h
+index 2ed9e6ef..38d154c6 100644
+--- a/terminal.h
++++ b/terminal.h
+@@ -327,4 +327,19 @@ struct terminal_tag {
+ 
+ #define in_utf(term) ((term)->utf || (term)->ucsdata->line_codepage==CP_UTF8)
+ 
++/*
++ * Maximum number of combining characters we're willing to store in a
++ * character cell. Our linked-list data representation permits an
++ * unlimited number of these in principle, but if we allowed that in
++ * practice then it would be an easy DoS to just squirt a squillion
++ * identical combining characters to someone's terminal and cause
++ * their PuTTY or pterm to consume lots of memory and CPU pointlessly.
++ *
++ * The precise figure of 32 is more or less arbitrary, but one point
++ * supporting it is UAX #15's comment that 30 combining characters is
++ * "significantly beyond what is required for any linguistic or
++ * technical usage".
++ */
++#define CC_LIMIT 32
++
+ #endif
diff -Nru putty-0.70/debian/patches/minibidi-fix-read-past-end-of-line.patch putty-0.70/debian/patches/minibidi-fix-read-past-end-of-line.patch
--- putty-0.70/debian/patches/minibidi-fix-read-past-end-of-line.patch	1970-01-01 01:00:00.000000000 +0100
+++ putty-0.70/debian/patches/minibidi-fix-read-past-end-of-line.patch	2019-03-17 09:36:53.000000000 +0000
@@ -0,0 +1,33 @@
+From 086e75a1cefd2c0727948fd2940ecda4338c11ce Mon Sep 17 00:00:00 2001
+From: Simon Tatham <anakin@pobox.com>
+Date: Sat, 9 Feb 2019 14:12:16 +0000
+Subject: minibidi: fix read past end of line in rule W5.
+
+The check for a sequence of ET with an EN after it could potentially
+skip ETs all the way up to the end of the buffer and then look for an
+EN in the following nonexistent array element. Now it only skips ETs
+up to count-1, in the same style as the similar check in rule N1.
+
+Change-Id: Ifdbae494a22d1b96bf49ae1bcae0efb901565f45
+
+Origin: upstream, https://git.tartarus.org/?p=simon/putty.git;a=commitdiff;h=03492ab59369dc8347d8eb8693da548b5e27cf0b
+Last-Update: 2019-03-16
+
+Patch-Name: minibidi-fix-read-past-end-of-line.patch
+---
+ minibidi.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/minibidi.c b/minibidi.c
+index 2bdf4deb..47ca558d 100644
+--- a/minibidi.c
++++ b/minibidi.c
+@@ -1413,7 +1413,7 @@ int do_bidi(bidi_char *line, int count)
+                 continue;
+             } else if (i < count-1 && types[i+1] == ET) {
+                 j=i;
+-                while (j <count && types[j] == ET) {
++                while (j < count-1 && types[j] == ET) {
+                     j++;
+                 }
+                 if (types[j] == EN)
diff -Nru putty-0.70/debian/patches/random-add-noise-overrun.patch putty-0.70/debian/patches/random-add-noise-overrun.patch
--- putty-0.70/debian/patches/random-add-noise-overrun.patch	1970-01-01 01:00:00.000000000 +0100
+++ putty-0.70/debian/patches/random-add-noise-overrun.patch	2019-03-17 09:36:52.000000000 +0000
@@ -0,0 +1,69 @@
+From ac3bf240f22558a1a3ed5523fbfbb240c7637c74 Mon Sep 17 00:00:00 2001
+From: Simon Tatham <anakin@pobox.com>
+Date: Tue, 29 Jan 2019 21:07:41 +0000
+Subject: Fix one-byte buffer overrun in random_add_noise().
+
+The variable 'poolpos' is incremented by two functions: random_byte(),
+which reads from the pool, and random_add_noise(), which writes to it.
+Both of them must handle the case where poolpos reaches its upper
+limit POOLSIZE, wrap poolpos round to the bottom, and trigger a
+random_stir().
+
+Unfortunately, random_byte checks that poolpos < POOLSIZE _before_ the
+read, so it may leave poolpos==POOLSIZE on exit. And random_add_noise
+does it the other way round - it assumes it's safe to write the first
+byte _before_ doing the bounds check. So if exactly the right number
+of random_byte calls happen before the next add_noise, then a byte of
+entropy can be XORed into the thing just beyond the end of pool[].
+
+What _is_ beyond that point is the LSB of poolpos itself! Since
+POOLSIZE is not a multiple of 256, this means that poolpos could be
+made larger or smaller by this overwrite. If it's made larger, that's
+the safe case - the subsequent bounds check will still fail, and then
+poolpos will be reset anyway. (And this is also what will happen in
+the very likely case that poolpos was cached in a register during that
+part of the function.)
+
+The dangerous case is if poolpos is made _smaller_ by that overwrite.
+In that situation, the effect will be to rewind the position in the
+random pool, delaying a necessary random_stir() and causing
+already-output random numbers to either be reused, or to be reused
+combined with unhashed input noise. In the latter case, it's just
+conceivable that an attacker who was somehow controlling at least one
+entropy source might be able to manipulate the RNG output on demand.
+
+Last-Update: 2019-03-16
+
+Patch-Name: random-add-noise-overrun.patch
+---
+ sshrand.c | 9 ++++++---
+ 1 file changed, 6 insertions(+), 3 deletions(-)
+
+diff --git a/sshrand.c b/sshrand.c
+index 31b1739e..05adbfa0 100644
+--- a/sshrand.c
++++ b/sshrand.c
+@@ -234,17 +234,20 @@ void random_add_noise(void *noise, int length)
+      * sources then we would be throwing away valuable stuff.
+      */
+     while (length >= (HASHINPUT - pool.incomingpos)) {
++        int need_stir = FALSE;
+ 	memcpy(pool.incomingb + pool.incomingpos, p,
+ 	       HASHINPUT - pool.incomingpos);
+ 	p += HASHINPUT - pool.incomingpos;
+ 	length -= HASHINPUT - pool.incomingpos;
+ 	SHATransform((word32 *) pool.incoming, (word32 *) pool.incomingb);
+ 	for (i = 0; i < HASHSIZE; i++) {
+-	    pool.pool[pool.poolpos++] ^= pool.incoming[i];
+-	    if (pool.poolpos >= POOLSIZE)
++	    if (pool.poolpos >= POOLSIZE) {
+ 		pool.poolpos = 0;
++                need_stir = TRUE;
++            }
++	    pool.pool[pool.poolpos++] ^= pool.incoming[i];
+ 	}
+-	if (pool.poolpos < HASHSIZE)
++	if (need_stir)
+ 	    random_stir();
+ 
+ 	pool.incomingpos = 0;
diff -Nru putty-0.70/debian/patches/remove-pending-toplevel-callbacks.patch putty-0.70/debian/patches/remove-pending-toplevel-callbacks.patch
--- putty-0.70/debian/patches/remove-pending-toplevel-callbacks.patch	1970-01-01 01:00:00.000000000 +0100
+++ putty-0.70/debian/patches/remove-pending-toplevel-callbacks.patch	2019-03-17 09:36:52.000000000 +0000
@@ -0,0 +1,140 @@
+From 56d59111aab5be734e0467c4a189780013788709 Mon Sep 17 00:00:00 2001
+From: Simon Tatham <anakin@pobox.com>
+Date: Sat, 25 Nov 2017 17:17:21 +0000
+Subject: New facility for removing pending toplevel callbacks.
+
+This is used when you're about to destroy an object that is
+(potentially) the context parameter for some still-pending toplevel
+callback. It causes callbacks.c to go through its pending list and
+delete any callback records referring to that context parameter, so
+that when you destroy the object those callbacks aren't still waiting
+to cause stale-pointer dereferences.
+
+(cherry picked from commit afa9734b7de6656fd8db0b077f00f39d8218a4c6)
+
+Origin: upstream, https://git.tartarus.org/?p=simon/putty.git;a=commitdiff;h=afa9734b7de6656fd8db0b077f00f39d8218a4c6
+Last-Update: 2019-03-16
+
+Patch-Name: remove-pending-toplevel-callbacks.patch
+---
+ callback.c | 65 +++++++++++++++++++++++++++++++++++++++++-------------
+ putty.h    |  1 +
+ 2 files changed, 51 insertions(+), 15 deletions(-)
+
+diff --git a/callback.c b/callback.c
+index c70dc53f..84ea0c80 100644
+--- a/callback.c
++++ b/callback.c
+@@ -14,7 +14,7 @@ struct callback {
+     void *ctx;
+ };
+ 
+-struct callback *cbhead = NULL, *cbtail = NULL;
++struct callback *cbcurr = NULL, *cbhead = NULL, *cbtail = NULL;
+ 
+ toplevel_callback_notify_fn_t notify_frontend = NULL;
+ void *frontend = NULL;
+@@ -26,6 +26,30 @@ void request_callback_notifications(toplevel_callback_notify_fn_t fn,
+     frontend = fr;
+ }
+ 
++void delete_callbacks_for_context(void *ctx)
++{
++    struct callback *newhead, *newtail;
++
++    newhead = newtail = NULL;
++    while (cbhead) {
++        struct callback *cb = cbhead;
++        cbhead = cbhead->next;
++        if (cb->ctx == ctx) {
++            sfree(cb);
++        } else {
++            if (!newhead)
++                newhead = cb;
++            else
++                newtail->next = cb;
++
++            newtail = cb;
++        }
++    }
++
++    cbhead = newhead;
++    cbtail = newtail;
++}
++
+ void queue_toplevel_callback(toplevel_callback_fn_t fn, void *ctx)
+ {
+     struct callback *cb;
+@@ -34,10 +58,18 @@ void queue_toplevel_callback(toplevel_callback_fn_t fn, void *ctx)
+     cb->fn = fn;
+     cb->ctx = ctx;
+ 
+-    /* If the front end has requested notification of pending
++    /*
++     * If the front end has requested notification of pending
+      * callbacks, and we didn't already have one queued, let it know
+-     * we do have one now. */
+-    if (notify_frontend && !cbhead)
++     * we do have one now.
++     *
++     * If cbcurr is non-NULL, i.e. we are actually in the middle of
++     * executing a callback right now, then we count that as the queue
++     * already having been non-empty. That saves the front end getting
++     * a constant stream of needless re-notifications if the last
++     * callback keeps re-scheduling itself.
++     */
++    if (notify_frontend && !cbhead && !cbcurr)
+         notify_frontend(frontend);
+ 
+     if (cbtail)
+@@ -51,24 +83,27 @@ void queue_toplevel_callback(toplevel_callback_fn_t fn, void *ctx)
+ void run_toplevel_callbacks(void)
+ {
+     if (cbhead) {
+-        struct callback *cb = cbhead;
+         /*
+-         * Careful ordering here. We call the function _before_
+-         * advancing cbhead (though, of course, we must free cb
+-         * _after_ advancing it). This means that if the very last
+-         * callback schedules another callback, cbhead does not become
+-         * NULL at any point, and so the frontend notification
+-         * function won't be needlessly pestered.
++         * Transfer the head callback into cbcurr to indicate that
++         * it's being executed. Then operations which transform the
++         * queue, like delete_callbacks_for_context, can proceed as if
++         * it's not there.
+          */
+-        cb->fn(cb->ctx);
+-        cbhead = cb->next;
+-        sfree(cb);
++        cbcurr = cbhead;
++        cbhead = cbhead->next;
+         if (!cbhead)
+             cbtail = NULL;
++
++        /*
++         * Now run the callback, and then clear it out of cbcurr.
++         */
++        cbcurr->fn(cbcurr->ctx);
++        sfree(cbcurr);
++        cbcurr = NULL;
+     }
+ }
+ 
+ int toplevel_callback_pending(void)
+ {
+-    return cbhead != NULL;
++    return cbcurr != NULL || cbhead != NULL;
+ }
+diff --git a/putty.h b/putty.h
+index fd2d0250..28fb59a0 100644
+--- a/putty.h
++++ b/putty.h
+@@ -1507,6 +1507,7 @@ typedef void (*toplevel_callback_fn_t)(void *ctx);
+ void queue_toplevel_callback(toplevel_callback_fn_t fn, void *ctx);
+ void run_toplevel_callbacks(void);
+ int toplevel_callback_pending(void);
++void delete_callbacks_for_context(void *ctx);
+ 
+ typedef void (*toplevel_callback_notify_fn_t)(void *frontend);
+ void request_callback_notifications(toplevel_callback_notify_fn_t notify,
diff -Nru putty-0.70/debian/patches/rsa-kex-enforce-minimum-key-length.patch putty-0.70/debian/patches/rsa-kex-enforce-minimum-key-length.patch
--- putty-0.70/debian/patches/rsa-kex-enforce-minimum-key-length.patch	1970-01-01 01:00:00.000000000 +0100
+++ putty-0.70/debian/patches/rsa-kex-enforce-minimum-key-length.patch	2019-03-17 09:36:53.000000000 +0000
@@ -0,0 +1,77 @@
+From cb90098659f9bdd80b9e488dc7f3e291ac0da36d Mon Sep 17 00:00:00 2001
+From: Simon Tatham <anakin@pobox.com>
+Date: Thu, 7 Feb 2019 20:04:17 +0000
+Subject: RSA kex: enforce the minimum key length.
+
+I completely forgot to check that the server had actually sent a key
+of at least MINKLEN bits, as RFC 4432 clearly says that it MUST.
+Without this restriction, not only can a server trick the client into
+using a shared secret with inadequate entropy, but it can send a key
+so short that the client attempts to generate a secret integer of
+negative length, with integer-overflowing results.
+
+Origin: upstream, https://git.tartarus.org/?p=simon/putty.git;a=commitdiff;h=d82854999516046122501b2e145099740ed0284f
+Last-Update: 2019-03-16
+
+Patch-Name: rsa-kex-enforce-minimum-key-length.patch
+---
+ ssh.c    | 12 ++++++++++++
+ ssh.h    |  2 ++
+ sshrsa.c |  4 ++--
+ 3 files changed, 16 insertions(+), 2 deletions(-)
+
+diff --git a/ssh.c b/ssh.c
+index 1d80e919..1edecfe1 100644
+--- a/ssh.c
++++ b/ssh.c
+@@ -7251,7 +7251,19 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen,
+          */
+         {
+             int klen = ssh_rsakex_klen(s->rsakey);
++
++            int minklen = (ssh->kex == &ssh_rsa_kex_sha1 ? 1024 : 2048);
++            if (klen < minklen) {
++                sfree(s->rsakeydata);
++                bombout(("Server sent %d-bit RSA key, "
++                         "less than the minimum size %d for %s "
++                         "key exchange", klen, minklen, ssh->kex->name));
++                crStopV;
++            }
++
+             int nbits = klen - (2*ssh->kex->hash->hlen*8 + 49);
++            assert(nbits > 0);
++
+             int i, byte = 0;
+             unsigned char *kstr1, *kstr2, *outstr;
+             int kstr1len, kstr2len, outstrlen;
+diff --git a/ssh.h b/ssh.h
+index 371de837..3cafc7f9 100644
+--- a/ssh.h
++++ b/ssh.h
+@@ -463,6 +463,8 @@ extern const struct ssh_kexes ssh_diffiehellman_group1;
+ extern const struct ssh_kexes ssh_diffiehellman_group14;
+ extern const struct ssh_kexes ssh_diffiehellman_gex;
+ extern const struct ssh_kexes ssh_rsa_kex;
++extern const struct ssh_kex ssh_rsa_kex_sha1;
++extern const struct ssh_kex ssh_rsa_kex_sha256;
+ extern const struct ssh_kexes ssh_ecdh_kex;
+ extern const struct ssh_signkey ssh_dss;
+ extern const struct ssh_signkey ssh_rsa;
+diff --git a/sshrsa.c b/sshrsa.c
+index 1dbf16bf..e066fd25 100644
+--- a/sshrsa.c
++++ b/sshrsa.c
+@@ -1063,11 +1063,11 @@ void ssh_rsakex_encrypt(const struct ssh_hash *h, unsigned char *in, int inlen,
+      */
+ }
+ 
+-static const struct ssh_kex ssh_rsa_kex_sha1 = {
++const struct ssh_kex ssh_rsa_kex_sha1 = {
+     "rsa1024-sha1", NULL, KEXTYPE_RSA, &ssh_sha1, NULL,
+ };
+ 
+-static const struct ssh_kex ssh_rsa_kex_sha256 = {
++const struct ssh_kex ssh_rsa_kex_sha256 = {
+     "rsa2048-sha256", NULL, KEXTYPE_RSA, &ssh_sha256, NULL,
+ };
+ 
diff -Nru putty-0.70/debian/patches/sanity-check-public-lines.patch putty-0.70/debian/patches/sanity-check-public-lines.patch
--- putty-0.70/debian/patches/sanity-check-public-lines.patch	1970-01-01 01:00:00.000000000 +0100
+++ putty-0.70/debian/patches/sanity-check-public-lines.patch	2019-03-17 09:36:53.000000000 +0000
@@ -0,0 +1,100 @@
+From 1134ca985ce1601410f0e21edbd4b1643051e8d1 Mon Sep 17 00:00:00 2001
+From: Simon Tatham <anakin@pobox.com>
+Date: Sat, 19 Jan 2019 07:40:07 +0000
+Subject: Sanity-check the 'Public-Lines' field in ppk files.
+
+If it's too large, memory allocation can fail, or worse,
+under-allocate due to integer overflow.
+
+Origin: upstream, https://git.tartarus.org/?p=simon/putty.git;a=commitdiff;h=63a58759b5c0c11183726767a095f3a154b0f131
+Last-Update: 2019-03-16
+
+Patch-Name: sanity-check-public-lines.patch
+---
+ sshpubk.c | 35 ++++++++++++++++++++++++++++++++---
+ 1 file changed, 32 insertions(+), 3 deletions(-)
+
+diff --git a/sshpubk.c b/sshpubk.c
+index 1a27c313..acf2292c 100644
+--- a/sshpubk.c
++++ b/sshpubk.c
+@@ -23,6 +23,18 @@
+ 
+ static int key_type_fp(FILE *fp);
+ 
++/*
++ * Fairly arbitrary size limit on any public or private key blob.
++ * Chosen to match AGENT_MAX_MSGLEN, on the basis that any key too
++ * large to transfer over the ssh-agent protocol is probably too large
++ * to be useful in general.
++ *
++ * MAX_KEY_BLOB_LINES is the corresponding limit on the Public-Lines
++ * or Private-Lines header field in a key file.
++ */
++#define MAX_KEY_BLOB_SIZE 262144
++#define MAX_KEY_BLOB_LINES (MAX_KEY_BLOB_SIZE / 48)
++
+ static int loadrsakey_main(FILE * fp, struct RSAKey *key, int pub_only,
+ 			   char **commentptr, const char *passphrase,
+ 			   const char **error)
+@@ -575,6 +587,7 @@ static unsigned char *read_blob(FILE * fp, int nlines, int *bloblen)
+     int i, j, k;
+ 
+     /* We expect at most 64 base64 characters, ie 48 real bytes, per line. */
++    assert(nlines < MAX_KEY_BLOB_LINES);
+     blob = snewn(48 * nlines, unsigned char);
+     len = 0;
+     for (i = 0; i < nlines; i++) {
+@@ -634,6 +647,16 @@ const struct ssh_signkey *find_pubkey_alg(const char *name)
+     return find_pubkey_alg_len(strlen(name), name);
+ }
+ 
++static int userkey_parse_line_counter(const char *text)
++{
++    char *endptr;
++    unsigned long ul = strtoul(text, &endptr, 10);
++    if (*text && !*endptr && ul < MAX_KEY_BLOB_LINES)
++        return ul;
++    else
++        return -1;
++}
++
+ struct ssh2_userkey *ssh2_load_userkey(const Filename *filename,
+ 				       const char *passphrase,
+                                        const char **errorstr)
+@@ -714,8 +737,10 @@ struct ssh2_userkey *ssh2_load_userkey(const Filename *filename,
+ 	goto error;
+     if ((b = read_body(fp)) == NULL)
+ 	goto error;
+-    i = atoi(b);
++    i = userkey_parse_line_counter(b);
+     sfree(b);
++    if (i < 0)
++        goto error;
+     if ((public_blob = read_blob(fp, i, &public_blob_len)) == NULL)
+ 	goto error;
+ 
+@@ -724,8 +749,10 @@ struct ssh2_userkey *ssh2_load_userkey(const Filename *filename,
+ 	goto error;
+     if ((b = read_body(fp)) == NULL)
+ 	goto error;
+-    i = atoi(b);
++    i = userkey_parse_line_counter(b);
+     sfree(b);
++    if (i < 0)
++        goto error;
+     if ((private_blob = read_blob(fp, i, &private_blob_len)) == NULL)
+ 	goto error;
+ 
+@@ -1186,8 +1213,10 @@ unsigned char *ssh2_userkey_loadpub(const Filename *filename, char **algorithm,
+ 	goto error;
+     if ((b = read_body(fp)) == NULL)
+ 	goto error;
+-    i = atoi(b);
++    i = userkey_parse_line_counter(b);
+     sfree(b);
++    if (i < 0)
++        goto error;
+     if ((public_blob = read_blob(fp, i, &public_blob_len)) == NULL)
+ 	goto error;
+ 
diff -Nru putty-0.70/debian/patches/series putty-0.70/debian/patches/series
--- putty-0.70/debian/patches/series	2018-10-28 17:18:52.000000000 +0000
+++ putty-0.70/debian/patches/series	2019-03-17 09:36:52.000000000 +0000
@@ -5,3 +5,16 @@
 pscp-fixed-size-buffer.patch
 cmdgen-fixed-size-buffer.patch
 gtk-focus.patch
+remove-pending-toplevel-callbacks.patch
+random-add-noise-overrun.patch
+uxnet-clean-up-callbacks.patch
+sk_tcp_close-fix-memory-leak.patch
+fix-bad-rsa-key-handling.patch
+sanity-check-public-lines.patch
+uxsel-enum.patch
+switch-to-poll.patch
+rsa-kex-enforce-minimum-key-length.patch
+fix-esc6-combining-chars-crash.patch
+limit-combining-chars-per-cell.patch
+minibidi-fix-read-past-end-of-line.patch
+fix-double-width-crash.patch
diff -Nru putty-0.70/debian/patches/sk_tcp_close-fix-memory-leak.patch putty-0.70/debian/patches/sk_tcp_close-fix-memory-leak.patch
--- putty-0.70/debian/patches/sk_tcp_close-fix-memory-leak.patch	1970-01-01 01:00:00.000000000 +0100
+++ putty-0.70/debian/patches/sk_tcp_close-fix-memory-leak.patch	2019-03-17 09:36:52.000000000 +0000
@@ -0,0 +1,44 @@
+From 5e4227e8e306fea03b7f6a3aa3d1b664af8c2178 Mon Sep 17 00:00:00 2001
+From: Simon Tatham <anakin@pobox.com>
+Date: Tue, 29 Jan 2019 20:13:47 +0000
+Subject: sk_tcp_close: fix memory leak of output bufchain.
+
+If there was still pending output data on a NetSocket's output_data
+bufchain when it was closed, then we wouldn't have freed it, on either
+Unix or Windows.
+
+Origin: upstream, https://git.tartarus.org/?p=simon/putty.git;a=commitdiff;h=0212b9e5e5cb55ecaf678a65c2ef0d8d968c8a26
+Last-Update: 2019-03-16
+
+Patch-Name: sk_tcp_close-fix-memory-leak.patch
+---
+ unix/uxnet.c     | 2 ++
+ windows/winnet.c | 2 ++
+ 2 files changed, 4 insertions(+)
+
+diff --git a/unix/uxnet.c b/unix/uxnet.c
+index 6549eca5..35a90ac7 100644
+--- a/unix/uxnet.c
++++ b/unix/uxnet.c
+@@ -1023,6 +1023,8 @@ static void sk_tcp_close(Socket sock)
+     if (s->child)
+         sk_tcp_close((Socket)s->child);
+ 
++    bufchain_clear(&s->output_data);
++
+     uxsel_del(s->s);
+     del234(sktree, s);
+     close(s->s);
+diff --git a/windows/winnet.c b/windows/winnet.c
+index 86f7735f..3c02c3f8 100644
+--- a/windows/winnet.c
++++ b/windows/winnet.c
+@@ -1464,6 +1464,8 @@ static void sk_tcp_close(Socket sock)
+     if (s->child)
+ 	sk_tcp_close((Socket)s->child);
+ 
++    bufchain_clear(&s->output_data);
++
+     del234(sktree, s);
+     do_select(s->s, 0);
+     p_closesocket(s->s);
diff -Nru putty-0.70/debian/patches/switch-to-poll.patch putty-0.70/debian/patches/switch-to-poll.patch
--- putty-0.70/debian/patches/switch-to-poll.patch	1970-01-01 01:00:00.000000000 +0100
+++ putty-0.70/debian/patches/switch-to-poll.patch	2019-03-17 09:36:53.000000000 +0000
@@ -0,0 +1,842 @@
+From 685388367abb55cd685fbae4e2d7c10fda6c1958 Mon Sep 17 00:00:00 2001
+From: Simon Tatham <anakin@pobox.com>
+Date: Thu, 7 Feb 2019 18:21:06 +0000
+Subject: Switch to using poll(2) in place of select(2).
+
+I've always thought poll was more hassle to set up, because if you
+want to reuse part of your pollfds list between calls then you have to
+index every fd by its position in the list as well as the fd number
+itself, which gives you twice as many indices to keep track of than if
+the fd is always its own key.
+
+But the problem is that select is fundamentally limited to the range
+of fds that can fit in an fd_set, which is not the range of fds that
+can _exist_, so I've had a change of heart and now have to go with
+poll.
+
+For the moment, I've surrounded it with a 'pollwrapper' structure that
+lets me treat it more or less like select, containing a tree234 that
+maps each fd to its location in the list, and also translating between
+the simple select r/w/x classification and the richer poll flags.
+That's let me do the migration with minimal disruption to the call
+sites.
+
+In future perhaps I can start using poll more directly, and/or using
+the richer flag system (though the latter might be fiddly because of
+sometimes being constrained to use the glib event loop). But this will
+do for now.
+
+Origin: upstream, https://git.tartarus.org/?p=simon/putty.git;a=commitdiff;h=5c926d9ea4a9e0a0a2384f06c7583648cdff3ed6
+Last-Update: 2019-03-16
+
+Patch-Name: switch-to-poll.patch
+---
+ Recipe         |   4 +-
+ cmdline.c      |   2 +-
+ configure.ac   |   2 +-
+ putty.h        |   4 +-
+ unix/unix.h    |  19 +++++++
+ unix/uxcons.c  |  18 +++----
+ unix/uxnet.c   |   2 +-
+ unix/uxpgnt.c  |  56 +++++++++-----------
+ unix/uxplink.c |  70 +++++++++++-------------
+ unix/uxpoll.c  | 141 +++++++++++++++++++++++++++++++++++++++++++++++++
+ unix/uxsel.c   |   4 +-
+ unix/uxsftp.c  |  57 +++++++++-----------
+ 12 files changed, 260 insertions(+), 119 deletions(-)
+ create mode 100644 unix/uxpoll.c
+
+diff --git a/Recipe b/Recipe
+index f5458122..dfbf3f04 100644
+--- a/Recipe
++++ b/Recipe
+@@ -263,7 +263,7 @@ SFTP     = sftp int64 logging
+ MISC     = timing callback misc version settings tree234 proxy conf be_misc
+ WINMISC  = MISC winstore winnet winhandl cmdline windefs winmisc winproxy
+          + wintime winhsock errsock winsecur winucs miscucs
+-UXMISC   = MISC uxstore uxsel uxnet uxpeer cmdline uxmisc uxproxy time
++UXMISC   = MISC uxstore uxsel uxpoll uxnet uxpeer cmdline uxmisc uxproxy time
+ 
+ # import.c and dependencies, for PuTTYgen-like utilities that have to
+ # load foreign key files.
+@@ -330,7 +330,7 @@ plink    : [U] uxplink uxcons NONSSH UXSSH U_BE_ALL logging UXMISC uxsignal
+ PUTTYGEN_UNIX = sshrsag sshdssg sshprime sshdes sshbn sshmd5 version
+          + sshrand uxnoise sshsha misc sshrsa sshdss uxcons uxstore uxmisc
+          + sshpubk sshaes sshsh256 sshsh512 IMPORT puttygen.res time tree234
+-         + uxgen notiming conf sshecc sshecdsag uxnogtk
++         + uxgen notiming conf sshecc sshecdsag uxnogtk uxpoll
+ puttygen : [U] cmdgen PUTTYGEN_UNIX
+ cgtest   : [UT] cgtest PUTTYGEN_UNIX
+ 
+diff --git a/cmdline.c b/cmdline.c
+index f288ed62..83b55717 100644
+--- a/cmdline.c
++++ b/cmdline.c
+@@ -121,7 +121,7 @@ int cmdline_get_passwd_input(prompts_t *p, const unsigned char *in, int inlen)
+  * transfer tools (psftp, pscp) can't do a great deal with protocol
+  * selections (ever tried running scp over telnet?) or with port
+  * forwarding (even if it wasn't a hideously bad idea, they don't
+- * have the select() infrastructure to make them work).
++ * have the select/poll infrastructure to make them work).
+  */
+ int cmdline_tooltype = 0;
+ 
+diff --git a/configure.ac b/configure.ac
+index 1b760920..ca0bbba9 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -87,7 +87,7 @@ case "$gtk_version_desired" in
+   *) AC_ERROR([Invalid GTK version specified])
+ esac
+ 
+-AC_CHECK_HEADERS([utmpx.h sys/select.h],,,[
++AC_CHECK_HEADERS([utmpx.h],,,[
+ #include <sys/types.h>
+ #include <utmp.h>])
+ 
+diff --git a/putty.h b/putty.h
+index 28fb59a0..9c822744 100644
+--- a/putty.h
++++ b/putty.h
+@@ -1427,7 +1427,7 @@ char filename_char_sanitise(char c);   /* rewrite special pathname chars */
+  * The reason for this is that an OS's system clock might not agree
+  * exactly with the timing mechanisms it supplies to wait for a
+  * given interval. I'll illustrate this by the simple example of
+- * Unix Plink, which uses timeouts to select() in a way which for
++ * Unix Plink, which uses timeouts to poll() in a way which for
+  * these purposes can simply be considered to be a wait() function.
+  * Suppose, for the sake of argument, that this wait() function
+  * tends to return early by 1%. Then a possible sequence of actions
+@@ -1499,7 +1499,7 @@ unsigned long timing_last_clock(void);
+  * instead request notifications when a callback is available, so that
+  * it knows to ask its delegate event loop to do the same thing. Also,
+  * if a front end needs to know whether a callback is pending without
+- * actually running it (e.g. so as to put a zero timeout on a select()
++ * actually running it (e.g. so as to put a zero timeout on a poll()
+  * call) then it can call toplevel_callback_pending(), which will
+  * return true if at least one callback is in the queue.
+  */
+diff --git a/unix/unix.h b/unix/unix.h
+index 0bd012cb..af2f4a7c 100644
+--- a/unix/unix.h
++++ b/unix/unix.h
+@@ -264,4 +264,23 @@ int so_peercred(int fd, int *pid, int *uid, int *gid);
+ #define DEFAULT_GTK_FONT "server:fixed"
+ #endif
+ 
++/*
++ * uxpoll.c.
++ */
++typedef struct pollwrapper pollwrapper;
++pollwrapper *pollwrap_new(void);
++void pollwrap_free(pollwrapper *pw);
++void pollwrap_clear(pollwrapper *pw);
++void pollwrap_add_fd_events(pollwrapper *pw, int fd, int events);
++void pollwrap_add_fd_rwx(pollwrapper *pw, int fd, int rwx);
++int pollwrap_poll_instant(pollwrapper *pw);
++int pollwrap_poll_endless(pollwrapper *pw);
++int pollwrap_poll_timeout(pollwrapper *pw, int milliseconds);
++int pollwrap_get_fd_events(pollwrapper *pw, int fd);
++int pollwrap_get_fd_rwx(pollwrapper *pw, int fd);
++static inline int pollwrap_check_fd_rwx(pollwrapper *pw, int fd, int rwx)
++{
++    return (pollwrap_get_fd_rwx(pw, fd) & rwx) != 0;
++}
++
+ #endif
+diff --git a/unix/uxcons.c b/unix/uxcons.c
+index 716f3fc5..6df81509 100644
+--- a/unix/uxcons.c
++++ b/unix/uxcons.c
+@@ -13,9 +13,6 @@
+ #include <unistd.h>
+ #include <fcntl.h>
+ #include <sys/time.h>
+-#ifndef HAVE_NO_SYS_SELECT_H
+-#include <sys/select.h>
+-#endif
+ 
+ #include "putty.h"
+ #include "storage.h"
+@@ -82,12 +79,13 @@ void timer_change_notify(unsigned long next)
+ /*
+  * Wrapper around Unix read(2), suitable for use on a file descriptor
+  * that's been set into nonblocking mode. Handles EAGAIN/EWOULDBLOCK
+- * by means of doing a one-fd select and then trying again; all other
+- * errors (including errors from select) are returned to the caller.
++ * by means of doing a one-fd poll and then trying again; all other
++ * errors (including errors from poll) are returned to the caller.
+  */
+ static int block_and_read(int fd, void *buf, size_t len)
+ {
+     int ret;
++    pollwrapper *pw = pollwrap_new();
+ 
+     while ((ret = read(fd, buf, len)) < 0 && (
+ #ifdef EAGAIN
+@@ -98,18 +96,18 @@ static int block_and_read(int fd, void *buf, size_t len)
+ #endif
+                0)) {
+ 
+-        fd_set rfds;
+-        FD_ZERO(&rfds);
+-        FD_SET(fd, &rfds);
++        pollwrap_clear(pw);
++        pollwrap_add_fd_rwx(pw, fd, SELECT_R);
+         do {
+-            ret = select(fd+1, &rfds, NULL, NULL, NULL);
++            ret = pollwrap_poll_endless(pw);
+         } while (ret < 0 && errno == EINTR);
+         assert(ret != 0);
+         if (ret < 0)
+             return ret;
+-        assert(FD_ISSET(fd, &rfds));
++        assert(pollwrap_check_fd_rwx(pw, fd, SELECT_R));
+     }
+ 
++    pollwrap_free(pw);
+     return ret;
+ }
+ 
+diff --git a/unix/uxnet.c b/unix/uxnet.c
+index f2e35b38..ba6a1138 100644
+--- a/unix/uxnet.c
++++ b/unix/uxnet.c
+@@ -1401,7 +1401,7 @@ static void net_select_result(int fd, int event)
+       case SELECT_W:                   /* writable */
+ 	if (!s->connected) {
+ 	    /*
+-	     * select() reports a socket as _writable_ when an
++	     * select/poll reports a socket as _writable_ when an
+ 	     * asynchronous connect() attempt either completes or
+ 	     * fails. So first we must find out which.
+ 	     */
+diff --git a/unix/uxpgnt.c b/unix/uxpgnt.c
+index eba08632..37360a21 100644
+--- a/unix/uxpgnt.c
++++ b/unix/uxpgnt.c
+@@ -8,6 +8,7 @@
+ #include <assert.h>
+ #include <signal.h>
+ #include <ctype.h>
++#include <limits.h>
+ 
+ #include <sys/types.h>
+ #include <sys/wait.h>
+@@ -845,20 +846,17 @@ void run_agent(void)
+ 
+     now = GETTICKCOUNT();
+ 
++    pollwrapper *pw = pollwrap_new();
++
+     while (!time_to_die) {
+-	fd_set rset, wset, xset;
+-	int maxfd;
+ 	int rwx;
+ 	int ret;
+         unsigned long next;
+ 
+-	FD_ZERO(&rset);
+-	FD_ZERO(&wset);
+-	FD_ZERO(&xset);
+-	maxfd = 0;
++        pollwrap_clear(pw);
+ 
+         if (signalpipe[0] >= 0) {
+-            FD_SET_MAX(signalpipe[0], maxfd, rset);
++            pollwrap_add_fd_rwx(pw, signalpipe[0], SELECT_R);
+         }
+ 
+ 	/* Count the currently active fds. */
+@@ -873,30 +871,21 @@ void run_agent(void)
+ 	}
+ 
+ 	/*
+-	 * Add all currently open fds to the select sets, and store
+-	 * them in fdlist as well.
++	 * Add all currently open fds to pw, and store them in fdlist
++	 * as well.
+ 	 */
+ 	fdcount = 0;
+ 	for (fd = first_fd(&fdstate, &rwx); fd >= 0;
+ 	     fd = next_fd(&fdstate, &rwx)) {
+ 	    fdlist[fdcount++] = fd;
+-	    if (rwx & 1)
+-		FD_SET_MAX(fd, maxfd, rset);
+-	    if (rwx & 2)
+-		FD_SET_MAX(fd, maxfd, wset);
+-	    if (rwx & 4)
+-		FD_SET_MAX(fd, maxfd, xset);
++            pollwrap_add_fd_rwx(pw, fd, rwx);
+ 	}
+ 
+         if (toplevel_callback_pending()) {
+-            struct timeval tv;
+-            tv.tv_sec = 0;
+-            tv.tv_usec = 0;
+-            ret = select(maxfd, &rset, &wset, &xset, &tv);
++            ret = pollwrap_poll_instant(pw);
+         } else if (run_timers(now, &next)) {
+             unsigned long then;
+             long ticks;
+-            struct timeval tv;
+ 
+             then = now;
+             now = GETTICKCOUNT();
+@@ -904,22 +893,27 @@ void run_agent(void)
+                 ticks = 0;
+             else
+                 ticks = next - now;
+-            tv.tv_sec = ticks / 1000;
+-            tv.tv_usec = ticks % 1000 * 1000;
+-            ret = select(maxfd, &rset, &wset, &xset, &tv);
+-            if (ret == 0)
++
++            int overflow = FALSE;
++            if (ticks > INT_MAX) {
++                ticks = INT_MAX;
++                overflow = TRUE;
++            }
++
++            ret = pollwrap_poll_timeout(pw, ticks);
++            if (ret == 0 && !overflow)
+                 now = next;
+             else
+                 now = GETTICKCOUNT();
+         } else {
+-            ret = select(maxfd, &rset, &wset, &xset, NULL);
++            ret = pollwrap_poll_endless(pw);
+         }
+ 
+         if (ret < 0 && errno == EINTR)
+             continue;
+ 
+ 	if (ret < 0) {
+-	    perror("select");
++	    perror("poll");
+ 	    exit(1);
+ 	}
+ 
+@@ -939,20 +933,22 @@ void run_agent(void)
+ 
+ 	for (i = 0; i < fdcount; i++) {
+ 	    fd = fdlist[i];
++            int rwx = pollwrap_get_fd_rwx(pw, fd);
+             /*
+              * We must process exceptional notifications before
+              * ordinary readability ones, or we may go straight
+              * past the urgent marker.
+              */
+-	    if (FD_ISSET(fd, &xset))
++	    if (rwx & SELECT_X)
+ 		select_result(fd, SELECT_X);
+-	    if (FD_ISSET(fd, &rset))
++	    if (rwx & SELECT_R)
+ 		select_result(fd, SELECT_R);
+-	    if (FD_ISSET(fd, &wset))
++	    if (rwx & SELECT_W)
+ 		select_result(fd, SELECT_W);
+ 	}
+ 
+-        if (signalpipe[0] >= 0 && FD_ISSET(signalpipe[0], &rset)) {
++        if (signalpipe[0] >= 0 &&
++            pollwrap_check_fd_rwx(pw, signalpipe[0], SELECT_R)) {
+             char c[1];
+             if (read(signalpipe[0], c, 1) <= 0)
+                 /* ignore error */;
+diff --git a/unix/uxplink.c b/unix/uxplink.c
+index e7289bf2..521ed215 100644
+--- a/unix/uxplink.c
++++ b/unix/uxplink.c
+@@ -8,15 +8,13 @@
+ #include <assert.h>
+ #include <stdarg.h>
+ #include <signal.h>
++#include <limits.h>
+ #include <unistd.h>
+ #include <fcntl.h>
+ #include <termios.h>
+ #include <pwd.h>
+ #include <sys/ioctl.h>
+ #include <sys/time.h>
+-#ifndef HAVE_NO_SYS_SELECT_H
+-#include <sys/select.h>
+-#endif
+ 
+ #define PUTTY_DO_GLOBALS	       /* actually _define_ globals */
+ #include "putty.h"
+@@ -1038,36 +1036,33 @@ int main(int argc, char **argv)
+     sending = FALSE;
+     now = GETTICKCOUNT();
+ 
++    pollwrapper *pw = pollwrap_new();
++
+     while (1) {
+-	fd_set rset, wset, xset;
+-	int maxfd;
+ 	int rwx;
+ 	int ret;
+         unsigned long next;
+ 
+-	FD_ZERO(&rset);
+-	FD_ZERO(&wset);
+-	FD_ZERO(&xset);
+-	maxfd = 0;
++        pollwrap_clear(pw);
+ 
+-	FD_SET_MAX(signalpipe[0], maxfd, rset);
++	pollwrap_add_fd_rwx(pw, signalpipe[0], SELECT_R);
+ 
+ 	if (!sending &&
+ 	    back->connected(backhandle) &&
+ 	    back->sendok(backhandle) &&
+ 	    back->sendbuffer(backhandle) < MAX_STDIN_BACKLOG) {
+ 	    /* If we're OK to send, then try to read from stdin. */
+-	    FD_SET_MAX(STDIN_FILENO, maxfd, rset);
++            pollwrap_add_fd_rwx(pw, STDIN_FILENO, SELECT_R);
+ 	}
+ 
+ 	if (bufchain_size(&stdout_data) > 0) {
+ 	    /* If we have data for stdout, try to write to stdout. */
+-	    FD_SET_MAX(STDOUT_FILENO, maxfd, wset);
++            pollwrap_add_fd_rwx(pw, STDOUT_FILENO, SELECT_W);
+ 	}
+ 
+ 	if (bufchain_size(&stderr_data) > 0) {
+ 	    /* If we have data for stderr, try to write to stderr. */
+-	    FD_SET_MAX(STDERR_FILENO, maxfd, wset);
++            pollwrap_add_fd_rwx(pw, STDERR_FILENO, SELECT_W);
+ 	}
+ 
+ 	/* Count the currently active fds. */
+@@ -1082,31 +1077,22 @@ int main(int argc, char **argv)
+ 	}
+ 
+ 	/*
+-	 * Add all currently open fds to the select sets, and store
+-	 * them in fdlist as well.
++	 * Add all currently open fds to pw, and store them in fdlist
++	 * as well.
+ 	 */
+ 	fdcount = 0;
+ 	for (fd = first_fd(&fdstate, &rwx); fd >= 0;
+ 	     fd = next_fd(&fdstate, &rwx)) {
+ 	    fdlist[fdcount++] = fd;
+-	    if (rwx & 1)
+-		FD_SET_MAX(fd, maxfd, rset);
+-	    if (rwx & 2)
+-		FD_SET_MAX(fd, maxfd, wset);
+-	    if (rwx & 4)
+-		FD_SET_MAX(fd, maxfd, xset);
++            pollwrap_add_fd_rwx(pw, fd, rwx);
+ 	}
+ 
+         if (toplevel_callback_pending()) {
+-            struct timeval tv;
+-            tv.tv_sec = 0;
+-            tv.tv_usec = 0;
+-            ret = select(maxfd, &rset, &wset, &xset, &tv);
++            ret = pollwrap_poll_instant(pw);
+         } else if (run_timers(now, &next)) {
+             do {
+                 unsigned long then;
+                 long ticks;
+-                struct timeval tv;
+ 
+ 		then = now;
+ 		now = GETTICKCOUNT();
+@@ -1114,42 +1100,48 @@ int main(int argc, char **argv)
+ 		    ticks = 0;
+ 		else
+ 		    ticks = next - now;
+-		tv.tv_sec = ticks / 1000;
+-		tv.tv_usec = ticks % 1000 * 1000;
+-                ret = select(maxfd, &rset, &wset, &xset, &tv);
+-                if (ret == 0)
++
++                int overflow = FALSE;
++                if (ticks > INT_MAX) {
++                    ticks = INT_MAX;
++                    overflow = TRUE;
++                }
++
++                ret = pollwrap_poll_timeout(pw, ticks);
++                if (ret == 0 && !overflow)
+                     now = next;
+                 else
+                     now = GETTICKCOUNT();
+             } while (ret < 0 && errno == EINTR);
+         } else {
+-            ret = select(maxfd, &rset, &wset, &xset, NULL);
++            ret = pollwrap_poll_endless(pw);
+         }
+ 
+         if (ret < 0 && errno == EINTR)
+             continue;
+ 
+ 	if (ret < 0) {
+-	    perror("select");
++	    perror("poll");
+ 	    exit(1);
+ 	}
+ 
+ 	for (i = 0; i < fdcount; i++) {
+ 	    fd = fdlist[i];
++            int rwx = pollwrap_get_fd_rwx(pw, fd);
+             /*
+              * We must process exceptional notifications before
+              * ordinary readability ones, or we may go straight
+              * past the urgent marker.
+              */
+-	    if (FD_ISSET(fd, &xset))
++	    if (rwx & SELECT_X)
+ 		select_result(fd, SELECT_X);
+-	    if (FD_ISSET(fd, &rset))
++	    if (rwx & SELECT_R)
+ 		select_result(fd, SELECT_R);
+-	    if (FD_ISSET(fd, &wset))
++	    if (rwx & SELECT_W)
+ 		select_result(fd, SELECT_W);
+ 	}
+ 
+-	if (FD_ISSET(signalpipe[0], &rset)) {
++	if (pollwrap_check_fd_rwx(pw, signalpipe[0], SELECT_R)) {
+ 	    char c[1];
+ 	    struct winsize size;
+ 	    if (read(signalpipe[0], c, 1) <= 0)
+@@ -1159,7 +1151,7 @@ int main(int argc, char **argv)
+ 		back->size(backhandle, size.ws_col, size.ws_row);
+ 	}
+ 
+-	if (FD_ISSET(STDIN_FILENO, &rset)) {
++	if (pollwrap_check_fd_rwx(pw, STDIN_FILENO, SELECT_R)) {
+ 	    char buf[4096];
+ 	    int ret;
+ 
+@@ -1180,11 +1172,11 @@ int main(int argc, char **argv)
+ 	    }
+ 	}
+ 
+-	if (FD_ISSET(STDOUT_FILENO, &wset)) {
++	if (pollwrap_check_fd_rwx(pw, STDOUT_FILENO, SELECT_W)) {
+ 	    back->unthrottle(backhandle, try_output(FALSE));
+ 	}
+ 
+-	if (FD_ISSET(STDERR_FILENO, &wset)) {
++	if (pollwrap_check_fd_rwx(pw, STDERR_FILENO, SELECT_W)) {
+ 	    back->unthrottle(backhandle, try_output(TRUE));
+ 	}
+ 
+diff --git a/unix/uxpoll.c b/unix/uxpoll.c
+new file mode 100644
+index 00000000..16ad0254
+--- /dev/null
++++ b/unix/uxpoll.c
+@@ -0,0 +1,141 @@
++#include <assert.h>
++
++#include <poll.h>
++
++#include "putty.h"
++#include "tree234.h"
++
++struct pollwrapper {
++    struct pollfd *fds;
++    size_t nfd, fdsize;
++    tree234 *fdtopos;
++};
++
++typedef struct pollwrap_fdtopos pollwrap_fdtopos;
++struct pollwrap_fdtopos {
++    int fd;
++    size_t pos;
++};
++
++static int pollwrap_fd_cmp(void *av, void *bv)
++{
++    pollwrap_fdtopos *a = (pollwrap_fdtopos *)av;
++    pollwrap_fdtopos *b = (pollwrap_fdtopos *)bv;
++    return a->fd < b->fd ? -1 : a->fd > b->fd ? +1 : 0;
++}
++
++pollwrapper *pollwrap_new(void)
++{
++    pollwrapper *pw = snew(pollwrapper);
++    pw->fdsize = 16;
++    pw->nfd = 0;
++    pw->fds = snewn(pw->fdsize, struct pollfd);
++    pw->fdtopos = newtree234(pollwrap_fd_cmp);
++    return pw;
++}
++
++void pollwrap_free(pollwrapper *pw)
++{
++    pollwrap_clear(pw);
++    freetree234(pw->fdtopos);
++    sfree(pw->fds);
++    sfree(pw);
++}
++
++void pollwrap_clear(pollwrapper *pw)
++{
++    pw->nfd = 0;
++    for (pollwrap_fdtopos *f2p;
++         (f2p = delpos234(pw->fdtopos, 0)) != NULL ;)
++        sfree(f2p);
++}
++
++void pollwrap_add_fd_events(pollwrapper *pw, int fd, int events)
++{
++    pollwrap_fdtopos *f2p, f2p_find;
++
++    assert(fd >= 0);
++
++    f2p_find.fd = fd;
++    f2p = find234(pw->fdtopos, &f2p_find, NULL);
++    if (!f2p) {
++        if (pw->nfd >= pw->fdsize) {
++            pw->fdsize = pw->nfd * 5 / 4 + 32;
++            pw->fds = sresize(pw->fds, pw->fdsize, struct pollfd);
++        }
++        size_t index = pw->nfd++;
++        pw->fds[index].fd = fd;
++        pw->fds[index].events = pw->fds[index].revents = 0;
++
++        f2p = snew(pollwrap_fdtopos);
++        f2p->fd = fd;
++        f2p->pos = index;
++        pollwrap_fdtopos *added = add234(pw->fdtopos, f2p);
++        assert(added == f2p);
++    }
++
++    pw->fds[f2p->pos].events |= events;
++}
++
++#define SELECT_R_IN (POLLIN  | POLLRDNORM | POLLRDBAND)
++#define SELECT_W_IN (POLLOUT | POLLWRNORM | POLLWRBAND)
++#define SELECT_X_IN (POLLPRI)
++
++#define SELECT_R_OUT (SELECT_R_IN | POLLERR | POLLHUP)
++#define SELECT_W_OUT (SELECT_W_IN | POLLERR)
++#define SELECT_X_OUT (SELECT_X_IN)
++
++void pollwrap_add_fd_rwx(pollwrapper *pw, int fd, int rwx)
++{
++    int events = 0;
++    if (rwx & SELECT_R)
++        events |= SELECT_R_IN;
++    if (rwx & SELECT_W)
++        events |= SELECT_W_IN;
++    if (rwx & SELECT_X)
++        events |= SELECT_X_IN;
++    pollwrap_add_fd_events(pw, fd, events);
++}
++
++int pollwrap_poll_instant(pollwrapper *pw)
++{
++    return poll(pw->fds, pw->nfd, 0);
++}
++
++int pollwrap_poll_endless(pollwrapper *pw)
++{
++    return poll(pw->fds, pw->nfd, -1);
++}
++
++int pollwrap_poll_timeout(pollwrapper *pw, int milliseconds)
++{
++    assert(milliseconds >= 0);
++    return poll(pw->fds, pw->nfd, milliseconds);
++}
++
++int pollwrap_get_fd_events(pollwrapper *pw, int fd)
++{
++    pollwrap_fdtopos *f2p, f2p_find;
++
++    assert(fd >= 0);
++
++    f2p_find.fd = fd;
++    f2p = find234(pw->fdtopos, &f2p_find, NULL);
++    if (!f2p)
++        return 0;
++
++    return pw->fds[f2p->pos].revents;
++}
++
++int pollwrap_get_fd_rwx(pollwrapper *pw, int fd)
++{
++    int revents = pollwrap_get_fd_events(pw, fd);
++    int rwx = 0;
++    if (revents & SELECT_R_OUT)
++        rwx |= SELECT_R;
++    if (revents & SELECT_W_OUT)
++        rwx |= SELECT_W;
++    if (revents & SELECT_X_OUT)
++        rwx |= SELECT_X;
++    return rwx;
++}
+diff --git a/unix/uxsel.c b/unix/uxsel.c
+index 52f346a4..a74db11a 100644
+--- a/unix/uxsel.c
++++ b/unix/uxsel.c
+@@ -4,8 +4,8 @@
+  * This module is a sort of all-purpose interchange for file
+  * descriptors. At one end it talks to uxnet.c and pty.c and
+  * anything else which might have one or more fds that need
+- * select()-type things doing to them during an extended program
+- * run; at the other end it talks to pterm.c or uxplink.c or
++ * select() or poll()-type things doing to them during an extended
++ * program run; at the other end it talks to pterm.c or uxplink.c or
+  * anything else which might have its own means of actually doing
+  * those select()-type things.
+  */
+diff --git a/unix/uxsftp.c b/unix/uxsftp.c
+index 65eda985..dd2851e5 100644
+--- a/unix/uxsftp.c
++++ b/unix/uxsftp.c
+@@ -6,6 +6,7 @@
+ #include <sys/types.h>
+ #include <sys/stat.h>
+ #include <stdlib.h>
++#include <limits.h>
+ #include <fcntl.h>
+ #include <dirent.h>
+ #include <unistd.h>
+@@ -13,9 +14,6 @@
+ #include <errno.h>
+ #include <assert.h>
+ #include <glob.h>
+-#ifndef HAVE_NO_SYS_SELECT_H
+-#include <sys/select.h>
+-#endif
+ 
+ #include "putty.h"
+ #include "ssh.h"
+@@ -454,15 +452,16 @@ char *dir_file_cat(const char *dir, const char *file)
+  */
+ static int ssh_sftp_do_select(int include_stdin, int no_fds_ok)
+ {
+-    fd_set rset, wset, xset;
+     int i, fdcount, fdsize, *fdlist;
+-    int fd, fdstate, rwx, ret, maxfd;
++    int fd, fdstate, rwx, ret;
+     unsigned long now = GETTICKCOUNT();
+     unsigned long next;
+ 
+     fdlist = NULL;
+     fdcount = fdsize = 0;
+ 
++    pollwrapper *pw = pollwrap_new();
++
+     do {
+ 
+ 	/* Count the currently active fds. */
+@@ -479,10 +478,7 @@ static int ssh_sftp_do_select(int include_stdin, int no_fds_ok)
+ 	    fdlist = sresize(fdlist, fdsize, int);
+ 	}
+ 
+-	FD_ZERO(&rset);
+-	FD_ZERO(&wset);
+-	FD_ZERO(&xset);
+-	maxfd = 0;
++        pollwrap_clear(pw);
+ 
+ 	/*
+ 	 * Add all currently open fds to the select sets, and store
+@@ -492,29 +488,20 @@ static int ssh_sftp_do_select(int include_stdin, int no_fds_ok)
+ 	for (fd = first_fd(&fdstate, &rwx); fd >= 0;
+ 	     fd = next_fd(&fdstate, &rwx)) {
+ 	    fdlist[fdcount++] = fd;
+-	    if (rwx & 1)
+-		FD_SET_MAX(fd, maxfd, rset);
+-	    if (rwx & 2)
+-		FD_SET_MAX(fd, maxfd, wset);
+-	    if (rwx & 4)
+-		FD_SET_MAX(fd, maxfd, xset);
++            pollwrap_add_fd_rwx(pw, fd, rwx);
+ 	}
+ 
+ 	if (include_stdin)
+-	    FD_SET_MAX(0, maxfd, rset);
++	    pollwrap_add_fd_rwx(pw, 0, SELECT_R);
+ 
+         if (toplevel_callback_pending()) {
+-            struct timeval tv;
+-            tv.tv_sec = 0;
+-            tv.tv_usec = 0;
+-            ret = select(maxfd, &rset, &wset, &xset, &tv);
++            ret = pollwrap_poll_instant(pw);
+             if (ret == 0)
+                 run_toplevel_callbacks();
+         } else if (run_timers(now, &next)) {
+             do {
+                 unsigned long then;
+                 long ticks;
+-                struct timeval tv;
+ 
+ 		then = now;
+ 		now = GETTICKCOUNT();
+@@ -522,38 +509,44 @@ static int ssh_sftp_do_select(int include_stdin, int no_fds_ok)
+ 		    ticks = 0;
+ 		else
+ 		    ticks = next - now;
+-		tv.tv_sec = ticks / 1000;
+-		tv.tv_usec = ticks % 1000 * 1000;
+-                ret = select(maxfd, &rset, &wset, &xset, &tv);
+-                if (ret == 0)
++
++                int overflow = FALSE;
++                if (ticks > INT_MAX) {
++                    ticks = INT_MAX;
++                    overflow = TRUE;
++                }
++
++                ret = pollwrap_poll_timeout(pw, ticks);
++                if (ret == 0 && !overflow)
+                     now = next;
+                 else
+                     now = GETTICKCOUNT();
+             } while (ret < 0 && errno == EINTR);
+         } else {
+             do {
+-                ret = select(maxfd, &rset, &wset, &xset, NULL);
++                ret = pollwrap_poll_endless(pw);
+             } while (ret < 0 && errno == EINTR);
+         }
+     } while (ret == 0);
+ 
+     if (ret < 0) {
+-	perror("select");
++	perror("poll");
+ 	exit(1);
+     }
+ 
+     for (i = 0; i < fdcount; i++) {
+ 	fd = fdlist[i];
++        int rwx = pollwrap_get_fd_rwx(pw, fd);
+ 	/*
+ 	 * We must process exceptional notifications before
+ 	 * ordinary readability ones, or we may go straight
+ 	 * past the urgent marker.
+ 	 */
+-	if (FD_ISSET(fd, &xset))
++	if (rwx & SELECT_X)
+ 	    select_result(fd, SELECT_X);
+-	if (FD_ISSET(fd, &rset))
++	if (rwx & SELECT_R)
+ 	    select_result(fd, SELECT_R);
+-	if (FD_ISSET(fd, &wset))
++	if (rwx & SELECT_W)
+ 	    select_result(fd, SELECT_W);
+     }
+ 
+@@ -561,7 +554,9 @@ static int ssh_sftp_do_select(int include_stdin, int no_fds_ok)
+ 
+     run_toplevel_callbacks();
+ 
+-    return FD_ISSET(0, &rset) ? 1 : 0;
++    int toret = pollwrap_check_fd_rwx(pw, 0, SELECT_R) ? 1 : 0;
++    pollwrap_free(pw);
++    return toret;
+ }
+ 
+ /*
diff -Nru putty-0.70/debian/patches/uxnet-clean-up-callbacks.patch putty-0.70/debian/patches/uxnet-clean-up-callbacks.patch
--- putty-0.70/debian/patches/uxnet-clean-up-callbacks.patch	1970-01-01 01:00:00.000000000 +0100
+++ putty-0.70/debian/patches/uxnet-clean-up-callbacks.patch	2019-03-17 09:36:52.000000000 +0000
@@ -0,0 +1,32 @@
+From 5443fe621319e044a45c30fa3ef2fb7df7c482b4 Mon Sep 17 00:00:00 2001
+From: Simon Tatham <anakin@pobox.com>
+Date: Tue, 29 Jan 2019 20:15:43 +0000
+Subject: uxnet: clean up callbacks when closing a NetSocket.
+
+uxnet.c's method for passing socket errors on to the Plug involves
+setting up a toplevel callback using the NetSocket itself as the
+context. Therefore, it should call delete_callbacks_for_context when
+it destroys a NetSocket. For example, if _two_ socket errors manage to
+occur, and the first one causes the socket to be closed, you need the
+second callback to not happen, or it'll dereference the freed pointer.
+
+Origin: upstream, https://git.tartarus.org/?p=simon/putty.git;a=commitdiff;h=8329d192be5b2bcd1be61634e0b689ecf2a810a8
+Last-Update: 2019-03-16
+
+Patch-Name: uxnet-clean-up-callbacks.patch
+---
+ unix/uxnet.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/unix/uxnet.c b/unix/uxnet.c
+index ddcd9228..6549eca5 100644
+--- a/unix/uxnet.c
++++ b/unix/uxnet.c
+@@ -1028,6 +1028,7 @@ static void sk_tcp_close(Socket sock)
+     close(s->s);
+     if (s->addr)
+         sk_addr_free(s->addr);
++    delete_callbacks_for_context(s);
+     sfree(s);
+ }
+ 
diff -Nru putty-0.70/debian/patches/uxsel-enum.patch putty-0.70/debian/patches/uxsel-enum.patch
--- putty-0.70/debian/patches/uxsel-enum.patch	1970-01-01 01:00:00.000000000 +0100
+++ putty-0.70/debian/patches/uxsel-enum.patch	2019-03-17 09:36:53.000000000 +0000
@@ -0,0 +1,241 @@
+From f40787e64d93fd747bb354fbef8eb6eba16ba323 Mon Sep 17 00:00:00 2001
+From: Simon Tatham <anakin@pobox.com>
+Date: Thu, 7 Feb 2019 18:13:56 +0000
+Subject: Introduce an enum of the uxsel / select_result flags.
+
+Those magic numbers 1,2,4 were getting annoying. Time to replace them
+while I can still remember what they do.
+
+Origin: upstream, https://git.tartarus.org/?p=simon/putty.git;a=commitdiff;h=47202c4e163373736a5bce01730bebc2d9e878fd
+Last-Update: 2019-03-17
+
+Patch-Name: uxsel-enum.patch
+---
+ unix/gtkcomm.c  | 12 ++++++------
+ unix/unix.h     |  1 +
+ unix/uxagentc.c |  4 ++--
+ unix/uxnet.c    | 14 +++++++-------
+ unix/uxpgnt.c   |  6 +++---
+ unix/uxplink.c  |  6 +++---
+ unix/uxpty.c    | 10 +++++-----
+ unix/uxsftp.c   |  6 +++---
+ 8 files changed, 30 insertions(+), 29 deletions(-)
+
+diff --git a/unix/gtkcomm.c b/unix/gtkcomm.c
+index 28884654..aa7acd3f 100644
+--- a/unix/gtkcomm.c
++++ b/unix/gtkcomm.c
+@@ -82,11 +82,11 @@ gboolean fd_input_func(GIOChannel *source, GIOCondition condition,
+      * marker.
+      */
+     if (condition & G_IO_PRI)
+-        select_result(sourcefd, 4);
++        select_result(sourcefd, SELECT_X);
+     if (condition & G_IO_IN)
+-        select_result(sourcefd, 1);
++        select_result(sourcefd, SELECT_R);
+     if (condition & G_IO_OUT)
+-        select_result(sourcefd, 2);
++        select_result(sourcefd, SELECT_W);
+ 
+     return TRUE;
+ }
+@@ -94,11 +94,11 @@ gboolean fd_input_func(GIOChannel *source, GIOCondition condition,
+ void fd_input_func(gpointer data, gint sourcefd, GdkInputCondition condition)
+ {
+     if (condition & GDK_INPUT_EXCEPTION)
+-        select_result(sourcefd, 4);
++        select_result(sourcefd, SELECT_X);
+     if (condition & GDK_INPUT_READ)
+-        select_result(sourcefd, 1);
++        select_result(sourcefd, SELECT_R);
+     if (condition & GDK_INPUT_WRITE)
+-        select_result(sourcefd, 2);
++        select_result(sourcefd, SELECT_W);
+ }
+ #endif
+ 
+diff --git a/unix/unix.h b/unix/unix.h
+index f21d23ff..0bd012cb 100644
+--- a/unix/unix.h
++++ b/unix/unix.h
+@@ -183,6 +183,7 @@ void uxsel_init(void);
+ typedef void (*uxsel_callback_fn)(int fd, int event);
+ void uxsel_set(int fd, int rwx, uxsel_callback_fn callback);
+ void uxsel_del(int fd);
++enum { SELECT_R = 1, SELECT_W = 2, SELECT_X = 4 };
+ void select_result(int fd, int event);
+ int first_fd(int *state, int *rwx);
+ int next_fd(int *state, int *rwx);
+diff --git a/unix/uxagentc.c b/unix/uxagentc.c
+index 51f9a1eb..8f505aab 100644
+--- a/unix/uxagentc.c
++++ b/unix/uxagentc.c
+@@ -102,7 +102,7 @@ static void agent_select_result(int fd, int event)
+ {
+     agent_pending_query *conn;
+ 
+-    assert(event == 1);		       /* not selecting for anything but R */
++    assert(event == SELECT_R);  /* not selecting for anything but R */
+ 
+     conn = find234(agent_pending_queries, &fd, agent_connfind);
+     if (!conn) {
+@@ -197,7 +197,7 @@ agent_pending_query *agent_query(
+ 	agent_pending_queries = newtree234(agent_conncmp);
+     add234(agent_pending_queries, conn);
+ 
+-    uxsel_set(sock, 1, agent_select_result);
++    uxsel_set(sock, SELECT_R, agent_select_result);
+     return conn;
+ 
+     failure:
+diff --git a/unix/uxnet.c b/unix/uxnet.c
+index 35a90ac7..f2e35b38 100644
+--- a/unix/uxnet.c
++++ b/unix/uxnet.c
+@@ -1284,7 +1284,7 @@ static void net_select_result(int fd, int event)
+     noise_ultralight(event);
+ 
+     switch (event) {
+-      case 4:			       /* exceptional */
++      case SELECT_X:                   /* exceptional */
+ 	if (!s->oobinline) {
+ 	    /*
+ 	     * On a non-oobinline socket, this indicates that we
+@@ -1321,7 +1321,7 @@ static void net_select_result(int fd, int event)
+ 	 */
+ 	s->oobpending = TRUE;
+         break;
+-      case 1: 			       /* readable; also acceptance */
++      case SELECT_R:                   /* readable; also acceptance */
+ 	if (s->listener) {
+ 	    /*
+ 	     * On a listening socket, the readability event means a
+@@ -1398,7 +1398,7 @@ static void net_select_result(int fd, int event)
+ 	    plug_receive(s->plug, atmark ? 0 : 1, buf, ret);
+ 	}
+ 	break;
+-      case 2:			       /* writable */
++      case SELECT_W:                   /* writable */
+ 	if (!s->connected) {
+ 	    /*
+ 	     * select() reports a socket as _writable_ when an
+@@ -1536,14 +1536,14 @@ static void uxsel_tell(Actual_Socket s)
+     int rwx = 0;
+     if (!s->pending_error) {
+         if (s->listener) {
+-            rwx |= 1;                  /* read == accept */
++            rwx |= SELECT_R;           /* read == accept */
+         } else {
+             if (!s->connected)
+-                rwx |= 2;              /* write == connect */
++                rwx |= SELECT_W;       /* write == connect */
+             if (s->connected && !s->frozen && !s->incomingeof)
+-                rwx |= 1 | 4;          /* read, except */
++                rwx |= SELECT_R | SELECT_X;
+             if (bufchain_size(&s->output_data))
+-                rwx |= 2;              /* write */
++                rwx |= SELECT_W;
+         }
+     }
+     uxsel_set(s->s, rwx, net_select_result);
+diff --git a/unix/uxpgnt.c b/unix/uxpgnt.c
+index cb85b160..eba08632 100644
+--- a/unix/uxpgnt.c
++++ b/unix/uxpgnt.c
+@@ -945,11 +945,11 @@ void run_agent(void)
+              * past the urgent marker.
+              */
+ 	    if (FD_ISSET(fd, &xset))
+-		select_result(fd, 4);
++		select_result(fd, SELECT_X);
+ 	    if (FD_ISSET(fd, &rset))
+-		select_result(fd, 1);
++		select_result(fd, SELECT_R);
+ 	    if (FD_ISSET(fd, &wset))
+-		select_result(fd, 2);
++		select_result(fd, SELECT_W);
+ 	}
+ 
+         if (signalpipe[0] >= 0 && FD_ISSET(signalpipe[0], &rset)) {
+diff --git a/unix/uxplink.c b/unix/uxplink.c
+index e891d66a..e7289bf2 100644
+--- a/unix/uxplink.c
++++ b/unix/uxplink.c
+@@ -1142,11 +1142,11 @@ int main(int argc, char **argv)
+              * past the urgent marker.
+              */
+ 	    if (FD_ISSET(fd, &xset))
+-		select_result(fd, 4);
++		select_result(fd, SELECT_X);
+ 	    if (FD_ISSET(fd, &rset))
+-		select_result(fd, 1);
++		select_result(fd, SELECT_R);
+ 	    if (FD_ISSET(fd, &wset))
+-		select_result(fd, 2);
++		select_result(fd, SELECT_W);
+ 	}
+ 
+ 	if (FD_ISSET(signalpipe[0], &rset)) {
+diff --git a/unix/uxpty.c b/unix/uxpty.c
+index 618fe9bd..8be507d5 100644
+--- a/unix/uxpty.c
++++ b/unix/uxpty.c
+@@ -600,7 +600,7 @@ void pty_real_select_result(Pty pty, int event, int status)
+ 	    finished = TRUE;
+ 	}
+     } else {
+-	if (event == 1) {
++	if (event == SELECT_R) {
+ 
+ 	    ret = read(pty->master_fd, buf, sizeof(buf));
+ 
+@@ -627,7 +627,7 @@ void pty_real_select_result(Pty pty, int event, int status)
+ 	    } else if (ret > 0) {
+ 		from_backend(pty->frontend, 0, buf, ret);
+ 	    }
+-	} else if (event == 2) {
++	} else if (event == SELECT_W) {
+             /*
+              * Attempt to send data down the pty.
+              */
+@@ -707,9 +707,9 @@ static void pty_uxsel_setup(Pty pty)
+ {
+     int rwx;
+ 
+-    rwx = 1;                           /* always want to read from pty */
++    rwx = SELECT_R;                    /* always want to read from pty */
+     if (bufchain_size(&pty->output_data))
+-        rwx |= 2;                      /* might also want to write to it */
++        rwx |= SELECT_W;               /* might also want to write to it */
+     uxsel_set(pty->master_fd, rwx, pty_select_result);
+ 
+     /*
+@@ -717,7 +717,7 @@ static void pty_uxsel_setup(Pty pty)
+      * backend instances, but it's simplest just to call it every
+      * time; uxsel won't mind.
+      */
+-    uxsel_set(pty_signal_pipe[0], 1, pty_select_result);
++    uxsel_set(pty_signal_pipe[0], SELECT_R, pty_select_result);
+ }
+ 
+ /*
+diff --git a/unix/uxsftp.c b/unix/uxsftp.c
+index 56f7a136..65eda985 100644
+--- a/unix/uxsftp.c
++++ b/unix/uxsftp.c
+@@ -550,11 +550,11 @@ static int ssh_sftp_do_select(int include_stdin, int no_fds_ok)
+ 	 * past the urgent marker.
+ 	 */
+ 	if (FD_ISSET(fd, &xset))
+-	    select_result(fd, 4);
++	    select_result(fd, SELECT_X);
+ 	if (FD_ISSET(fd, &rset))
+-	    select_result(fd, 1);
++	    select_result(fd, SELECT_R);
+ 	if (FD_ISSET(fd, &wset))
+-	    select_result(fd, 2);
++	    select_result(fd, SELECT_W);
+     }
+ 
+     sfree(fdlist);

Reply to: