!50 Fix CVE-2024-42367
From: @starlet-dx Reviewed-by: @lyn1001 Signed-off-by: @lyn1001
This commit is contained in:
commit
0225ebb3ae
198
CVE-2024-42367.patch
Normal file
198
CVE-2024-42367.patch
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
From f98240ad2279c3e97b65eddce40d37948f383416 Mon Sep 17 00:00:00 2001
|
||||||
|
From: "J. Nick Koston" <nick@koston.org>
|
||||||
|
Date: Thu, 8 Aug 2024 11:19:28 -0500
|
||||||
|
Subject: [PATCH] Do not follow symlinks for compressed file variants (#8652)
|
||||||
|
|
||||||
|
Co-authored-by: Steve Repsher <steverep@users.noreply.github.com>
|
||||||
|
(cherry picked from commit b0536ae6babf160105d4025ea87c02b9fa5629f1)
|
||||||
|
---
|
||||||
|
CHANGES/8652.bugfix.rst | 1 +
|
||||||
|
aiohttp/web_fileresponse.py | 9 ++++++--
|
||||||
|
tests/test_web_sendfile.py | 37 ++++++++++++++++++++-------------
|
||||||
|
tests/test_web_urldispatcher.py | 32 ++++++++++++++++++++++++++++
|
||||||
|
4 files changed, 63 insertions(+), 16 deletions(-)
|
||||||
|
create mode 100644 CHANGES/8652.bugfix.rst
|
||||||
|
|
||||||
|
diff --git a/CHANGES/8652.bugfix.rst b/CHANGES/8652.bugfix.rst
|
||||||
|
new file mode 100644
|
||||||
|
index 0000000..3a1003e
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/CHANGES/8652.bugfix.rst
|
||||||
|
@@ -0,0 +1 @@
|
||||||
|
+Fixed incorrectly following symlinks for compressed file variants -- by :user:`steverep`.
|
||||||
|
diff --git a/aiohttp/web_fileresponse.py b/aiohttp/web_fileresponse.py
|
||||||
|
index 6496ffa..acb0579 100644
|
||||||
|
--- a/aiohttp/web_fileresponse.py
|
||||||
|
+++ b/aiohttp/web_fileresponse.py
|
||||||
|
@@ -2,6 +2,7 @@ import asyncio
|
||||||
|
import mimetypes
|
||||||
|
import os
|
||||||
|
import pathlib
|
||||||
|
+from stat import S_ISREG
|
||||||
|
from typing import ( # noqa
|
||||||
|
IO,
|
||||||
|
TYPE_CHECKING,
|
||||||
|
@@ -136,12 +137,16 @@ class FileResponse(StreamResponse):
|
||||||
|
if check_for_gzipped_file:
|
||||||
|
gzip_path = filepath.with_name(filepath.name + ".gz")
|
||||||
|
try:
|
||||||
|
- return gzip_path, gzip_path.stat(), True
|
||||||
|
+ st = gzip_path.lstat()
|
||||||
|
+ if S_ISREG(st.st_mode):
|
||||||
|
+ return gzip_path, st, True
|
||||||
|
except OSError:
|
||||||
|
# Fall through and try the non-gzipped file
|
||||||
|
pass
|
||||||
|
|
||||||
|
- return filepath, filepath.stat(), False
|
||||||
|
+ st = filepath.lstat()
|
||||||
|
+ if S_ISREG(st.st_mode):
|
||||||
|
+ return filepath, st, False
|
||||||
|
|
||||||
|
async def prepare(self, request: "BaseRequest") -> Optional[AbstractStreamWriter]:
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
diff --git a/tests/test_web_sendfile.py b/tests/test_web_sendfile.py
|
||||||
|
index 2817e08..da9e6ae 100644
|
||||||
|
--- a/tests/test_web_sendfile.py
|
||||||
|
+++ b/tests/test_web_sendfile.py
|
||||||
|
@@ -1,10 +1,13 @@
|
||||||
|
from pathlib import Path
|
||||||
|
+from stat import S_IFREG, S_IRUSR, S_IWUSR
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
from aiohttp import hdrs
|
||||||
|
from aiohttp.test_utils import make_mocked_coro, make_mocked_request
|
||||||
|
from aiohttp.web_fileresponse import FileResponse
|
||||||
|
|
||||||
|
+MOCK_MODE = S_IFREG | S_IRUSR | S_IWUSR
|
||||||
|
+
|
||||||
|
|
||||||
|
def test_using_gzip_if_header_present_and_file_available(loop) -> None:
|
||||||
|
request = make_mocked_request(
|
||||||
|
@@ -12,8 +15,9 @@ def test_using_gzip_if_header_present_and_file_available(loop) -> None:
|
||||||
|
)
|
||||||
|
|
||||||
|
gz_filepath = mock.create_autospec(Path, spec_set=True)
|
||||||
|
- gz_filepath.stat.return_value.st_size = 1024
|
||||||
|
- gz_filepath.stat.return_value.st_mtime_ns = 1603733507222449291
|
||||||
|
+ gz_filepath.lstat.return_value.st_size = 1024
|
||||||
|
+ gz_filepath.lstat.return_value.st_mtime_ns = 1603733507222449291
|
||||||
|
+ gz_filepath.lstat.return_value.st_mode = MOCK_MODE
|
||||||
|
|
||||||
|
filepath = mock.create_autospec(Path, spec_set=True)
|
||||||
|
filepath.name = "logo.png"
|
||||||
|
@@ -33,14 +37,16 @@ def test_gzip_if_header_not_present_and_file_available(loop) -> None:
|
||||||
|
request = make_mocked_request("GET", "http://python.org/logo.png", headers={})
|
||||||
|
|
||||||
|
gz_filepath = mock.create_autospec(Path, spec_set=True)
|
||||||
|
- gz_filepath.stat.return_value.st_size = 1024
|
||||||
|
- gz_filepath.stat.return_value.st_mtime_ns = 1603733507222449291
|
||||||
|
+ gz_filepath.lstat.return_value.st_size = 1024
|
||||||
|
+ gz_filepath.lstat.return_value.st_mtime_ns = 1603733507222449291
|
||||||
|
+ gz_filepath.lstat.return_value.st_mode = MOCK_MODE
|
||||||
|
|
||||||
|
filepath = mock.create_autospec(Path, spec_set=True)
|
||||||
|
filepath.name = "logo.png"
|
||||||
|
filepath.with_name.return_value = gz_filepath
|
||||||
|
- filepath.stat.return_value.st_size = 1024
|
||||||
|
- filepath.stat.return_value.st_mtime_ns = 1603733507222449291
|
||||||
|
+ filepath.lstat.return_value.st_size = 1024
|
||||||
|
+ filepath.lstat.return_value.st_mtime_ns = 1603733507222449291
|
||||||
|
+ filepath.lstat.return_value.st_mode = MOCK_MODE
|
||||||
|
|
||||||
|
file_sender = FileResponse(filepath)
|
||||||
|
file_sender._path = filepath
|
||||||
|
@@ -56,13 +62,14 @@ def test_gzip_if_header_not_present_and_file_not_available(loop) -> None:
|
||||||
|
request = make_mocked_request("GET", "http://python.org/logo.png", headers={})
|
||||||
|
|
||||||
|
gz_filepath = mock.create_autospec(Path, spec_set=True)
|
||||||
|
- gz_filepath.stat.side_effect = OSError(2, "No such file or directory")
|
||||||
|
+ gz_filepath.lstat.side_effect = OSError(2, "No such file or directory")
|
||||||
|
|
||||||
|
filepath = mock.create_autospec(Path, spec_set=True)
|
||||||
|
filepath.name = "logo.png"
|
||||||
|
filepath.with_name.return_value = gz_filepath
|
||||||
|
- filepath.stat.return_value.st_size = 1024
|
||||||
|
- filepath.stat.return_value.st_mtime_ns = 1603733507222449291
|
||||||
|
+ filepath.lstat.return_value.st_size = 1024
|
||||||
|
+ filepath.lstat.return_value.st_mtime_ns = 1603733507222449291
|
||||||
|
+ filepath.lstat.return_value.st_mode = MOCK_MODE
|
||||||
|
|
||||||
|
file_sender = FileResponse(filepath)
|
||||||
|
file_sender._path = filepath
|
||||||
|
@@ -80,13 +87,14 @@ def test_gzip_if_header_present_and_file_not_available(loop) -> None:
|
||||||
|
)
|
||||||
|
|
||||||
|
gz_filepath = mock.create_autospec(Path, spec_set=True)
|
||||||
|
- gz_filepath.stat.side_effect = OSError(2, "No such file or directory")
|
||||||
|
+ gz_filepath.lstat.side_effect = OSError(2, "No such file or directory")
|
||||||
|
|
||||||
|
filepath = mock.create_autospec(Path, spec_set=True)
|
||||||
|
filepath.name = "logo.png"
|
||||||
|
filepath.with_name.return_value = gz_filepath
|
||||||
|
- filepath.stat.return_value.st_size = 1024
|
||||||
|
- filepath.stat.return_value.st_mtime_ns = 1603733507222449291
|
||||||
|
+ filepath.lstat.return_value.st_size = 1024
|
||||||
|
+ filepath.lstat.return_value.st_mtime_ns = 1603733507222449291
|
||||||
|
+ filepath.lstat.return_value.st_mode = MOCK_MODE
|
||||||
|
|
||||||
|
file_sender = FileResponse(filepath)
|
||||||
|
file_sender._path = filepath
|
||||||
|
@@ -103,8 +111,9 @@ def test_status_controlled_by_user(loop) -> None:
|
||||||
|
|
||||||
|
filepath = mock.create_autospec(Path, spec_set=True)
|
||||||
|
filepath.name = "logo.png"
|
||||||
|
- filepath.stat.return_value.st_size = 1024
|
||||||
|
- filepath.stat.return_value.st_mtime_ns = 1603733507222449291
|
||||||
|
+ filepath.lstat.return_value.st_size = 1024
|
||||||
|
+ filepath.lstat.return_value.st_mtime_ns = 1603733507222449291
|
||||||
|
+ filepath.lstat.return_value.st_mode = MOCK_MODE
|
||||||
|
|
||||||
|
file_sender = FileResponse(filepath, status=203)
|
||||||
|
file_sender._path = filepath
|
||||||
|
diff --git a/tests/test_web_urldispatcher.py b/tests/test_web_urldispatcher.py
|
||||||
|
index 0441890..62cb0de 100644
|
||||||
|
--- a/tests/test_web_urldispatcher.py
|
||||||
|
+++ b/tests/test_web_urldispatcher.py
|
||||||
|
@@ -440,6 +440,38 @@ async def test_access_symlink_loop(
|
||||||
|
assert r.status == 404
|
||||||
|
|
||||||
|
|
||||||
|
+async def test_access_compressed_file_as_symlink(
|
||||||
|
+ tmp_path: pathlib.Path, aiohttp_client: AiohttpClient
|
||||||
|
+) -> None:
|
||||||
|
+ """Test that compressed file variants as symlinks are ignored."""
|
||||||
|
+ private_file = tmp_path / "private.txt"
|
||||||
|
+ private_file.write_text("private info")
|
||||||
|
+ www_dir = tmp_path / "www"
|
||||||
|
+ www_dir.mkdir()
|
||||||
|
+ gz_link = www_dir / "file.txt.gz"
|
||||||
|
+ gz_link.symlink_to(f"../{private_file.name}")
|
||||||
|
+
|
||||||
|
+ app = web.Application()
|
||||||
|
+ app.router.add_static("/", www_dir)
|
||||||
|
+ client = await aiohttp_client(app)
|
||||||
|
+
|
||||||
|
+ # Symlink should be ignored; response reflects missing uncompressed file.
|
||||||
|
+ resp = await client.get(f"/{gz_link.stem}", auto_decompress=False)
|
||||||
|
+ assert resp.status == 404
|
||||||
|
+ resp.release()
|
||||||
|
+
|
||||||
|
+ # Again symlin is ignored, and then uncompressed is served.
|
||||||
|
+ txt_file = gz_link.with_suffix("")
|
||||||
|
+ txt_file.write_text("public data")
|
||||||
|
+ resp = await client.get(f"/{txt_file.name}")
|
||||||
|
+ assert resp.status == 200
|
||||||
|
+ assert resp.headers.get("Content-Encoding") is None
|
||||||
|
+ assert resp.content_type == "text/plain"
|
||||||
|
+ assert await resp.text() == "public data"
|
||||||
|
+ resp.release()
|
||||||
|
+ await client.close()
|
||||||
|
+
|
||||||
|
+
|
||||||
|
async def test_access_special_resource(
|
||||||
|
tmp_path: pathlib.Path, aiohttp_client: AiohttpClient
|
||||||
|
) -> None:
|
||||||
|
--
|
||||||
|
2.33.0
|
||||||
|
|
||||||
@ -1,7 +1,7 @@
|
|||||||
%global _empty_manifest_terminate_build 0
|
%global _empty_manifest_terminate_build 0
|
||||||
Name: python-aiohttp
|
Name: python-aiohttp
|
||||||
Version: 3.9.3
|
Version: 3.9.3
|
||||||
Release: 4
|
Release: 5
|
||||||
Summary: Async http client/server framework (asyncio)
|
Summary: Async http client/server framework (asyncio)
|
||||||
License: Apache 2
|
License: Apache 2
|
||||||
URL: https://github.com/aio-libs/aiohttp
|
URL: https://github.com/aio-libs/aiohttp
|
||||||
@ -16,6 +16,7 @@ Patch2: CVE-2024-30251-PR-8332-482e6cdf-backport-3.9-Add-set_content_dis
|
|||||||
Patch3: CVE-2024-30251-PR-8335-5a6949da-backport-3.9-Add-Content-Dispositio.patch
|
Patch3: CVE-2024-30251-PR-8335-5a6949da-backport-3.9-Add-Content-Dispositio.patch
|
||||||
# https://github.com/aio-libs/aiohttp/commit/9ba9a4e531599b9cb2f8cc80effbde40c7eab0bd
|
# https://github.com/aio-libs/aiohttp/commit/9ba9a4e531599b9cb2f8cc80effbde40c7eab0bd
|
||||||
Patch4: Fix-Python-parser-to-mark-responses-without-length-a.patch
|
Patch4: Fix-Python-parser-to-mark-responses-without-length-a.patch
|
||||||
|
Patch5: CVE-2024-42367.patch
|
||||||
|
|
||||||
Requires: python3-attrs
|
Requires: python3-attrs
|
||||||
Requires: python3-charset-normalizer
|
Requires: python3-charset-normalizer
|
||||||
@ -91,6 +92,9 @@ mv %{buildroot}/doclist.lst .
|
|||||||
%{_docdir}/*
|
%{_docdir}/*
|
||||||
|
|
||||||
%changelog
|
%changelog
|
||||||
|
* Fri Oct 11 2024 yaoxin <yao_xin001@hoperun.com> - 3.9.3-5
|
||||||
|
- Fix CVE-2024-42367
|
||||||
|
|
||||||
* Mon May 06 2024 xiaozai <xiaozai@kylinos.cn> - 3.9.3-4
|
* Mon May 06 2024 xiaozai <xiaozai@kylinos.cn> - 3.9.3-4
|
||||||
- Fix Python parser to mark responses without length as closing
|
- Fix Python parser to mark responses without length as closing
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user