You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

145 lines
5.7 KiB

  1. From 81d18a8e359685c169cfd30e6a1574b98aedbaeb Mon Sep 17 00:00:00 2001
  2. From: Glenn Strauss <gstrauss@gluelogic.com>
  3. Date: Thu, 22 Apr 2021 01:11:47 -0400
  4. Subject: [PATCH] [core] discard some HTTP/2 DATA after response (fixes #3078)
  5. (thx oldium)
  6. improve handling of HTTP/2 DATA frames received
  7. a short time after sending response
  8. x-ref:
  9. "POST request DATA part for non-existing URI closes HTTP/2 connection prematurely"
  10. https://redmine.lighttpd.net/issues/3078
  11. Signed-off-by: Glenn Strauss <gstrauss@gluelogic.com>
  12. ---
  13. src/h2.c | 64 ++++++++++++++++++++++++++++++++++++++++++--------------
  14. src/h2.h | 1 +
  15. 2 files changed, 49 insertions(+), 16 deletions(-)
  16. --- a/src/h2.c
  17. +++ b/src/h2.c
  18. @@ -272,10 +272,23 @@ h2_send_rst_stream_id (uint32_t h2id, co
  19. __attribute_cold__
  20. static void
  21. -h2_send_rst_stream (request_st * const r, connection * const con, const request_h2error_t e)
  22. +h2_send_rst_stream_state (request_st * const r, h2con * const h2c)
  23. {
  24. + if (r->h2state != H2_STATE_HALF_CLOSED_REMOTE
  25. + && r->h2state != H2_STATE_CLOSED) {
  26. + /* set timestamp for comparison; not tracking individual stream ids */
  27. + h2c->half_closed_ts = log_epoch_secs;
  28. + }
  29. r->state = CON_STATE_ERROR;
  30. r->h2state = H2_STATE_CLOSED;
  31. +}
  32. +
  33. +
  34. +__attribute_cold__
  35. +static void
  36. +h2_send_rst_stream (request_st * const r, connection * const con, const request_h2error_t e)
  37. +{
  38. + h2_send_rst_stream_state(r, con->h2);/*(sets r->h2state = H2_STATE_CLOSED)*/
  39. h2_send_rst_stream_id(r->h2id, con, e);
  40. }
  41. @@ -289,13 +302,10 @@ h2_send_goaway_rst_stream (connection *
  42. for (uint32_t i = 0, rused = h2c->rused; i < rused; ++i) {
  43. request_st * const r = h2c->r[i];
  44. if (r->h2state == H2_STATE_CLOSED) continue;
  45. + h2_send_rst_stream_state(r, h2c);/*(sets r->h2state = H2_STATE_CLOSED)*/
  46. /*(XXX: might consider always sending RST_STREAM)*/
  47. - if (!sent_goaway) {
  48. - r->state = CON_STATE_ERROR;
  49. - r->h2state = H2_STATE_CLOSED;
  50. - }
  51. - else /*(also sets r->h2state = H2_STATE_CLOSED)*/
  52. - h2_send_rst_stream(r, con, H2_E_PROTOCOL_ERROR);
  53. + if (sent_goaway)
  54. + h2_send_rst_stream_id(r->h2id, con, H2_E_PROTOCOL_ERROR);
  55. }
  56. }
  57. @@ -780,14 +790,27 @@ h2_recv_data (connection * const con, co
  58. }
  59. chunkqueue * const cq = con->read_queue;
  60. if (NULL == r) {
  61. - /* XXX: TODO: might need to keep a list of recently retired streams
  62. - * for a few seconds so that if we send RST_STREAM, then we ignore
  63. - * further DATA and do not send connection error, though recv windows
  64. - * still must be updated. */
  65. - if (h2c->h2_cid < id || (!h2c->sent_goaway && 0 != alen))
  66. - h2_send_goaway_e(con, H2_E_PROTOCOL_ERROR);
  67. + /* simplistic heuristic to discard additional DATA from recently-closed
  68. + * streams (or half-closed (local)), where recently-closed here is
  69. + * within 2-3 seconds of any (other) stream being half-closed (local)
  70. + * or reset before that (other) stream received END_STREAM from peer.
  71. + * (e.g. clients might fire off POST request followed by DATA,
  72. + * and a response might be sent before processing DATA frames)
  73. + * (id <= h2c->h2_cid) already checked above, else H2_E_PROTOCOL_ERROR
  74. + * If the above conditions do not hold, then send GOAWAY to attempt to
  75. + * reduce the chance of becoming an infinite data sink for misbehaving
  76. + * clients, though remaining streams are still handled before the
  77. + * connection is closed. */
  78. chunkqueue_mark_written(cq, 9+len);
  79. - return 0;
  80. + if (h2c->half_closed_ts + 2 >= log_epoch_secs) {
  81. + h2_send_window_update(con, 0, len); /*(h2r->h2_rwin)*/
  82. + return 1;
  83. + }
  84. + else {
  85. + if (!h2c->sent_goaway && 0 != alen)
  86. + h2_send_goaway_e(con, H2_E_NO_ERROR);
  87. + return 0;
  88. + }
  89. }
  90. if (r->h2state == H2_STATE_CLOSED
  91. @@ -808,7 +831,7 @@ h2_recv_data (connection * const con, co
  92. }
  93. }
  94. /*(allow h2r->h2_rwin to dip below 0 so that entire frame is processed)*/
  95. - /*(undeflow will not occur (with reasonable SETTINGS_MAX_FRAME_SIZE used)
  96. + /*(underflow will not occur (with reasonable SETTINGS_MAX_FRAME_SIZE used)
  97. * since windows updated elsewhere and data is streamed to temp files if
  98. * not FDEVENT_STREAM_REQUEST_BUFMIN)*/
  99. /*r->h2_rwin -= (int32_t)len;*/
  100. @@ -2347,16 +2370,25 @@ h2_send_end_stream_data (request_st * co
  101. } };
  102. dataframe.u[2] = htonl(r->h2id);
  103. - r->h2state = H2_STATE_CLOSED;
  104. /*(ignore window updates when sending 0-length DATA frame with END_STREAM)*/
  105. chunkqueue_append_mem(con->write_queue, /*(+3 to skip over align pad)*/
  106. (const char *)dataframe.c+3, sizeof(dataframe)-3);
  107. +
  108. + if (r->h2state != H2_STATE_HALF_CLOSED_REMOTE) {
  109. + /* set timestamp for comparison; not tracking individual stream ids */
  110. + h2con * const h2c = con->h2;
  111. + h2c->half_closed_ts = log_epoch_secs;
  112. + /* indicate to peer that no more DATA should be sent from peer */
  113. + h2_send_rst_stream_id(r->h2id, con, H2_E_NO_ERROR);
  114. + }
  115. + r->h2state = H2_STATE_CLOSED;
  116. }
  117. void
  118. h2_send_end_stream (request_st * const r, connection * const con)
  119. {
  120. + if (r->h2state == H2_STATE_CLOSED) return;
  121. if (r->state != CON_STATE_ERROR && r->resp_body_finished) {
  122. /* CON_STATE_RESPONSE_END */
  123. if (r->gw_dechunk && r->gw_dechunk->done
  124. --- a/src/h2.h
  125. +++ b/src/h2.h
  126. @@ -92,6 +92,7 @@ struct h2con {
  127. uint32_t s_max_header_list_size; /* SETTINGS_MAX_HEADER_LIST_SIZE */
  128. struct lshpack_dec decoder;
  129. struct lshpack_enc encoder;
  130. + time_t half_closed_ts;
  131. };
  132. void h2_send_goaway (connection *con, request_h2error_t e);