mirror of
https://github.com/openembedded/meta-openembedded.git
synced 2026-09-01 04:00:16 +00:00
python3-aiohttp: fix CVE-2026-54277
This patch applies the upstream fix as referenced in [2], using the commit shown in [1]. [1] https://github.com/aio-libs/aiohttp/commit/5ab61bb4cd88f19b712f12c7c9295fe262bf804d [2] https://github.com/advisories/GHSA-63hw-fmq6-xxg2 Signed-off-by: Darsh Kelaiya <dkelaiya@cisco.com> Signed-off-by: Anuj Mittal <anuj.mittal@oss.qualcomm.com>
This commit is contained in:
@@ -0,0 +1,249 @@
|
||||
From f61e8949f10a0e77ceba28a6639627e6e57bf167 Mon Sep 17 00:00:00 2001
|
||||
From: "J. Nick Koston" <nick@koston.org>
|
||||
Date: Sun, 7 Jun 2026 00:33:03 -0500
|
||||
Subject: [PATCH] [PR #12826/36df6c13 backport][3.14] Enforce max_line_size on
|
||||
fragmented request target and reason in C parser (#12837)
|
||||
|
||||
CVE: CVE-2026-54277
|
||||
Upstream-Status: Backport [https://github.com/aio-libs/aiohttp/commit/5ab61bb4cd88f19b712f12c7c9295fe262bf804d]
|
||||
|
||||
Backport Changes:
|
||||
- Added aiohttp/_http_parser.c because Wrynose builds the
|
||||
pre-generated C parser without running Cython.
|
||||
- Updated cb_on_url() and cb_on_status() to enforce max_line_size
|
||||
against the accumulated parser buffer.
|
||||
|
||||
(cherry picked from commit 5ab61bb4cd88f19b712f12c7c9295fe262bf804d)
|
||||
Signed-off-by: Darsh Kelaiya <dkelaiya@cisco.com>
|
||||
---
|
||||
CHANGES/12826.bugfix.rst | 1 +
|
||||
aiohttp/_http_parser.c | 36 ++++++++++++++++++------------------
|
||||
aiohttp/_http_parser.pyx | 4 ++--
|
||||
tests/test_http_parser.py | 22 ++++++++++++++++++++++
|
||||
4 files changed, 43 insertions(+), 20 deletions(-)
|
||||
create mode 100644 CHANGES/12826.bugfix.rst
|
||||
|
||||
diff --git a/CHANGES/12826.bugfix.rst b/CHANGES/12826.bugfix.rst
|
||||
new file mode 100644
|
||||
index 000000000..7e095615d
|
||||
--- /dev/null
|
||||
+++ b/CHANGES/12826.bugfix.rst
|
||||
@@ -0,0 +1 @@
|
||||
+Fixed the C HTTP parser not enforcing ``max_line_size`` on a request target or response reason phrase that is split across multiple reads; each fragment was checked on its own, so an accumulated line could exceed the limit without raising ``LineTooLong``. The accumulated length is now checked, matching the pure-Python parser -- by :user:`bdraco`.
|
||||
diff --git a/aiohttp/_http_parser.c b/aiohttp/_http_parser.c
|
||||
index 911c9aa65..14c5b3350 100644
|
||||
--- a/aiohttp/_http_parser.c
|
||||
+++ b/aiohttp/_http_parser.c
|
||||
@@ -14760,7 +14760,7 @@ static int __pyx_f_7aiohttp_12_http_parser_cb_on_url(llhttp_t *__pyx_v_parser, c
|
||||
* const char *at, size_t length) except -1:
|
||||
* cdef HttpParser pyparser = <HttpParser>parser.data # <<<<<<<<<<<<<<
|
||||
* try:
|
||||
- * if length > pyparser._max_line_size:
|
||||
+ * if len(pyparser._buf) + length > pyparser._max_line_size:
|
||||
*/
|
||||
__pyx_t_1 = ((PyObject *)__pyx_v_parser->data);
|
||||
__Pyx_INCREF(__pyx_t_1);
|
||||
@@ -14771,7 +14771,7 @@ static int __pyx_f_7aiohttp_12_http_parser_cb_on_url(llhttp_t *__pyx_v_parser, c
|
||||
* const char *at, size_t length) except -1:
|
||||
* cdef HttpParser pyparser = <HttpParser>parser.data
|
||||
* try: # <<<<<<<<<<<<<<
|
||||
- * if length > pyparser._max_line_size:
|
||||
+ * if len(pyparser._buf) + length > pyparser._max_line_size:
|
||||
* status = pyparser._buf + at[:length]
|
||||
*/
|
||||
{
|
||||
@@ -14786,16 +14786,16 @@ static int __pyx_f_7aiohttp_12_http_parser_cb_on_url(llhttp_t *__pyx_v_parser, c
|
||||
/* "aiohttp/_http_parser.pyx":721
|
||||
* cdef HttpParser pyparser = <HttpParser>parser.data
|
||||
* try:
|
||||
- * if length > pyparser._max_line_size: # <<<<<<<<<<<<<<
|
||||
+ * if len(pyparser._buf) + length > pyparser._max_line_size: # <<<<<<<<<<<<<<
|
||||
* status = pyparser._buf + at[:length]
|
||||
* raise LineTooLong(status[:100] + b"...", pyparser._max_line_size)
|
||||
*/
|
||||
- __pyx_t_5 = (__pyx_v_length > __pyx_v_pyparser->_max_line_size);
|
||||
+ __pyx_t_5 = ((__Pyx_PyByteArray_GET_SIZE(__pyx_v_pyparser->_buf) + __pyx_v_length) > __pyx_v_pyparser->_max_line_size);
|
||||
if (unlikely(__pyx_t_5)) {
|
||||
|
||||
/* "aiohttp/_http_parser.pyx":722
|
||||
* try:
|
||||
- * if length > pyparser._max_line_size:
|
||||
+ * if len(pyparser._buf) + length > pyparser._max_line_size:
|
||||
* status = pyparser._buf + at[:length] # <<<<<<<<<<<<<<
|
||||
* raise LineTooLong(status[:100] + b"...", pyparser._max_line_size)
|
||||
* extend(pyparser._buf, at, length)
|
||||
@@ -14809,7 +14809,7 @@ static int __pyx_f_7aiohttp_12_http_parser_cb_on_url(llhttp_t *__pyx_v_parser, c
|
||||
__pyx_t_6 = 0;
|
||||
|
||||
/* "aiohttp/_http_parser.pyx":723
|
||||
- * if length > pyparser._max_line_size:
|
||||
+ * if len(pyparser._buf) + length > pyparser._max_line_size:
|
||||
* status = pyparser._buf + at[:length]
|
||||
* raise LineTooLong(status[:100] + b"...", pyparser._max_line_size) # <<<<<<<<<<<<<<
|
||||
* extend(pyparser._buf, at, length)
|
||||
@@ -14854,7 +14854,7 @@ static int __pyx_f_7aiohttp_12_http_parser_cb_on_url(llhttp_t *__pyx_v_parser, c
|
||||
/* "aiohttp/_http_parser.pyx":721
|
||||
* cdef HttpParser pyparser = <HttpParser>parser.data
|
||||
* try:
|
||||
- * if length > pyparser._max_line_size: # <<<<<<<<<<<<<<
|
||||
+ * if len(pyparser._buf) + length > pyparser._max_line_size: # <<<<<<<<<<<<<<
|
||||
* status = pyparser._buf + at[:length]
|
||||
* raise LineTooLong(status[:100] + b"...", pyparser._max_line_size)
|
||||
*/
|
||||
@@ -14878,7 +14878,7 @@ static int __pyx_f_7aiohttp_12_http_parser_cb_on_url(llhttp_t *__pyx_v_parser, c
|
||||
* const char *at, size_t length) except -1:
|
||||
* cdef HttpParser pyparser = <HttpParser>parser.data
|
||||
* try: # <<<<<<<<<<<<<<
|
||||
- * if length > pyparser._max_line_size:
|
||||
+ * if len(pyparser._buf) + length > pyparser._max_line_size:
|
||||
* status = pyparser._buf + at[:length]
|
||||
*/
|
||||
}
|
||||
@@ -14968,7 +14968,7 @@ static int __pyx_f_7aiohttp_12_http_parser_cb_on_url(llhttp_t *__pyx_v_parser, c
|
||||
* const char *at, size_t length) except -1:
|
||||
* cdef HttpParser pyparser = <HttpParser>parser.data
|
||||
* try: # <<<<<<<<<<<<<<
|
||||
- * if length > pyparser._max_line_size:
|
||||
+ * if len(pyparser._buf) + length > pyparser._max_line_size:
|
||||
* status = pyparser._buf + at[:length]
|
||||
*/
|
||||
__pyx_L5_except_error:;
|
||||
@@ -15045,7 +15045,7 @@ static int __pyx_f_7aiohttp_12_http_parser_cb_on_status(llhttp_t *__pyx_v_parser
|
||||
* const char *at, size_t length) except -1:
|
||||
* cdef HttpParser pyparser = <HttpParser>parser.data # <<<<<<<<<<<<<<
|
||||
* try:
|
||||
- * if length > pyparser._max_line_size:
|
||||
+ * if len(pyparser._buf) + length > pyparser._max_line_size:
|
||||
*/
|
||||
__pyx_t_1 = ((PyObject *)__pyx_v_parser->data);
|
||||
__Pyx_INCREF(__pyx_t_1);
|
||||
@@ -15056,7 +15056,7 @@ static int __pyx_f_7aiohttp_12_http_parser_cb_on_status(llhttp_t *__pyx_v_parser
|
||||
* const char *at, size_t length) except -1:
|
||||
* cdef HttpParser pyparser = <HttpParser>parser.data
|
||||
* try: # <<<<<<<<<<<<<<
|
||||
- * if length > pyparser._max_line_size:
|
||||
+ * if len(pyparser._buf) + length > pyparser._max_line_size:
|
||||
* reason = pyparser._buf + at[:length]
|
||||
*/
|
||||
{
|
||||
@@ -15071,16 +15071,16 @@ static int __pyx_f_7aiohttp_12_http_parser_cb_on_status(llhttp_t *__pyx_v_parser
|
||||
/* "aiohttp/_http_parser.pyx":736
|
||||
* cdef HttpParser pyparser = <HttpParser>parser.data
|
||||
* try:
|
||||
- * if length > pyparser._max_line_size: # <<<<<<<<<<<<<<
|
||||
+ * if len(pyparser._buf) + length > pyparser._max_line_size: # <<<<<<<<<<<<<<
|
||||
* reason = pyparser._buf + at[:length]
|
||||
* raise LineTooLong(reason[:100] + b"...", pyparser._max_line_size)
|
||||
*/
|
||||
- __pyx_t_5 = (__pyx_v_length > __pyx_v_pyparser->_max_line_size);
|
||||
+ __pyx_t_5 = ((__Pyx_PyByteArray_GET_SIZE(__pyx_v_pyparser->_buf) + __pyx_v_length) > __pyx_v_pyparser->_max_line_size);
|
||||
if (unlikely(__pyx_t_5)) {
|
||||
|
||||
/* "aiohttp/_http_parser.pyx":737
|
||||
* try:
|
||||
- * if length > pyparser._max_line_size:
|
||||
+ * if len(pyparser._buf) + length > pyparser._max_line_size:
|
||||
* reason = pyparser._buf + at[:length] # <<<<<<<<<<<<<<
|
||||
* raise LineTooLong(reason[:100] + b"...", pyparser._max_line_size)
|
||||
* extend(pyparser._buf, at, length)
|
||||
@@ -15094,7 +15094,7 @@ static int __pyx_f_7aiohttp_12_http_parser_cb_on_status(llhttp_t *__pyx_v_parser
|
||||
__pyx_t_6 = 0;
|
||||
|
||||
/* "aiohttp/_http_parser.pyx":738
|
||||
- * if length > pyparser._max_line_size:
|
||||
+ * if len(pyparser._buf) + length > pyparser._max_line_size:
|
||||
* reason = pyparser._buf + at[:length]
|
||||
* raise LineTooLong(reason[:100] + b"...", pyparser._max_line_size) # <<<<<<<<<<<<<<
|
||||
* extend(pyparser._buf, at, length)
|
||||
@@ -15139,7 +15139,7 @@ static int __pyx_f_7aiohttp_12_http_parser_cb_on_status(llhttp_t *__pyx_v_parser
|
||||
/* "aiohttp/_http_parser.pyx":736
|
||||
* cdef HttpParser pyparser = <HttpParser>parser.data
|
||||
* try:
|
||||
- * if length > pyparser._max_line_size: # <<<<<<<<<<<<<<
|
||||
+ * if len(pyparser._buf) + length > pyparser._max_line_size: # <<<<<<<<<<<<<<
|
||||
* reason = pyparser._buf + at[:length]
|
||||
* raise LineTooLong(reason[:100] + b"...", pyparser._max_line_size)
|
||||
*/
|
||||
@@ -15163,7 +15163,7 @@ static int __pyx_f_7aiohttp_12_http_parser_cb_on_status(llhttp_t *__pyx_v_parser
|
||||
* const char *at, size_t length) except -1:
|
||||
* cdef HttpParser pyparser = <HttpParser>parser.data
|
||||
* try: # <<<<<<<<<<<<<<
|
||||
- * if length > pyparser._max_line_size:
|
||||
+ * if len(pyparser._buf) + length > pyparser._max_line_size:
|
||||
* reason = pyparser._buf + at[:length]
|
||||
*/
|
||||
}
|
||||
@@ -15253,7 +15253,7 @@ static int __pyx_f_7aiohttp_12_http_parser_cb_on_status(llhttp_t *__pyx_v_parser
|
||||
* const char *at, size_t length) except -1:
|
||||
* cdef HttpParser pyparser = <HttpParser>parser.data
|
||||
* try: # <<<<<<<<<<<<<<
|
||||
- * if length > pyparser._max_line_size:
|
||||
+ * if len(pyparser._buf) + length > pyparser._max_line_size:
|
||||
* reason = pyparser._buf + at[:length]
|
||||
*/
|
||||
__pyx_L5_except_error:;
|
||||
diff --git a/aiohttp/_http_parser.pyx b/aiohttp/_http_parser.pyx
|
||||
index 5da835bc6..e1edee310 100644
|
||||
--- a/aiohttp/_http_parser.pyx
|
||||
+++ b/aiohttp/_http_parser.pyx
|
||||
@@ -718,7 +718,7 @@ cdef int cb_on_url(cparser.llhttp_t* parser,
|
||||
const char *at, size_t length) except -1:
|
||||
cdef HttpParser pyparser = <HttpParser>parser.data
|
||||
try:
|
||||
- if length > pyparser._max_line_size:
|
||||
+ if len(pyparser._buf) + length > pyparser._max_line_size:
|
||||
status = pyparser._buf + at[:length]
|
||||
raise LineTooLong(status[:100] + b"...", pyparser._max_line_size)
|
||||
extend(pyparser._buf, at, length)
|
||||
@@ -733,7 +733,7 @@ cdef int cb_on_status(cparser.llhttp_t* parser,
|
||||
const char *at, size_t length) except -1:
|
||||
cdef HttpParser pyparser = <HttpParser>parser.data
|
||||
try:
|
||||
- if length > pyparser._max_line_size:
|
||||
+ if len(pyparser._buf) + length > pyparser._max_line_size:
|
||||
reason = pyparser._buf + at[:length]
|
||||
raise LineTooLong(reason[:100] + b"...", pyparser._max_line_size)
|
||||
extend(pyparser._buf, at, length)
|
||||
diff --git a/tests/test_http_parser.py b/tests/test_http_parser.py
|
||||
index 25604dbcc..8cb591f20 100644
|
||||
--- a/tests/test_http_parser.py
|
||||
+++ b/tests/test_http_parser.py
|
||||
@@ -1272,6 +1272,17 @@ def test_http_request_max_status_line_under_limit(parser: HttpRequestParser) ->
|
||||
assert msg.url == URL("/path" + path.decode())
|
||||
|
||||
|
||||
+def test_http_request_max_status_line_fragmented(
|
||||
+ parser: HttpRequestParser,
|
||||
+) -> None:
|
||||
+ # Split an overlong request target across reads so that each callback
|
||||
+ # fragment is under the limit but the accumulated target is not.
|
||||
+ match = "400, message:\n Got more than 8190 bytes when reading"
|
||||
+ with pytest.raises(http_exceptions.LineTooLong, match=match):
|
||||
+ parser.feed_data(b"GET /" + b"a" * 8000)
|
||||
+ parser.feed_data(b"a" * 8000 + b" HTTP/1.1\r\nHost: a\r\n\r\n")
|
||||
+
|
||||
+
|
||||
def test_http_response_parser_utf8(response) -> None:
|
||||
text = "HTTP/1.1 200 Ok\r\nx-test:тест\r\n\r\n".encode()
|
||||
|
||||
@@ -1349,6 +1360,17 @@ def test_http_response_parser_status_line_under_limit(
|
||||
assert msg.reason == reason.decode()
|
||||
|
||||
|
||||
+def test_http_response_parser_status_line_too_long_fragmented(
|
||||
+ response: HttpResponseParser,
|
||||
+) -> None:
|
||||
+ # Split an overlong reason phrase across reads so that each callback
|
||||
+ # fragment is under the limit but the accumulated reason is not.
|
||||
+ match = "400, message:\n Got more than 8190 bytes when reading"
|
||||
+ with pytest.raises(http_exceptions.LineTooLong, match=match):
|
||||
+ response.feed_data(b"HTTP/1.1 200 " + b"a" * 8000)
|
||||
+ response.feed_data(b"a" * 8000 + b"\r\n\r\n")
|
||||
+
|
||||
+
|
||||
def test_http_response_parser_bad_version(response) -> None:
|
||||
with pytest.raises(http_exceptions.BadHttpMessage):
|
||||
response.feed_data(b"HT/11 200 Ok\r\n\r\n")
|
||||
--
|
||||
2.35.6
|
||||
|
||||
@@ -13,6 +13,7 @@ SRC_URI += " \
|
||||
file://CVE-2026-54274.patch \
|
||||
file://CVE-2026-54275.patch \
|
||||
file://CVE-2026-54276.patch \
|
||||
file://CVE-2026-54277.patch \
|
||||
"
|
||||
|
||||
CVE_PRODUCT = "aiohttp"
|
||||
|
||||
Reference in New Issue
Block a user