Blame SOURCES/nghttp2-1.31.1-CVE-2019-9511-and-CVE-2019-9513.patch

e008a4
From 4b7aefd8fd1612d455f2f128c09230335ed0cee6 Mon Sep 17 00:00:00 2001
e008a4
From: Tatsuhiro Tsujikawa <tatsuhiro.t@gmail.com>
e008a4
Date: Tue, 6 Aug 2019 20:48:50 +0900
e008a4
Subject: [PATCH 1/3] nghttpx: Fix request stall
e008a4
e008a4
Fix request stall if backend connection is reused and buffer is full.
e008a4
e008a4
Upstream-commit: db2f612a30d54aa152ce5530fa1d683738baa4d1
e008a4
Signed-off-by: Kamil Dudka <kdudka@redhat.com>
e008a4
---
e008a4
 integration-tests/nghttpx_http1_test.go | 29 +++++++++++++++++++++++++
e008a4
 integration-tests/server_tester.go      |  4 +++-
e008a4
 src/shrpx_downstream.cc                 | 12 +++++++++-
e008a4
 src/shrpx_downstream.h                  |  4 ++++
e008a4
 src/shrpx_http_downstream_connection.cc | 16 +++++++++++++-
e008a4
 src/shrpx_https_upstream.cc             |  4 +---
e008a4
 6 files changed, 63 insertions(+), 6 deletions(-)
e008a4
e008a4
diff --git a/integration-tests/nghttpx_http1_test.go b/integration-tests/nghttpx_http1_test.go
e008a4
index a765333..3d41677 100644
e008a4
--- a/integration-tests/nghttpx_http1_test.go
e008a4
+++ b/integration-tests/nghttpx_http1_test.go
e008a4
@@ -625,6 +625,35 @@ func TestH1H1HTTPSRedirectPort(t *testing.T) {
e008a4
 	}
e008a4
 }
e008a4
 
e008a4
+// TestH1H1POSTRequests tests that server can handle 2 requests with
e008a4
+// request body.
e008a4
+func TestH1H1POSTRequests(t *testing.T) {
e008a4
+	st := newServerTester(nil, t, noopHandler)
e008a4
+	defer st.Close()
e008a4
+
e008a4
+	res, err := st.http1(requestParam{
e008a4
+		name: "TestH1H1POSTRequestsNo1",
e008a4
+		body: make([]byte, 1),
e008a4
+	})
e008a4
+	if err != nil {
e008a4
+		t.Fatalf("Error st.http1() = %v", err)
e008a4
+	}
e008a4
+	if got, want := res.status, 200; got != want {
e008a4
+		t.Errorf("res.status: %v; want %v", got, want)
e008a4
+	}
e008a4
+
e008a4
+	res, err = st.http1(requestParam{
e008a4
+		name: "TestH1H1POSTRequestsNo2",
e008a4
+		body: make([]byte, 65536),
e008a4
+	})
e008a4
+	if err != nil {
e008a4
+		t.Fatalf("Error st.http1() = %v", err)
e008a4
+	}
e008a4
+	if got, want := res.status, 200; got != want {
e008a4
+		t.Errorf("res.status: %v; want %v", got, want)
e008a4
+	}
e008a4
+}
e008a4
+
e008a4
 // // TestH1H2ConnectFailure tests that server handles the situation that
e008a4
 // // connection attempt to HTTP/2 backend failed.
e008a4
 // func TestH1H2ConnectFailure(t *testing.T) {
e008a4
diff --git a/integration-tests/server_tester.go b/integration-tests/server_tester.go
e008a4
index d145519..1156986 100644
e008a4
--- a/integration-tests/server_tester.go
e008a4
+++ b/integration-tests/server_tester.go
e008a4
@@ -662,7 +662,9 @@ func cloneHeader(h http.Header) http.Header {
e008a4
 	return h2
e008a4
 }
e008a4
 
e008a4
-func noopHandler(w http.ResponseWriter, r *http.Request) {}
e008a4
+func noopHandler(w http.ResponseWriter, r *http.Request) {
e008a4
+	ioutil.ReadAll(r.Body)
e008a4
+}
e008a4
 
e008a4
 type APIResponse struct {
e008a4
 	Status string                 `json:"status,omitempty"`
e008a4
diff --git a/src/shrpx_downstream.cc b/src/shrpx_downstream.cc
e008a4
index 360a9a9..48db65b 100644
e008a4
--- a/src/shrpx_downstream.cc
e008a4
+++ b/src/shrpx_downstream.cc
e008a4
@@ -144,7 +144,8 @@ Downstream::Downstream(Upstream *upstream, MemchunkPool *mcpool,
e008a4
       request_header_sent_(false),
e008a4
       accesslog_written_(false),
e008a4
       new_affinity_cookie_(false),
e008a4
-      blocked_request_data_eof_(false) {
e008a4
+      blocked_request_data_eof_(false),
e008a4
+      expect_100_continue_(false) {
e008a4
 
e008a4
   auto &timeoutconf = get_config()->http2.timeout;
e008a4
 
e008a4
@@ -807,6 +808,11 @@ void Downstream::inspect_http1_request() {
e008a4
       chunked_request_ = true;
e008a4
     }
e008a4
   }
e008a4
+
e008a4
+  auto expect = req_.fs.header(http2::HD_EXPECT);
e008a4
+  expect_100_continue_ =
e008a4
+      expect &&
e008a4
+      util::strieq(expect->value, StringRef::from_lit("100-continue"));
e008a4
 }
e008a4
 
e008a4
 void Downstream::inspect_http1_response() {
e008a4
@@ -1103,4 +1109,8 @@ bool Downstream::get_blocked_request_data_eof() const {
e008a4
   return blocked_request_data_eof_;
e008a4
 }
e008a4
 
e008a4
+bool Downstream::get_expect_100_continue() const {
e008a4
+  return expect_100_continue_;
e008a4
+}
e008a4
+
e008a4
 } // namespace shrpx
e008a4
diff --git a/src/shrpx_downstream.h b/src/shrpx_downstream.h
e008a4
index c81fcf6..b9a851f 100644
e008a4
--- a/src/shrpx_downstream.h
e008a4
+++ b/src/shrpx_downstream.h
e008a4
@@ -466,6 +466,8 @@ public:
e008a4
     EVENT_TIMEOUT = 0x2,
e008a4
   };
e008a4
 
e008a4
+  bool get_expect_100_continue() const;
e008a4
+
e008a4
   enum {
e008a4
     DISPATCH_NONE,
e008a4
     DISPATCH_PENDING,
e008a4
@@ -556,6 +558,8 @@ private:
e008a4
   // true if eof is received from client before sending header fields
e008a4
   // to backend.
e008a4
   bool blocked_request_data_eof_;
e008a4
+  // true if request contains "expect: 100-continue" header field.
e008a4
+  bool expect_100_continue_;
e008a4
 };
e008a4
 
e008a4
 } // namespace shrpx
e008a4
diff --git a/src/shrpx_http_downstream_connection.cc b/src/shrpx_http_downstream_connection.cc
e008a4
index f50c0f4..85ca947 100644
e008a4
--- a/src/shrpx_http_downstream_connection.cc
e008a4
+++ b/src/shrpx_http_downstream_connection.cc
e008a4
@@ -698,7 +698,8 @@ int HttpDownstreamConnection::push_request_headers() {
e008a4
   // signal_write() when we received request body chunk, and it
e008a4
   // enables us to send headers and data in one writev system call.
e008a4
   if (connect_method || downstream_->get_blocked_request_buf()->rleft() ||
e008a4
-      (!req.http2_expect_body && req.fs.content_length == 0)) {
e008a4
+      (!req.http2_expect_body && req.fs.content_length == 0) ||
e008a4
+      downstream_->get_expect_100_continue()) {
e008a4
     signal_write();
e008a4
   }
e008a4
 
e008a4
@@ -1172,6 +1173,19 @@ int HttpDownstreamConnection::write_reuse_first() {
e008a4
 
e008a4
   reuse_first_write_done_ = true;
e008a4
 
e008a4
+  // upstream->resume_read() might be called in
e008a4
+  // write_tls()/write_clear(), but before blocked_request_buf_ is
e008a4
+  // reset.  So upstream read might still be blocked.  Let's do it
e008a4
+  // again here.
e008a4
+  auto input = downstream_->get_request_buf();
e008a4
+  if (input->rleft() == 0) {
e008a4
+    auto upstream = downstream_->get_upstream();
e008a4
+    auto &req = downstream_->request();
e008a4
+
e008a4
+    upstream->resume_read(SHRPX_NO_BUFFER, downstream_,
e008a4
+                          req.unconsumed_body_length);
e008a4
+  }
e008a4
+
e008a4
   return 0;
e008a4
 }
e008a4
 
e008a4
diff --git a/src/shrpx_https_upstream.cc b/src/shrpx_https_upstream.cc
e008a4
index 452ec90..96ca2cd 100644
e008a4
--- a/src/shrpx_https_upstream.cc
e008a4
+++ b/src/shrpx_https_upstream.cc
e008a4
@@ -467,9 +467,7 @@ int htp_hdrs_completecb(http_parser *htp) {
e008a4
     // and let them decide whether responds with 100 Continue or not.
e008a4
     // For alternative mode, we have no backend, so just send 100
e008a4
     // Continue here to make the client happy.
e008a4
-    auto expect = req.fs.header(http2::HD_EXPECT);
e008a4
-    if (expect &&
e008a4
-        util::strieq(expect->value, StringRef::from_lit("100-continue"))) {
e008a4
+    if (downstream->get_expect_100_continue()) {
e008a4
       auto output = downstream->get_response_buf();
e008a4
       constexpr auto res = StringRef::from_lit("HTTP/1.1 100 Continue\r\n\r\n");
e008a4
       output->append(res);
e008a4
-- 
e008a4
2.20.1
e008a4
e008a4
e008a4
From 589a98eba0b3c7a4dbb2262c60b609cac2b1f838 Mon Sep 17 00:00:00 2001
e008a4
From: Tatsuhiro Tsujikawa <tatsuhiro.t@gmail.com>
e008a4
Date: Tue, 25 Jun 2019 22:33:35 +0900
e008a4
Subject: [PATCH 2/3] Add nghttp2_option_set_max_outbound_ack
e008a4
e008a4
Upstream-commit: a76d0723b5f52902139ff453e0ec840673e86e75
e008a4
Signed-off-by: Kamil Dudka <kdudka@redhat.com>
e008a4
---
e008a4
 doc/Makefile.am                |  1 +
e008a4
 lib/includes/nghttp2/nghttp2.h | 11 +++++++++++
e008a4
 lib/nghttp2_option.c           |  5 +++++
e008a4
 lib/nghttp2_option.h           |  5 +++++
e008a4
 lib/nghttp2_session.c          |  9 +++++++--
e008a4
 lib/nghttp2_session.h          |  8 ++++++--
e008a4
 tests/nghttp2_session_test.c   |  4 ++--
e008a4
 7 files changed, 37 insertions(+), 6 deletions(-)
e008a4
e008a4
diff --git a/doc/Makefile.am b/doc/Makefile.am
e008a4
index 07cd34e..66e5ba3 100644
e008a4
--- a/doc/Makefile.am
e008a4
+++ b/doc/Makefile.am
e008a4
@@ -67,6 +67,7 @@ APIDOCS= \
e008a4
 	nghttp2_option_set_no_recv_client_magic.rst \
e008a4
 	nghttp2_option_set_peer_max_concurrent_streams.rst \
e008a4
 	nghttp2_option_set_user_recv_extension_type.rst \
e008a4
+	nghttp2_option_set_max_outbound_ack.rst \
e008a4
 	nghttp2_pack_settings_payload.rst \
e008a4
 	nghttp2_priority_spec_check_default.rst \
e008a4
 	nghttp2_priority_spec_default_init.rst \
e008a4
diff --git a/lib/includes/nghttp2/nghttp2.h b/lib/includes/nghttp2/nghttp2.h
e008a4
index 14f8950..137a675 100644
e008a4
--- a/lib/includes/nghttp2/nghttp2.h
e008a4
+++ b/lib/includes/nghttp2/nghttp2.h
e008a4
@@ -2637,6 +2637,17 @@ nghttp2_option_set_max_deflate_dynamic_table_size(nghttp2_option *option,
e008a4
 NGHTTP2_EXTERN void nghttp2_option_set_no_closed_streams(nghttp2_option *option,
e008a4
                                                          int val);
e008a4
 
e008a4
+/**
e008a4
+ * @function
e008a4
+ *
e008a4
+ * This function sets the maximum number of outgoing SETTINGS ACK and
e008a4
+ * PING ACK frames retained in :type:`nghttp2_session` object.  If
e008a4
+ * more than those frames are retained, the peer is considered to be
e008a4
+ * misbehaving and session will be closed.  The default value is 1000.
e008a4
+ */
e008a4
+NGHTTP2_EXTERN void nghttp2_option_set_max_outbound_ack(nghttp2_option *option,
e008a4
+                                                        size_t val);
e008a4
+
e008a4
 /**
e008a4
  * @function
e008a4
  *
e008a4
diff --git a/lib/nghttp2_option.c b/lib/nghttp2_option.c
e008a4
index aec5dcf..ae22493 100644
e008a4
--- a/lib/nghttp2_option.c
e008a4
+++ b/lib/nghttp2_option.c
e008a4
@@ -116,3 +116,8 @@ void nghttp2_option_set_no_closed_streams(nghttp2_option *option, int val) {
e008a4
   option->opt_set_mask |= NGHTTP2_OPT_NO_CLOSED_STREAMS;
e008a4
   option->no_closed_streams = val;
e008a4
 }
e008a4
+
e008a4
+void nghttp2_option_set_max_outbound_ack(nghttp2_option *option, size_t val) {
e008a4
+  option->opt_set_mask |= NGHTTP2_OPT_MAX_OUTBOUND_ACK;
e008a4
+  option->max_outbound_ack = val;
e008a4
+}
e008a4
diff --git a/lib/nghttp2_option.h b/lib/nghttp2_option.h
e008a4
index c743e33..86d31f7 100644
e008a4
--- a/lib/nghttp2_option.h
e008a4
+++ b/lib/nghttp2_option.h
e008a4
@@ -66,6 +66,7 @@ typedef enum {
e008a4
   NGHTTP2_OPT_MAX_SEND_HEADER_BLOCK_LENGTH = 1 << 8,
e008a4
   NGHTTP2_OPT_MAX_DEFLATE_DYNAMIC_TABLE_SIZE = 1 << 9,
e008a4
   NGHTTP2_OPT_NO_CLOSED_STREAMS = 1 << 10,
e008a4
+  NGHTTP2_OPT_MAX_OUTBOUND_ACK = 1 << 11,
e008a4
 } nghttp2_option_flag;
e008a4
 
e008a4
 /**
e008a4
@@ -80,6 +81,10 @@ struct nghttp2_option {
e008a4
    * NGHTTP2_OPT_MAX_DEFLATE_DYNAMIC_TABLE_SIZE
e008a4
    */
e008a4
   size_t max_deflate_dynamic_table_size;
e008a4
+  /**
e008a4
+   * NGHTTP2_OPT_MAX_OUTBOUND_ACK
e008a4
+   */
e008a4
+  size_t max_outbound_ack;
e008a4
   /**
e008a4
    * Bitwise OR of nghttp2_option_flag to determine that which fields
e008a4
    * are specified.
e008a4
diff --git a/lib/nghttp2_session.c b/lib/nghttp2_session.c
e008a4
index c58f059..8628cc7 100644
e008a4
--- a/lib/nghttp2_session.c
e008a4
+++ b/lib/nghttp2_session.c
e008a4
@@ -457,6 +457,7 @@ static int session_new(nghttp2_session **session_ptr,
e008a4
   (*session_ptr)->remote_settings.max_concurrent_streams = 100;
e008a4
 
e008a4
   (*session_ptr)->max_send_header_block_length = NGHTTP2_MAX_HEADERSLEN;
e008a4
+  (*session_ptr)->max_outbound_ack = NGHTTP2_DEFAULT_MAX_OBQ_FLOOD_ITEM;
e008a4
 
e008a4
   if (option) {
e008a4
     if ((option->opt_set_mask & NGHTTP2_OPT_NO_AUTO_WINDOW_UPDATE) &&
e008a4
@@ -516,6 +517,10 @@ static int session_new(nghttp2_session **session_ptr,
e008a4
         option->no_closed_streams) {
e008a4
       (*session_ptr)->opt_flags |= NGHTTP2_OPTMASK_NO_CLOSED_STREAMS;
e008a4
     }
e008a4
+
e008a4
+    if (option->opt_set_mask & NGHTTP2_OPT_MAX_OUTBOUND_ACK) {
e008a4
+      (*session_ptr)->max_outbound_ack = option->max_outbound_ack;
e008a4
+    }
e008a4
   }
e008a4
 
e008a4
   rv = nghttp2_hd_deflate_init2(&(*session_ptr)->hd_deflater,
e008a4
@@ -6831,7 +6836,7 @@ int nghttp2_session_add_ping(nghttp2_session *session, uint8_t flags,
e008a4
   mem = &session->mem;
e008a4
 
e008a4
   if ((flags & NGHTTP2_FLAG_ACK) &&
e008a4
-      session->obq_flood_counter_ >= NGHTTP2_MAX_OBQ_FLOOD_ITEM) {
e008a4
+      session->obq_flood_counter_ >= session->max_outbound_ack) {
e008a4
     return NGHTTP2_ERR_FLOODED;
e008a4
   }
e008a4
 
e008a4
@@ -6976,7 +6981,7 @@ int nghttp2_session_add_settings(nghttp2_session *session, uint8_t flags,
e008a4
       return NGHTTP2_ERR_INVALID_ARGUMENT;
e008a4
     }
e008a4
 
e008a4
-    if (session->obq_flood_counter_ >= NGHTTP2_MAX_OBQ_FLOOD_ITEM) {
e008a4
+    if (session->obq_flood_counter_ >= session->max_outbound_ack) {
e008a4
       return NGHTTP2_ERR_FLOODED;
e008a4
     }
e008a4
   }
e008a4
diff --git a/lib/nghttp2_session.h b/lib/nghttp2_session.h
e008a4
index c7cb27d..d9e2846 100644
e008a4
--- a/lib/nghttp2_session.h
e008a4
+++ b/lib/nghttp2_session.h
e008a4
@@ -97,7 +97,7 @@ typedef struct {
e008a4
    response frames are stacked up, which leads to memory exhaustion.
e008a4
    The value selected here is arbitrary, but safe value and if we have
e008a4
    these frames in this number, it is considered suspicious. */
e008a4
-#define NGHTTP2_MAX_OBQ_FLOOD_ITEM 10000
e008a4
+#define NGHTTP2_DEFAULT_MAX_OBQ_FLOOD_ITEM 1000
e008a4
 
e008a4
 /* The default value of maximum number of concurrent streams. */
e008a4
 #define NGHTTP2_DEFAULT_MAX_CONCURRENT_STREAMS 0xffffffffu
e008a4
@@ -260,8 +260,12 @@ struct nghttp2_session {
e008a4
   size_t num_idle_streams;
e008a4
   /* The number of bytes allocated for nvbuf */
e008a4
   size_t nvbuflen;
e008a4
-  /* Counter for detecting flooding in outbound queue */
e008a4
+  /* Counter for detecting flooding in outbound queue.  If it exceeds
e008a4
+     max_outbound_ack, session will be closed. */
e008a4
   size_t obq_flood_counter_;
e008a4
+  /* The maximum number of outgoing SETTINGS ACK and PING ACK in
e008a4
+     outbound queue. */
e008a4
+  size_t max_outbound_ack;
e008a4
   /* The maximum length of header block to send.  Calculated by the
e008a4
      same way as nghttp2_hd_deflate_bound() does. */
e008a4
   size_t max_send_header_block_length;
e008a4
diff --git a/tests/nghttp2_session_test.c b/tests/nghttp2_session_test.c
e008a4
index 783b0ed..debec59 100644
e008a4
--- a/tests/nghttp2_session_test.c
e008a4
+++ b/tests/nghttp2_session_test.c
e008a4
@@ -9894,7 +9894,7 @@ void test_nghttp2_session_flooding(void) {
e008a4
 
e008a4
   buf = &bufs.head->buf;
e008a4
 
e008a4
-  for (i = 0; i < NGHTTP2_MAX_OBQ_FLOOD_ITEM; ++i) {
e008a4
+  for (i = 0; i < NGHTTP2_DEFAULT_MAX_OBQ_FLOOD_ITEM; ++i) {
e008a4
     CU_ASSERT(
e008a4
         (ssize_t)nghttp2_buf_len(buf) ==
e008a4
         nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)));
e008a4
@@ -9916,7 +9916,7 @@ void test_nghttp2_session_flooding(void) {
e008a4
 
e008a4
   buf = &bufs.head->buf;
e008a4
 
e008a4
-  for (i = 0; i < NGHTTP2_MAX_OBQ_FLOOD_ITEM; ++i) {
e008a4
+  for (i = 0; i < NGHTTP2_DEFAULT_MAX_OBQ_FLOOD_ITEM; ++i) {
e008a4
     CU_ASSERT(
e008a4
         (ssize_t)nghttp2_buf_len(buf) ==
e008a4
         nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)));
e008a4
-- 
e008a4
2.20.1
e008a4
e008a4
e008a4
From e32b3e4c9df4abb83ca1c1c41901fadbae44699b Mon Sep 17 00:00:00 2001
e008a4
From: Tatsuhiro Tsujikawa <tatsuhiro.t@gmail.com>
e008a4
Date: Tue, 25 Jun 2019 22:38:43 +0900
e008a4
Subject: [PATCH 3/3] Don't read too greedily
e008a4
e008a4
Upstream-commit: 83d362c6d21f76599b86e7b94cd1992288a1d43c
e008a4
Signed-off-by: Kamil Dudka <kdudka@redhat.com>
e008a4
---
e008a4
 src/HttpServer.cc           | 2 ++
e008a4
 src/shrpx_client_handler.cc | 9 +++++++--
e008a4
 2 files changed, 9 insertions(+), 2 deletions(-)
e008a4
e008a4
diff --git a/src/HttpServer.cc b/src/HttpServer.cc
e008a4
index b3e35ef..a75cee4 100644
e008a4
--- a/src/HttpServer.cc
e008a4
+++ b/src/HttpServer.cc
e008a4
@@ -650,6 +650,7 @@ int Http2Handler::read_clear() {
e008a4
       }
e008a4
       return -1;
e008a4
     }
e008a4
+    break;
e008a4
   }
e008a4
 
e008a4
   return write_(*this);
e008a4
@@ -775,6 +776,7 @@ int Http2Handler::read_tls() {
e008a4
       }
e008a4
       return -1;
e008a4
     }
e008a4
+    break;
e008a4
   }
e008a4
 
e008a4
 fin:
e008a4
diff --git a/src/shrpx_client_handler.cc b/src/shrpx_client_handler.cc
e008a4
index 21430dd..fa1fc87 100644
e008a4
--- a/src/shrpx_client_handler.cc
e008a4
+++ b/src/shrpx_client_handler.cc
e008a4
@@ -111,6 +111,7 @@ void writecb(struct ev_loop *loop, ev_io *w, int revents) {
e008a4
 int ClientHandler::noop() { return 0; }
e008a4
 
e008a4
 int ClientHandler::read_clear() {
e008a4
+  auto should_break = false;
e008a4
   rb_.ensure_chunk();
e008a4
   for (;;) {
e008a4
     if (rb_.rleft() && on_read() != 0) {
e008a4
@@ -123,7 +124,7 @@ int ClientHandler::read_clear() {
e008a4
       return 0;
e008a4
     }
e008a4
 
e008a4
-    if (!ev_is_active(&conn_.rev)) {
e008a4
+    if (!ev_is_active(&conn_.rev) || should_break) {
e008a4
       return 0;
e008a4
     }
e008a4
 
e008a4
@@ -141,6 +142,7 @@ int ClientHandler::read_clear() {
e008a4
     }
e008a4
 
e008a4
     rb_.write(nread);
e008a4
+    should_break = true;
e008a4
   }
e008a4
 }
e008a4
 
e008a4
@@ -205,6 +207,8 @@ int ClientHandler::tls_handshake() {
e008a4
 }
e008a4
 
e008a4
 int ClientHandler::read_tls() {
e008a4
+  auto should_break = false;
e008a4
+
e008a4
   ERR_clear_error();
e008a4
 
e008a4
   rb_.ensure_chunk();
e008a4
@@ -221,7 +225,7 @@ int ClientHandler::read_tls() {
e008a4
       return 0;
e008a4
     }
e008a4
 
e008a4
-    if (!ev_is_active(&conn_.rev)) {
e008a4
+    if (!ev_is_active(&conn_.rev) || should_break) {
e008a4
       return 0;
e008a4
     }
e008a4
 
e008a4
@@ -239,6 +243,7 @@ int ClientHandler::read_tls() {
e008a4
     }
e008a4
 
e008a4
     rb_.write(nread);
e008a4
+    should_break = true;
e008a4
   }
e008a4
 }
e008a4
 
e008a4
-- 
e008a4
2.20.1
e008a4