diff options
| author | Yogita Urade <yogita.urade@windriver.com> | 2023-08-25 08:45:26 +0000 |
|---|---|---|
| committer | Steve Sakoman <steve@sakoman.com> | 2023-08-30 04:46:36 -1000 |
| commit | 1cae56f2168095927b6deafef9328a07f061c096 (patch) | |
| tree | 41309bb999a549c3eb728dc2f857af502a0e58ac | |
| parent | 074ad15e1e34007997e58892daf759c8d6d9abff (diff) | |
| download | poky-1cae56f2168095927b6deafef9328a07f061c096.tar.gz | |
nghttp2: fix CVE-2023-35945
Envoy is a cloud-native high-performance edge/middle/service
proxy. Envoy’s HTTP/2 codec may leak a header map and
bookkeeping structures upon receiving `RST_STREAM` immediately
followed by the `GOAWAY` frames from an upstream server. In
nghttp2, cleanup of pending requests due to receipt of the
`GOAWAY` frame skips de-allocation of the bookkeeping structure
and pending compressed header. The error return [code path] is
taken if connection is already marked for not sending more
requests due to `GOAWAY` frame. The clean-up code is right after
the return statement, causing memory leak. Denial of service
through memory exhaustion. This vulnerability was patched in
versions(s) 1.26.3, 1.25.8, 1.24.9, 1.23.11.
References:
https://nvd.nist.gov/vuln/detail/CVE-2023-35945
https://github.com/envoyproxy/envoy/security/advisories/GHSA-jfxv-29pc-x22r
(From OE-Core rev: 0e6eb0f417079eaf76b003973c9d93338e6363b5)
Signed-off-by: Yogita Urade <yogita.urade@windriver.com>
Signed-off-by: Steve Sakoman <steve@sakoman.com>
| -rw-r--r-- | meta/recipes-support/nghttp2/nghttp2/CVE-2023-35945.patch | 151 | ||||
| -rw-r--r-- | meta/recipes-support/nghttp2/nghttp2_1.47.0.bb | 1 |
2 files changed, 152 insertions, 0 deletions
diff --git a/meta/recipes-support/nghttp2/nghttp2/CVE-2023-35945.patch b/meta/recipes-support/nghttp2/nghttp2/CVE-2023-35945.patch new file mode 100644 index 0000000000..e03915fda8 --- /dev/null +++ b/meta/recipes-support/nghttp2/nghttp2/CVE-2023-35945.patch | |||
| @@ -0,0 +1,151 @@ | |||
| 1 | From ce385d3f55a4b76da976b3bdf71fe2deddf315ba Mon Sep 17 00:00:00 2001 | ||
| 2 | From: Tatsuhiro Tsujikawa <tatsuhiro.t@gmail.com> | ||
| 3 | Date: Thu, 24 Aug 2023 09:34:26 +0000 | ||
| 4 | Subject: [PATCH] Fix memory leak | ||
| 5 | |||
| 6 | This commit fixes memory leak that happens when PUSH_PROMISE or | ||
| 7 | HEADERS frame cannot be sent, and nghttp2_on_stream_close_callback | ||
| 8 | fails with a fatal error. For example, if GOAWAY frame has been | ||
| 9 | received, a HEADERS frame that opens new stream cannot be sent. | ||
| 10 | |||
| 11 | This issue has already been made public via CVE-2023-35945 [1] issued | ||
| 12 | by envoyproxy/envoy project. During embargo period, the patch to fix | ||
| 13 | this bug was accidentally submitted to nghttp2/nghttp2 repository [2]. | ||
| 14 | And they decided to disclose CVE early. I was notified just 1.5 hours | ||
| 15 | before disclosure. I had no time to respond. | ||
| 16 | |||
| 17 | PoC described in [1] is quite simple, but I think it is not enough to | ||
| 18 | trigger this bug. While it is true that receiving GOAWAY prevents a | ||
| 19 | client from opening new stream, and nghttp2 enters error handling | ||
| 20 | branch, in order to cause the memory leak, | ||
| 21 | nghttp2_session_close_stream function must return a fatal error. | ||
| 22 | nghttp2 defines 2 fatal error codes: | ||
| 23 | |||
| 24 | - NGHTTP2_ERR_NOMEM | ||
| 25 | - NGHTTP2_ERR_CALLBACK_FAILURE | ||
| 26 | |||
| 27 | NGHTTP2_ERR_NOMEM, as its name suggests, indicates out of memory. It | ||
| 28 | is unlikely that a process gets short of memory with this simple PoC | ||
| 29 | scenario unless application does something memory heavy processing. | ||
| 30 | |||
| 31 | NGHTTP2_ERR_CALLBACK_FAILURE is returned from application defined | ||
| 32 | callback function (nghttp2_on_stream_close_callback, in this case), | ||
| 33 | which indicates something fatal happened inside a callback, and a | ||
| 34 | connection must be closed immediately without any further action. As | ||
| 35 | nghttp2_on_stream_close_error_callback documentation says, any error | ||
| 36 | code other than 0 or NGHTTP2_ERR_CALLBACK_FAILURE is treated as fatal | ||
| 37 | error code. More specifically, it is treated as if | ||
| 38 | NGHTTP2_ERR_CALLBACK_FAILURE is returned. I guess that envoy returns | ||
| 39 | NGHTTP2_ERR_CALLBACK_FAILURE or other error code which is translated | ||
| 40 | into NGHTTP2_ERR_CALLBACK_FAILURE. | ||
| 41 | |||
| 42 | [1] https://github.com/envoyproxy/envoy/security/advisories/GHSA-jfxv-29pc-x22r | ||
| 43 | [2] https://github.com/nghttp2/nghttp2/pull/1929 | ||
| 44 | |||
| 45 | CVE: CVE-2023-35945 | ||
| 46 | |||
| 47 | Upstream-Status: Backport [https://github.com/nghttp2/nghttp2/commit/ce385d3f55a4b76da976b3bdf71fe2deddf315ba] | ||
| 48 | |||
| 49 | Signed-off-by: Yogita Urade <yogita.urade@windriver.com> | ||
| 50 | --- | ||
| 51 | lib/nghttp2_session.c | 10 +++++----- | ||
| 52 | tests/nghttp2_session_test.c | 34 ++++++++++++++++++++++++++++++++++ | ||
| 53 | 2 files changed, 39 insertions(+), 5 deletions(-) | ||
| 54 | |||
| 55 | diff --git a/lib/nghttp2_session.c b/lib/nghttp2_session.c | ||
| 56 | index 380a47c..2d9285f 100644 | ||
| 57 | --- a/lib/nghttp2_session.c | ||
| 58 | +++ b/lib/nghttp2_session.c | ||
| 59 | @@ -2940,6 +2940,7 @@ static ssize_t nghttp2_session_mem_send_internal(nghttp2_session *session, | ||
| 60 | if (rv < 0) { | ||
| 61 | int32_t opened_stream_id = 0; | ||
| 62 | uint32_t error_code = NGHTTP2_INTERNAL_ERROR; | ||
| 63 | + int rv2 = 0; | ||
| 64 | |||
| 65 | DEBUGF("send: frame preparation failed with %s\n", | ||
| 66 | nghttp2_strerror(rv)); | ||
| 67 | @@ -2982,19 +2983,18 @@ static ssize_t nghttp2_session_mem_send_internal(nghttp2_session *session, | ||
| 68 | } | ||
| 69 | if (opened_stream_id) { | ||
| 70 | /* careful not to override rv */ | ||
| 71 | - int rv2; | ||
| 72 | rv2 = nghttp2_session_close_stream(session, opened_stream_id, | ||
| 73 | error_code); | ||
| 74 | - | ||
| 75 | - if (nghttp2_is_fatal(rv2)) { | ||
| 76 | - return rv2; | ||
| 77 | - } | ||
| 78 | } | ||
| 79 | |||
| 80 | nghttp2_outbound_item_free(item, mem); | ||
| 81 | nghttp2_mem_free(mem, item); | ||
| 82 | active_outbound_item_reset(aob, mem); | ||
| 83 | |||
| 84 | + if (nghttp2_is_fatal(rv2)) { | ||
| 85 | + return rv2; | ||
| 86 | + } | ||
| 87 | + | ||
| 88 | if (rv == NGHTTP2_ERR_HEADER_COMP) { | ||
| 89 | /* If header compression error occurred, should terminiate | ||
| 90 | connection. */ | ||
| 91 | diff --git a/tests/nghttp2_session_test.c b/tests/nghttp2_session_test.c | ||
| 92 | index cb6bdf7..c2778bc 100644 | ||
| 93 | --- a/tests/nghttp2_session_test.c | ||
| 94 | +++ b/tests/nghttp2_session_test.c | ||
| 95 | @@ -584,6 +584,15 @@ static int on_stream_close_callback(nghttp2_session *session, int32_t stream_id, | ||
| 96 | return 0; | ||
| 97 | } | ||
| 98 | |||
| 99 | +static int fatal_error_on_stream_close_callback(nghttp2_session *session, | ||
| 100 | + int32_t stream_id, | ||
| 101 | + uint32_t error_code, | ||
| 102 | + void *user_data) { | ||
| 103 | + on_stream_close_callback(session, stream_id, error_code, user_data); | ||
| 104 | + | ||
| 105 | + return NGHTTP2_ERR_CALLBACK_FAILURE; | ||
| 106 | +} | ||
| 107 | + | ||
| 108 | static ssize_t pack_extension_callback(nghttp2_session *session, uint8_t *buf, | ||
| 109 | size_t len, const nghttp2_frame *frame, | ||
| 110 | void *user_data) { | ||
| 111 | @@ -3906,6 +3915,8 @@ void test_nghttp2_session_on_goaway_received(void) { | ||
| 112 | nghttp2_frame frame; | ||
| 113 | int i; | ||
| 114 | nghttp2_mem *mem; | ||
| 115 | + const uint8_t *data; | ||
| 116 | + ssize_t datalen; | ||
| 117 | |||
| 118 | mem = nghttp2_mem_default(); | ||
| 119 | user_data.frame_recv_cb_called = 0; | ||
| 120 | @@ -3947,6 +3958,29 @@ void test_nghttp2_session_on_goaway_received(void) { | ||
| 121 | |||
| 122 | nghttp2_frame_goaway_free(&frame.goaway, mem); | ||
| 123 | nghttp2_session_del(session); | ||
| 124 | + | ||
| 125 | + /* Make sure that no memory leak when stream_close callback fails | ||
| 126 | + with a fatal error */ | ||
| 127 | + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); | ||
| 128 | + callbacks.on_stream_close_callback = fatal_error_on_stream_close_callback; | ||
| 129 | + | ||
| 130 | + memset(&user_data, 0, sizeof(user_data)); | ||
| 131 | + | ||
| 132 | + nghttp2_session_client_new(&session, &callbacks, &user_data); | ||
| 133 | + | ||
| 134 | + nghttp2_frame_goaway_init(&frame.goaway, 0, NGHTTP2_NO_ERROR, NULL, 0); | ||
| 135 | + | ||
| 136 | + CU_ASSERT(0 == nghttp2_session_on_goaway_received(session, &frame)); | ||
| 137 | + | ||
| 138 | + nghttp2_submit_request(session, NULL, reqnv, ARRLEN(reqnv), NULL, NULL); | ||
| 139 | + | ||
| 140 | + datalen = nghttp2_session_mem_send(session, &data); | ||
| 141 | + | ||
| 142 | + CU_ASSERT(NGHTTP2_ERR_CALLBACK_FAILURE == datalen); | ||
| 143 | + CU_ASSERT(1 == user_data.stream_close_cb_called); | ||
| 144 | + | ||
| 145 | + nghttp2_frame_goaway_free(&frame.goaway, mem); | ||
| 146 | + nghttp2_session_del(session); | ||
| 147 | } | ||
| 148 | |||
| 149 | void test_nghttp2_session_on_window_update_received(void) { | ||
| 150 | -- | ||
| 151 | 2.35.5 | ||
diff --git a/meta/recipes-support/nghttp2/nghttp2_1.47.0.bb b/meta/recipes-support/nghttp2/nghttp2_1.47.0.bb index 90d3286ac6..0b9091f7e8 100644 --- a/meta/recipes-support/nghttp2/nghttp2_1.47.0.bb +++ b/meta/recipes-support/nghttp2/nghttp2_1.47.0.bb | |||
| @@ -9,6 +9,7 @@ UPSTREAM_CHECK_URI = "https://github.com/nghttp2/nghttp2/releases" | |||
| 9 | SRC_URI = "\ | 9 | SRC_URI = "\ |
| 10 | https://github.com/nghttp2/nghttp2/releases/download/v${PV}/nghttp2-${PV}.tar.xz \ | 10 | https://github.com/nghttp2/nghttp2/releases/download/v${PV}/nghttp2-${PV}.tar.xz \ |
| 11 | file://0001-fetch-ocsp-response-use-python3.patch \ | 11 | file://0001-fetch-ocsp-response-use-python3.patch \ |
| 12 | file://CVE-2023-35945.patch \ | ||
| 12 | " | 13 | " |
| 13 | SRC_URI[sha256sum] = "68271951324554c34501b85190f22f2221056db69f493afc3bbac8e7be21e7cc" | 14 | SRC_URI[sha256sum] = "68271951324554c34501b85190f22f2221056db69f493afc3bbac8e7be21e7cc" |
| 14 | 15 | ||
