mhd_h2_conn_streamid_window_incr() added the stream id to the stream
send window instead of the increment from the WINDOW_UPDATE frame. So a
reply larger than the window the client announced stalled after the
first window. The overflow check used the stream id too, in signed
arithmetic, which overflows for a large stream id.
The increment is now added, and the sum is checked in unsigned
arithmetic, as the connection window check does.
Added three cases to test_h2_raw: a reply larger than the announced
window, a stream window raised to exactly 2147483647 (accepted), and
one raised above it (refused).
---
src/mhd2/h2/h2_conn_streams.c | 4 +-
src/tests/raw/test_h2_raw.c | 298 ++++++++++++++++++++++++++++++++--
2 files changed, 283 insertions(+), 19 deletions(-)
diff --git a/src/mhd2/h2/h2_conn_streams.c b/src/mhd2/h2/h2_conn_streams.c
index b190dd9..f472959 100644
--- a/src/mhd2/h2/h2_conn_streams.c
+++ b/src/mhd2/h2/h2_conn_streams.c
@@ -533,10 +533,10 @@ mhd_h2_conn_streamid_window_incr (struct
MHD_Connection *restrict c,
return true; /* Just ignore the frame */
}
if ((0 < s->state.send_window)
- && (0 > s->state.send_window + (int_least32_t)stream_id))
+ && ((incr + (uint_least32_t)s->state.send_window) > 0x7FFFFFFFu))
return mhd_h2_stream_req_problem (s,
mhd_H2_REQ_PRBLM_FLOW_CONTROL);
- s->state.send_window += (int_least32_t)stream_id;
+ s->state.send_window += (int_least32_t)incr;
return true;
}
diff --git a/src/tests/raw/test_h2_raw.c b/src/tests/raw/test_h2_raw.c
index a5f1e49..4ad0372 100644
--- a/src/tests/raw/test_h2_raw.c
+++ b/src/tests/raw/test_h2_raw.c
@@ -66,6 +66,11 @@
*/
#define H2_FRAME_HDR_SIZE 9
+/**
+ * The type of the DATA frame, RFC 9113, Section 6.1.
+ */
+#define H2_FRAME_DATA 0x0
+
/**
* The type of the HEADERS frame, RFC 9113, Section 6.2.
*/
@@ -86,13 +91,19 @@
*/
#define H2_FRAME_GOAWAY 0x7
+/**
+ * The type of the WINDOW_UPDATE frame, RFC 9113, Section 6.9.
+ */
+#define H2_FRAME_WINDOW_UPDATE 0x8
+
/**
* The PROTOCOL_ERROR code, RFC 9113, Section 7.
*/
#define H2_ERR_PROTOCOL_ERROR 0x1
/**
- * The END_STREAM flag of the HEADERS frame, RFC 9113, Section 6.2.
+ * The END_STREAM flag of the DATA and HEADERS frames, RFC 9113,
+ * Sections 6.1 and 6.2.
*/
#define H2_FLAG_END_STREAM 0x1
@@ -101,6 +112,11 @@
*/
#define H2_FLAG_END_HEADERS 0x4
+/**
+ * The SETTINGS_INITIAL_WINDOW_SIZE identifier, RFC 9113, Section 6.5.2.
+ */
+#define H2_SETTINGS_INITIAL_WINDOW_SIZE 0x4
+
/**
* Defines a test.
*/
@@ -206,6 +222,90 @@ static const struct Test tests[] = {
};
+/**
+ * The request of the window tests.
+ */
+static const struct Test body_request = {
+ .method = "GET",
+ .scheme = "http",
+ .path = "/body",
+ .authority = "localhost",
+ .expect_processed = true,
+};
+
+
+/**
+ * The reply body of the window tests, larger than the initial
+ * connection window of 65535 bytes, RFC 9113, Section 6.9.2.
+ */
+static const char body[70000] = { 0 };
+
+
+/**
+ * Defines a window test: the client requests the body, and opens the
+ * windows with one WINDOW_UPDATE frame for the stream and one for the
+ * connection when the server has used them.
+ */
+struct WindowTest
+{
+ /**
+ * Human-readable name of the test. NULL to end test array.
+ */
+ const char *name;
+
+ /**
+ * The initial stream window of the client, sent as its only setting.
+ */
+ uint32_t init_window;
+
+ /**
+ * The increment of the stream window.
+ */
+ uint32_t stream_incr;
+
+ /**
+ * The increment of the connection window.
+ */
+ uint32_t conn_incr;
+
+ /**
+ * True if the stream window would exceed 2^31-1, so the server must
+ * reset the stream or end the connection instead of sending the rest.
+ */
+ bool expect_rejected;
+};
+
+
+static const struct WindowTest window_tests[] = {
+ {
+ /* RFC 9113, Section 6.5: a SETTINGS frame may carry one setting */
+ .name = "HTTP/2 GET with a stream window update",
+ .init_window = 1000,
+ .stream_incr = sizeof (body) - 1000,
+ .conn_incr = sizeof (body) - 65535,
+ },
+ {
+ /* RFC 9113, Section 6.9.1: the stream window may reach 2^31-1 */
+ .name = "HTTP/2 GET with a stream window update to 2^31-1",
+ .init_window = 0x7fffffffu,
+ .stream_incr = 65535,
+ .conn_incr = sizeof (body) - 65535,
+ },
+ {
+ /* RFC 9113, Section 6.9.1: a stream window above 2^31-1 is a
+ flow-control error */
+ .name = "HTTP/2 GET with a stream window update above 2^31-1",
+ .init_window = 0x7fffffffu,
+ .stream_incr = 65536,
+ .conn_incr = sizeof (body) - 65535,
+ .expect_rejected = true,
+ },
+ {
+ .name = NULL,
+ }
+};
+
+
/**
* True if the request has been given to the application.
* Set by a daemon thread, read by the main thread only after the reply
@@ -221,6 +321,7 @@ static uint16_t port;
/**
* Records that the request reached the application and replies.
+ * The reply for the path of the window tests is the body.
*
* @param cls closure
* @param request the request object
@@ -237,11 +338,17 @@ server_req_cb (void *cls,
uint_fast64_t upload_size)
{
(void)cls; /* Unused */
- (void)path; /* Unused */
(void)method; /* Unused */
(void)upload_size; /* Unused */
processed = true;
+ if (0 == strcmp (path->cstr,
+ body_request.path))
+ return MHD_action_from_response (
+ request,
+ MHD_response_from_buffer_static (MHD_HTTP_STATUS_OK,
+ sizeof (body),
+ body));
return MHD_action_from_response (
request,
MHD_response_from_empty (MHD_HTTP_STATUS_NO_CONTENT));
@@ -407,14 +514,13 @@ put_field (unsigned char *buf,
/**
- * Send the connection preface, the SETTINGS frame required to complete
- * it, and the request of the test as a single HEADERS frame on stream 1.
+ * Send the request of the test as a single HEADERS frame on stream 1.
*
* @param s the socket to send to
* @param t the test to send the request of
*/
static void
-send_request (int s,
+send_headers (int s,
const struct Test *t)
{
/* Five fields, each within the limits of put_field(), plus the frame
@@ -422,17 +528,6 @@ send_request (int s,
unsigned char buf[5 * 256 + H2_FRAME_HDR_SIZE];
size_t size;
- write_all (s,
- H2_PREFACE,
- strlen (H2_PREFACE));
- put_frame_header (buf,
- H2_FRAME_SETTINGS,
- 0,
- 0,
- 0);
- write_all (s,
- buf,
- H2_FRAME_HDR_SIZE);
size = put_field (buf + H2_FRAME_HDR_SIZE,
2 /* ":method" */,
NULL,
@@ -465,6 +560,43 @@ send_request (int s,
}
+/**
+ * Send the connection preface and the SETTINGS frame required to
+ * complete it.
+ *
+ * @param s the socket to send to
+ * @param init_window the initial stream window to send as the only
+ * setting, zero to send no setting
+ */
+static void
+send_preface (int s,
+ uint32_t init_window)
+{
+ unsigned char buf[H2_FRAME_HDR_SIZE + 6];
+ const size_t size = (0 == init_window) ? 0 : 6;
+
+ write_all (s,
+ H2_PREFACE,
+ strlen (H2_PREFACE));
+ put_frame_header (buf,
+ H2_FRAME_SETTINGS,
+ 0,
+ 0,
+ size);
+ if (0 != size)
+ {
+ /* Exactly one setting: a frame of six bytes must be processed too */
+ buf[H2_FRAME_HDR_SIZE] = 0;
+ buf[H2_FRAME_HDR_SIZE + 1] = H2_SETTINGS_INITIAL_WINDOW_SIZE;
+ put_u32 (buf + H2_FRAME_HDR_SIZE + 2,
+ init_window);
+ }
+ write_all (s,
+ buf,
+ H2_FRAME_HDR_SIZE + size);
+}
+
+
/**
* A connection to our server and the data read from it.
*/
@@ -663,7 +795,9 @@ run_test (const struct Test *t)
if (0 != connect_to_server (&c))
return 1;
processed = false;
- send_request (c.s,
+ send_preface (c.s,
+ 0);
+ send_headers (c.s,
t);
ret = read_reply (&c,
&answer,
@@ -705,6 +839,124 @@ run_test (const struct Test *t)
}
+/**
+ * Request the body with the initial window of the test and check that
+ * the server stops when the window is used, then send the window updates
+ * of the test in a single write. A stream window above 2^31-1 is a
+ * flow-control error (RFC 9113, Section 6.9.1).
+ *
+ * @param t the test to run
+ * @return 0 if succeed, 1 on failure
+ */
+static int
+run_window_test (const struct WindowTest *t)
+{
+ /* The smaller of the stream window and the initial connection window,
+ RFC 9113, Section 6.9.2 */
+ const uint32_t window = (65535u < t->init_window) ? 65535u :
+ t->init_window;
+ struct Conn c;
+ const unsigned char *f;
+ size_t data_got = 0;
+ bool window_sent = false;
+
+ if (0 != connect_to_server (&c))
+ return 1;
+ send_preface (c.s,
+ t->init_window);
+ send_headers (c.s,
+ &body_request);
+ while (NULL != (f = read_frame (&c)))
+ {
+ if ((H2_FRAME_RST_STREAM == f[3])
+ || (H2_FRAME_GOAWAY == f[3]))
+ {
+ close (c.s);
+ if (t->expect_rejected && window_sent)
+ return 0;
+ if (!window_sent)
+ {
+ fprintf (stderr,
+ "The server ended the stream before the window update\n");
+ return 1;
+ }
+ fprintf (stderr,
+ "The server rejected a stream window increment of %u\n",
+ (unsigned int)t->stream_incr);
+ return 1;
+ }
+ if ((H2_FRAME_DATA == f[3])
+ && (1 == (get_u32 (f + 5) & 0x7fffffffu)))
+ {
+ data_got += get_u24 (f);
+ if ((!window_sent) && (window < data_got))
+ {
+ fprintf (stderr,
+ "The server sent %u bytes, more than the window of %u\n",
+ (unsigned int)data_got,
+ (unsigned int)window);
+ close (c.s);
+ return 1;
+ }
+ if (0 != (f[4] & H2_FLAG_END_STREAM))
+ {
+ close (c.s);
+ if (t->expect_rejected)
+ {
+ fprintf (stderr,
+ "The server accepted a stream window above 2^31-1\n");
+ return 1;
+ }
+ if (sizeof (body) != data_got)
+ {
+ fprintf (stderr,
+ "The server sent %u bytes of the body, expected %u\n",
+ (unsigned int)data_got,
+ (unsigned int)sizeof (body));
+ return 1;
+ }
+ return 0;
+ }
+ }
+ if ((!window_sent) && (window <= data_got))
+ {
+ unsigned char wu[2 * (H2_FRAME_HDR_SIZE + 4)];
+
+ put_frame_header (wu,
+ H2_FRAME_WINDOW_UPDATE,
+ 0,
+ 1,
+ 4);
+ put_u32 (wu + H2_FRAME_HDR_SIZE,
+ t->stream_incr);
+ put_frame_header (wu + H2_FRAME_HDR_SIZE + 4,
+ H2_FRAME_WINDOW_UPDATE,
+ 0,
+ 0,
+ 4);
+ put_u32 (wu + 2 * H2_FRAME_HDR_SIZE + 4,
+ t->conn_incr);
+ write_all (c.s,
+ wu,
+ sizeof (wu));
+ window_sent = true;
+ }
+ }
+ if (t->expect_rejected && window_sent && (0 == errno))
+ {
+ close (c.s);
+ return 0; /* The server closed the connection without a frame */
+ }
+ fprintf (stderr,
+ "The server sent only %u bytes of the body: %s\n",
+ (unsigned int)data_got,
+ (0 == errno) ? "the connection has been closed" :
+ strerror (errno));
+ close (c.s);
+ return 1;
+}
+
+
static int
run_tests (void)
{
@@ -720,6 +972,18 @@ run_tests (void)
return 1;
}
}
+ for (unsigned int i = 0;
+ NULL != window_tests[i].name;
+ i++)
+ {
+ if (0 != run_window_test (&window_tests[i]))
+ {
+ fprintf (stderr,
+ "Test `%s' failed\n",
+ window_tests[i].name);
+ return 1;
+ }
+ }
return 0;
}
--
2.43.0