--- Begin Message ---
Package: release.debian.org
Severity: normal
User: release.debian....@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 [cjwat...@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 <cjwat...@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 <ana...@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 <ana...@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 <ana...@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 <ana...@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 <ana...@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 <ana...@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 <ana...@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 <ana...@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 <ana...@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 <ana...@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 <ana...@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 <ana...@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 <ana...@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);
--- End Message ---