Package: release.debian.org Severity: normal User: [email protected] 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 [[email protected]]
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 <[email protected]> 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 <[email protected]> +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 <[email protected]> +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 <[email protected]> +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 <[email protected]> +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 <[email protected]> +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 <[email protected]> +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 <[email protected]> +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 <[email protected]> +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 <[email protected]> +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 <[email protected]> +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 <[email protected]> +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 <[email protected]> +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 <[email protected]> +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);

