diff options
| author | Paul Eggert | 2019-01-15 23:51:45 -0800 |
|---|---|---|
| committer | Paul Eggert | 2019-01-15 23:52:47 -0800 |
| commit | e87e6a24c49542111e669b7d0f1a412024663f8e (patch) | |
| tree | ba382d326874795c7b131a7995c67e660650a80c /src | |
| parent | 6b9fa8804533a695094a930d634d2d6617e2b6c7 (diff) | |
| download | emacs-e87e6a24c49542111e669b7d0f1a412024663f8e.tar.gz emacs-e87e6a24c49542111e669b7d0f1a412024663f8e.zip | |
Fix unlikely races with GnuTLS, datagrams
Retry some calls if interrupted at inopportune times.
These were found by code inspection.
* src/gnutls.c (gnutls_try_handshake): Simplify by using
new emacs_gnutls_handle_error API.
(emacs_gnutls_write): Remove GNUTLS_E_AGAIN hack since
emacs_gnutls_handle_error now does that.
Use emacs_gnutls_handle_error only on errors.
(emacs_gnutls_read): Retry if gnutls_record_recv returns
GNUTLS_E_INTERRUPTED, to be consistent with emacs_read.
(emacs_gnutls_handle_error): Return 0 on fatal errors,
-1 (setting errno) on ordinary errors, to simplify callers.
Assume that ERR is negative, since it always is now.
Map non-fatal GnuTLS errors to errno values as best we can.
* src/process.c (read_process_output) [DATAGRAM_SOCKETS]:
Retry recvfrom if it is interrupted, to be consistent with
how things are handled when not a datagram channel.
(send_process) [DATAGRAM_SOCEKTS]: If sendto is interrupted,
process pending signals and retry it, to be consistent with
how things are handled when not a datagram channel.
Diffstat (limited to 'src')
| -rw-r--r-- | src/gnutls.c | 96 | ||||
| -rw-r--r-- | src/process.c | 28 |
2 files changed, 74 insertions, 50 deletions
diff --git a/src/gnutls.c b/src/gnutls.c index d0cb28dc536..63dbcf4162b 100644 --- a/src/gnutls.c +++ b/src/gnutls.c | |||
| @@ -72,7 +72,7 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */ | |||
| 72 | # include "w32.h" | 72 | # include "w32.h" |
| 73 | # endif | 73 | # endif |
| 74 | 74 | ||
| 75 | static bool emacs_gnutls_handle_error (gnutls_session_t, int); | 75 | static int emacs_gnutls_handle_error (gnutls_session_t, int); |
| 76 | 76 | ||
| 77 | static bool gnutls_global_initialized; | 77 | static bool gnutls_global_initialized; |
| 78 | 78 | ||
| @@ -579,15 +579,17 @@ gnutls_try_handshake (struct Lisp_Process *proc) | |||
| 579 | if (non_blocking) | 579 | if (non_blocking) |
| 580 | proc->gnutls_p = true; | 580 | proc->gnutls_p = true; |
| 581 | 581 | ||
| 582 | do | 582 | while ((ret = gnutls_handshake (state)) < 0) |
| 583 | { | 583 | { |
| 584 | ret = gnutls_handshake (state); | 584 | do |
| 585 | emacs_gnutls_handle_error (state, ret); | 585 | ret = gnutls_handshake (state); |
| 586 | while (ret == GNUTLS_E_INTERRUPTED); | ||
| 587 | |||
| 588 | if (0 <= ret || emacs_gnutls_handle_error (state, ret) == 0 | ||
| 589 | || non_blocking) | ||
| 590 | break; | ||
| 586 | maybe_quit (); | 591 | maybe_quit (); |
| 587 | } | 592 | } |
| 588 | while (ret < 0 | ||
| 589 | && gnutls_error_is_fatal (ret) == 0 | ||
| 590 | && ! non_blocking); | ||
| 591 | 593 | ||
| 592 | proc->gnutls_initstage = GNUTLS_STAGE_HANDSHAKE_TRIED; | 594 | proc->gnutls_initstage = GNUTLS_STAGE_HANDSHAKE_TRIED; |
| 593 | 595 | ||
| @@ -682,8 +684,6 @@ emacs_gnutls_transport_set_errno (gnutls_session_t state, int err) | |||
| 682 | ptrdiff_t | 684 | ptrdiff_t |
| 683 | emacs_gnutls_write (struct Lisp_Process *proc, const char *buf, ptrdiff_t nbyte) | 685 | emacs_gnutls_write (struct Lisp_Process *proc, const char *buf, ptrdiff_t nbyte) |
| 684 | { | 686 | { |
| 685 | ssize_t rtnval = 0; | ||
| 686 | ptrdiff_t bytes_written; | ||
| 687 | gnutls_session_t state = proc->gnutls_state; | 687 | gnutls_session_t state = proc->gnutls_state; |
| 688 | 688 | ||
| 689 | if (proc->gnutls_initstage != GNUTLS_STAGE_READY) | 689 | if (proc->gnutls_initstage != GNUTLS_STAGE_READY) |
| @@ -692,25 +692,19 @@ emacs_gnutls_write (struct Lisp_Process *proc, const char *buf, ptrdiff_t nbyte) | |||
| 692 | return 0; | 692 | return 0; |
| 693 | } | 693 | } |
| 694 | 694 | ||
| 695 | bytes_written = 0; | 695 | ptrdiff_t bytes_written = 0; |
| 696 | 696 | ||
| 697 | while (nbyte > 0) | 697 | while (nbyte > 0) |
| 698 | { | 698 | { |
| 699 | rtnval = gnutls_record_send (state, buf, nbyte); | 699 | ssize_t rtnval; |
| 700 | do | ||
| 701 | rtnval = gnutls_record_send (state, buf, nbyte); | ||
| 702 | while (rtnval == GNUTLS_E_INTERRUPTED); | ||
| 700 | 703 | ||
| 701 | if (rtnval < 0) | 704 | if (rtnval < 0) |
| 702 | { | 705 | { |
| 703 | if (rtnval == GNUTLS_E_INTERRUPTED) | 706 | emacs_gnutls_handle_error (state, rtnval); |
| 704 | continue; | 707 | break; |
| 705 | else | ||
| 706 | { | ||
| 707 | /* If we get GNUTLS_E_AGAIN, then set errno | ||
| 708 | appropriately so that send_process retries the | ||
| 709 | correct way instead of erroring out. */ | ||
| 710 | if (rtnval == GNUTLS_E_AGAIN) | ||
| 711 | errno = EAGAIN; | ||
| 712 | break; | ||
| 713 | } | ||
| 714 | } | 708 | } |
| 715 | 709 | ||
| 716 | buf += rtnval; | 710 | buf += rtnval; |
| @@ -718,14 +712,12 @@ emacs_gnutls_write (struct Lisp_Process *proc, const char *buf, ptrdiff_t nbyte) | |||
| 718 | bytes_written += rtnval; | 712 | bytes_written += rtnval; |
| 719 | } | 713 | } |
| 720 | 714 | ||
| 721 | emacs_gnutls_handle_error (state, rtnval); | ||
| 722 | return (bytes_written); | 715 | return (bytes_written); |
| 723 | } | 716 | } |
| 724 | 717 | ||
| 725 | ptrdiff_t | 718 | ptrdiff_t |
| 726 | emacs_gnutls_read (struct Lisp_Process *proc, char *buf, ptrdiff_t nbyte) | 719 | emacs_gnutls_read (struct Lisp_Process *proc, char *buf, ptrdiff_t nbyte) |
| 727 | { | 720 | { |
| 728 | ssize_t rtnval; | ||
| 729 | gnutls_session_t state = proc->gnutls_state; | 721 | gnutls_session_t state = proc->gnutls_state; |
| 730 | 722 | ||
| 731 | if (proc->gnutls_initstage != GNUTLS_STAGE_READY) | 723 | if (proc->gnutls_initstage != GNUTLS_STAGE_READY) |
| @@ -734,19 +726,18 @@ emacs_gnutls_read (struct Lisp_Process *proc, char *buf, ptrdiff_t nbyte) | |||
| 734 | return -1; | 726 | return -1; |
| 735 | } | 727 | } |
| 736 | 728 | ||
| 737 | rtnval = gnutls_record_recv (state, buf, nbyte); | 729 | ssize_t rtnval; |
| 730 | do | ||
| 731 | rtnval = gnutls_record_recv (state, buf, nbyte); | ||
| 732 | while (rtnval == GNUTLS_E_INTERRUPTED); | ||
| 733 | |||
| 738 | if (rtnval >= 0) | 734 | if (rtnval >= 0) |
| 739 | return rtnval; | 735 | return rtnval; |
| 740 | else if (rtnval == GNUTLS_E_UNEXPECTED_PACKET_LENGTH) | 736 | else if (rtnval == GNUTLS_E_UNEXPECTED_PACKET_LENGTH) |
| 741 | /* The peer closed the connection. */ | 737 | /* The peer closed the connection. */ |
| 742 | return 0; | 738 | return 0; |
| 743 | else if (emacs_gnutls_handle_error (state, rtnval)) | 739 | else |
| 744 | /* non-fatal error */ | 740 | return emacs_gnutls_handle_error (state, rtnval); |
| 745 | return -1; | ||
| 746 | else { | ||
| 747 | /* a fatal error occurred */ | ||
| 748 | return 0; | ||
| 749 | } | ||
| 750 | } | 741 | } |
| 751 | 742 | ||
| 752 | static char const * | 743 | static char const * |
| @@ -757,25 +748,24 @@ emacs_gnutls_strerror (int err) | |||
| 757 | } | 748 | } |
| 758 | 749 | ||
| 759 | /* Report a GnuTLS error to the user. | 750 | /* Report a GnuTLS error to the user. |
| 760 | Return true if the error code was successfully handled. */ | 751 | SESSION is the GnuTLS session, ERR is the (negative) GnuTLS error code. |
| 761 | static bool | 752 | Return 0 if the error was fatal, -1 (setting errno) otherwise so |
| 753 | that the caller can notice the error and attempt a repair. */ | ||
| 754 | static int | ||
| 762 | emacs_gnutls_handle_error (gnutls_session_t session, int err) | 755 | emacs_gnutls_handle_error (gnutls_session_t session, int err) |
| 763 | { | 756 | { |
| 764 | int max_log_level = 0; | 757 | int ret; |
| 765 | |||
| 766 | bool ret; | ||
| 767 | 758 | ||
| 768 | /* TODO: use a Lisp_Object generated by gnutls_make_error? */ | 759 | /* TODO: use a Lisp_Object generated by gnutls_make_error? */ |
| 769 | if (err >= 0) | ||
| 770 | return 1; | ||
| 771 | 760 | ||
| 772 | check_memory_full (err); | 761 | check_memory_full (err); |
| 773 | 762 | ||
| 774 | max_log_level = global_gnutls_log_level; | 763 | int max_log_level = global_gnutls_log_level; |
| 775 | 764 | ||
| 776 | /* TODO: use gnutls-error-fatalp and gnutls-error-string. */ | 765 | /* TODO: use gnutls-error-fatalp and gnutls-error-string. */ |
| 777 | 766 | ||
| 778 | char const *str = emacs_gnutls_strerror (err); | 767 | char const *str = emacs_gnutls_strerror (err); |
| 768 | int errnum = EINVAL; | ||
| 779 | 769 | ||
| 780 | if (gnutls_error_is_fatal (err)) | 770 | if (gnutls_error_is_fatal (err)) |
| 781 | { | 771 | { |
| @@ -789,11 +779,11 @@ emacs_gnutls_handle_error (gnutls_session_t session, int err) | |||
| 789 | # endif | 779 | # endif |
| 790 | 780 | ||
| 791 | GNUTLS_LOG2 (level, max_log_level, "fatal error:", str); | 781 | GNUTLS_LOG2 (level, max_log_level, "fatal error:", str); |
| 792 | ret = false; | 782 | ret = 0; |
| 793 | } | 783 | } |
| 794 | else | 784 | else |
| 795 | { | 785 | { |
| 796 | ret = true; | 786 | ret = -1; |
| 797 | 787 | ||
| 798 | switch (err) | 788 | switch (err) |
| 799 | { | 789 | { |
| @@ -809,6 +799,26 @@ emacs_gnutls_handle_error (gnutls_session_t session, int err) | |||
| 809 | "non-fatal error:", | 799 | "non-fatal error:", |
| 810 | str); | 800 | str); |
| 811 | } | 801 | } |
| 802 | |||
| 803 | switch (err) | ||
| 804 | { | ||
| 805 | case GNUTLS_E_AGAIN: | ||
| 806 | errnum = EAGAIN; | ||
| 807 | break; | ||
| 808 | |||
| 809 | # ifdef EMSGSIZE | ||
| 810 | case GNUTLS_E_LARGE_PACKET: | ||
| 811 | case GNUTLS_E_PUSH_ERROR: | ||
| 812 | errnum = EMSGSIZE; | ||
| 813 | break; | ||
| 814 | # endif | ||
| 815 | |||
| 816 | # if defined HAVE_GNUTLS3 && defined ECONNRESET | ||
| 817 | case GNUTLS_E_PREMATURE_TERMINATION: | ||
| 818 | errnum = ECONNRESET; | ||
| 819 | break; | ||
| 820 | # endif | ||
| 821 | } | ||
| 812 | } | 822 | } |
| 813 | 823 | ||
| 814 | if (err == GNUTLS_E_WARNING_ALERT_RECEIVED | 824 | if (err == GNUTLS_E_WARNING_ALERT_RECEIVED |
| @@ -822,6 +832,8 @@ emacs_gnutls_handle_error (gnutls_session_t session, int err) | |||
| 822 | 832 | ||
| 823 | GNUTLS_LOG2 (level, max_log_level, "Received alert: ", str); | 833 | GNUTLS_LOG2 (level, max_log_level, "Received alert: ", str); |
| 824 | } | 834 | } |
| 835 | |||
| 836 | errno = errnum; | ||
| 825 | return ret; | 837 | return ret; |
| 826 | } | 838 | } |
| 827 | 839 | ||
diff --git a/src/process.c b/src/process.c index 06555bac4c0..c0741403b57 100644 --- a/src/process.c +++ b/src/process.c | |||
| @@ -5840,7 +5840,8 @@ read_and_dispose_of_process_output (struct Lisp_Process *p, char *chars, | |||
| 5840 | 5840 | ||
| 5841 | /* Read pending output from the process channel, | 5841 | /* Read pending output from the process channel, |
| 5842 | starting with our buffered-ahead character if we have one. | 5842 | starting with our buffered-ahead character if we have one. |
| 5843 | Yield number of decoded characters read. | 5843 | Yield number of decoded characters read, |
| 5844 | or -1 (setting errno) if there is a read error. | ||
| 5844 | 5845 | ||
| 5845 | This function reads at most 4096 characters. | 5846 | This function reads at most 4096 characters. |
| 5846 | If you want to read all available subprocess output, | 5847 | If you want to read all available subprocess output, |
| @@ -5870,8 +5871,10 @@ read_process_output (Lisp_Object proc, int channel) | |||
| 5870 | if (DATAGRAM_CHAN_P (channel)) | 5871 | if (DATAGRAM_CHAN_P (channel)) |
| 5871 | { | 5872 | { |
| 5872 | socklen_t len = datagram_address[channel].len; | 5873 | socklen_t len = datagram_address[channel].len; |
| 5873 | nbytes = recvfrom (channel, chars + carryover, readmax, | 5874 | do |
| 5874 | 0, datagram_address[channel].sa, &len); | 5875 | nbytes = recvfrom (channel, chars + carryover, readmax, |
| 5876 | 0, datagram_address[channel].sa, &len); | ||
| 5877 | while (nbytes < 0 && errno == EINTR); | ||
| 5875 | } | 5878 | } |
| 5876 | else | 5879 | else |
| 5877 | #endif | 5880 | #endif |
| @@ -5921,8 +5924,6 @@ read_process_output (Lisp_Object proc, int channel) | |||
| 5921 | 5924 | ||
| 5922 | p->decoding_carryover = 0; | 5925 | p->decoding_carryover = 0; |
| 5923 | 5926 | ||
| 5924 | /* At this point, NBYTES holds number of bytes just received | ||
| 5925 | (including the one in proc_buffered_char[channel]). */ | ||
| 5926 | if (nbytes <= 0) | 5927 | if (nbytes <= 0) |
| 5927 | { | 5928 | { |
| 5928 | if (nbytes < 0 || coding->mode & CODING_MODE_LAST_BLOCK) | 5929 | if (nbytes < 0 || coding->mode & CODING_MODE_LAST_BLOCK) |
| @@ -5930,6 +5931,9 @@ read_process_output (Lisp_Object proc, int channel) | |||
| 5930 | coding->mode |= CODING_MODE_LAST_BLOCK; | 5931 | coding->mode |= CODING_MODE_LAST_BLOCK; |
| 5931 | } | 5932 | } |
| 5932 | 5933 | ||
| 5934 | /* At this point, NBYTES holds number of bytes just received | ||
| 5935 | (including the one in proc_buffered_char[channel]). */ | ||
| 5936 | |||
| 5933 | /* Ignore carryover, it's been added by a previous iteration already. */ | 5937 | /* Ignore carryover, it's been added by a previous iteration already. */ |
| 5934 | p->nbytes_read += nbytes; | 5938 | p->nbytes_read += nbytes; |
| 5935 | 5939 | ||
| @@ -6372,9 +6376,17 @@ send_process (Lisp_Object proc, const char *buf, ptrdiff_t len, | |||
| 6372 | #ifdef DATAGRAM_SOCKETS | 6376 | #ifdef DATAGRAM_SOCKETS |
| 6373 | if (DATAGRAM_CHAN_P (outfd)) | 6377 | if (DATAGRAM_CHAN_P (outfd)) |
| 6374 | { | 6378 | { |
| 6375 | rv = sendto (outfd, cur_buf, cur_len, | 6379 | while (true) |
| 6376 | 0, datagram_address[outfd].sa, | 6380 | { |
| 6377 | datagram_address[outfd].len); | 6381 | rv = sendto (outfd, cur_buf, cur_len, 0, |
| 6382 | datagram_address[outfd].sa, | ||
| 6383 | datagram_address[outfd].len); | ||
| 6384 | if (! (rv < 0 && errno == EINTR)) | ||
| 6385 | break; | ||
| 6386 | if (pending_signals) | ||
| 6387 | process_pending_signals (); | ||
| 6388 | } | ||
| 6389 | |||
| 6378 | if (rv >= 0) | 6390 | if (rv >= 0) |
| 6379 | written = rv; | 6391 | written = rv; |
| 6380 | else if (errno == EMSGSIZE) | 6392 | else if (errno == EMSGSIZE) |