Fix CVE-2024-35178
This commit is contained in:
parent
0e2064e6db
commit
d914ea1c97
227
backport-CVE-2024-35178-GHSA-hrw6-wg82-cm62.patch
Normal file
227
backport-CVE-2024-35178-GHSA-hrw6-wg82-cm62.patch
Normal file
@ -0,0 +1,227 @@
|
||||
From 79fbf801c5908f4d1d9bc90004b74cfaaeeed2df Mon Sep 17 00:00:00 2001
|
||||
From: Min RK <benjaminrk@gmail.com>
|
||||
Date: Fri, 31 May 2024 03:27:40 +0200
|
||||
Subject: [PATCH] Merge pull request from GHSA-hrw6-wg82-cm62
|
||||
|
||||
* filefind: avoid handling absolute paths
|
||||
|
||||
we don't need or want absolute path support,
|
||||
which we inherited from generic ipython_genutils
|
||||
|
||||
only supporting relative paths lets us avoid attempting to accessing files we know we won't accept
|
||||
|
||||
* Apply suggestions from code review
|
||||
|
||||
Co-authored-by: M Bussonnier <bussonniermatthias@gmail.com>
|
||||
|
||||
* filefind: only accept Sequence[str]
|
||||
|
||||
we only call it one place, might as well be simple about it
|
||||
|
||||
* version_info gate for is_relative_to
|
||||
|
||||
* clarify docstring
|
||||
|
||||
Co-authored-by: Carol Willing <carolcode@willingconsulting.com>
|
||||
|
||||
---------
|
||||
|
||||
Co-authored-by: M Bussonnier <bussonniermatthias@gmail.com>
|
||||
Co-authored-by: Carol Willing <carolcode@willingconsulting.com>
|
||||
---
|
||||
jupyter_server/utils.py | 99 +++++++++++++++++------------------------
|
||||
tests/test_utils.py | 39 ++++++++++++++++
|
||||
2 files changed, 81 insertions(+), 57 deletions(-)
|
||||
|
||||
diff --git a/jupyter_server/utils.py b/jupyter_server/utils.py
|
||||
index e9225b746..9a1bd89af 100644
|
||||
--- a/jupyter_server/utils.py
|
||||
+++ b/jupyter_server/utils.py
|
||||
@@ -11,6 +11,7 @@ import socket
|
||||
import sys
|
||||
import warnings
|
||||
from contextlib import contextmanager
|
||||
+from pathlib import Path
|
||||
from typing import Any, Generator, NewType, Sequence
|
||||
from urllib.parse import (
|
||||
SplitResult,
|
||||
@@ -338,81 +339,65 @@ def is_namespace_package(namespace: str) -> bool | None:
|
||||
return isinstance(spec.submodule_search_locations, _NamespacePath)
|
||||
|
||||
|
||||
-def filefind(filename: str, path_dirs: Sequence[str] | str | None = None) -> str:
|
||||
+def filefind(filename: str, path_dirs: Sequence[str]) -> str:
|
||||
"""Find a file by looking through a sequence of paths.
|
||||
- This iterates through a sequence of paths looking for a file and returns
|
||||
- the full, absolute path of the first occurrence of the file. If no set of
|
||||
- path dirs is given, the filename is tested as is, after running through
|
||||
- :func:`expandvars` and :func:`expanduser`. Thus a simple call::
|
||||
|
||||
- filefind("myfile.txt")
|
||||
+ For use in FileFindHandler.
|
||||
|
||||
- will find the file in the current working dir, but::
|
||||
+ Iterates through a sequence of paths looking for a file and returns
|
||||
+ the full, absolute path of the first occurrence of the file.
|
||||
|
||||
- filefind("~/myfile.txt")
|
||||
+ Absolute paths are not accepted for inputs.
|
||||
|
||||
- Will find the file in the users home directory. This function does not
|
||||
- automatically try any paths, such as the cwd or the user's home directory.
|
||||
+ This function does not automatically try any paths,
|
||||
+ such as the cwd or the user's home directory.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
filename : str
|
||||
- The filename to look for.
|
||||
- path_dirs : str, None or sequence of str
|
||||
- The sequence of paths to look for the file in. If None, the filename
|
||||
- need to be absolute or be in the cwd. If a string, the string is
|
||||
- put into a sequence and the searched. If a sequence, walk through
|
||||
- each element and join with ``filename``, calling :func:`expandvars`
|
||||
- and :func:`expanduser` before testing for existence.
|
||||
+ The filename to look for. Must be a relative path.
|
||||
+ path_dirs : sequence of str
|
||||
+ The sequence of paths to look in for the file.
|
||||
+ Walk through each element and join with ``filename``.
|
||||
+ Only after ensuring the path resolves within the directory is it checked for existence.
|
||||
|
||||
Returns
|
||||
-------
|
||||
- Raises :exc:`IOError` or returns absolute path to file.
|
||||
+ Raises :exc:`OSError` or returns absolute path to file.
|
||||
"""
|
||||
-
|
||||
- # If paths are quoted, abspath gets confused, strip them...
|
||||
- filename = filename.strip('"').strip("'")
|
||||
- # If the input is an absolute path, just check it exists
|
||||
- if os.path.isabs(filename) and os.path.isfile(filename):
|
||||
- return filename
|
||||
-
|
||||
- if path_dirs is None:
|
||||
- path_dirs = ("",)
|
||||
- elif isinstance(path_dirs, str):
|
||||
- path_dirs = (path_dirs,)
|
||||
-
|
||||
- for path in path_dirs:
|
||||
- if path == ".":
|
||||
- path = os.getcwd() # noqa: PLW2901
|
||||
- testname = expand_path(os.path.join(path, filename))
|
||||
- if os.path.isfile(testname):
|
||||
- return os.path.abspath(testname)
|
||||
+ file_path = Path(filename)
|
||||
+
|
||||
+ # If the input is an absolute path, reject it
|
||||
+ if file_path.is_absolute():
|
||||
+ msg = f"{filename} is absolute, filefind only accepts relative paths."
|
||||
+ raise OSError(msg)
|
||||
+
|
||||
+ for path_str in path_dirs:
|
||||
+ path = Path(path_str).absolute()
|
||||
+ test_path = path / file_path
|
||||
+ # os.path.abspath resolves '..', but Path.absolute() doesn't
|
||||
+ # Path.resolve() does, but traverses symlinks, which we don't want
|
||||
+ test_path = Path(os.path.abspath(test_path))
|
||||
+ if sys.version_info >= (3, 9):
|
||||
+ if not test_path.is_relative_to(path):
|
||||
+ # points outside root, e.g. via `filename='../foo'`
|
||||
+ continue
|
||||
+ else:
|
||||
+ # is_relative_to is new in 3.9
|
||||
+ try:
|
||||
+ test_path.relative_to(path)
|
||||
+ except ValueError:
|
||||
+ # points outside root, e.g. via `filename='../foo'`
|
||||
+ continue
|
||||
+ # make sure we don't call is_file before we know it's a file within a prefix
|
||||
+ # GHSA-hrw6-wg82-cm62 - can leak password hash on windows.
|
||||
+ if test_path.is_file():
|
||||
+ return os.path.abspath(test_path)
|
||||
|
||||
msg = f"File {filename!r} does not exist in any of the search paths: {path_dirs!r}"
|
||||
raise OSError(msg)
|
||||
|
||||
|
||||
-def expand_path(s: str) -> str:
|
||||
- """Expand $VARS and ~names in a string, like a shell
|
||||
-
|
||||
- :Examples:
|
||||
- In [2]: os.environ['FOO']='test'
|
||||
- In [3]: expand_path('variable FOO is $FOO')
|
||||
- Out[3]: 'variable FOO is test'
|
||||
- """
|
||||
- # This is a pretty subtle hack. When expand user is given a UNC path
|
||||
- # on Windows (\\server\share$\%username%), os.path.expandvars, removes
|
||||
- # the $ to get (\\server\share\%username%). I think it considered $
|
||||
- # alone an empty var. But, we need the $ to remains there (it indicates
|
||||
- # a hidden share).
|
||||
- if os.name == "nt":
|
||||
- s = s.replace("$\\", "IPYTHON_TEMP")
|
||||
- s = os.path.expandvars(os.path.expanduser(s))
|
||||
- if os.name == "nt":
|
||||
- s = s.replace("IPYTHON_TEMP", "$\\")
|
||||
- return s
|
||||
-
|
||||
-
|
||||
def import_item(name: str) -> Any:
|
||||
"""Import and return ``bar`` given the string ``foo.bar``.
|
||||
Calling ``bar = import_item("foo.bar")`` is the functional equivalent of
|
||||
diff --git a/tests/test_utils.py b/tests/test_utils.py
|
||||
index 83d2a1d92..fdbfd6058 100644
|
||||
--- a/tests/test_utils.py
|
||||
+++ b/tests/test_utils.py
|
||||
@@ -13,6 +13,7 @@ from traitlets.tests.utils import check_help_all_output
|
||||
from jupyter_server.utils import (
|
||||
check_pid,
|
||||
check_version,
|
||||
+ filefind,
|
||||
is_namespace_package,
|
||||
path2url,
|
||||
run_sync_in_loop,
|
||||
@@ -125,3 +126,41 @@ def test_unix_socket_in_use(tmp_path):
|
||||
sock.listen(0)
|
||||
assert unix_socket_in_use(server_address)
|
||||
sock.close()
|
||||
+
|
||||
+
|
||||
+@pytest.mark.parametrize(
|
||||
+ "filename, result",
|
||||
+ [
|
||||
+ ("/foo", OSError),
|
||||
+ ("../c/in-c", OSError),
|
||||
+ ("in-a", "a/in-a"),
|
||||
+ ("in-b", "b/in-b"),
|
||||
+ ("in-both", "a/in-both"),
|
||||
+ (r"\in-a", OSError),
|
||||
+ ("not-found", OSError),
|
||||
+ ],
|
||||
+)
|
||||
+def test_filefind(tmp_path, filename, result):
|
||||
+ a = tmp_path / "a"
|
||||
+ a.mkdir()
|
||||
+ b = tmp_path / "b"
|
||||
+ b.mkdir()
|
||||
+ c = tmp_path / "c"
|
||||
+ c.mkdir()
|
||||
+ for parent in (a, b):
|
||||
+ with parent.joinpath("in-both").open("w"):
|
||||
+ pass
|
||||
+ with a.joinpath("in-a").open("w"):
|
||||
+ pass
|
||||
+ with b.joinpath("in-b").open("w"):
|
||||
+ pass
|
||||
+ with c.joinpath("in-c").open("w"):
|
||||
+ pass
|
||||
+
|
||||
+ if isinstance(result, str):
|
||||
+ found = filefind(filename, [str(a), str(b)])
|
||||
+ found_relative = Path(found).relative_to(tmp_path)
|
||||
+ assert str(found_relative) == result
|
||||
+ else:
|
||||
+ with pytest.raises(result):
|
||||
+ filefind(filename, [str(a), str(b)])
|
||||
--
|
||||
2.42.0.windows.2
|
||||
|
||||
@ -1,12 +1,13 @@
|
||||
Name: python-jupyter-server
|
||||
Version: 2.13.0
|
||||
Release: 1
|
||||
Release: 2
|
||||
Summary: The backend for Jupyter web applications
|
||||
|
||||
License: BSD-3-Clause
|
||||
URL: https://pypi.org/project/jupyter-server/#files
|
||||
Source0: %{url}/archive/%{version}/jupyter_server-%{version}.tar.gz
|
||||
Patch1: 0001-modify-the-version-limit-for-jupyter-core.patch
|
||||
Patch3000: backport-CVE-2024-35178-GHSA-hrw6-wg82-cm62.patch
|
||||
BuildArch: noarch
|
||||
|
||||
%global _description %{expand:
|
||||
@ -46,6 +47,9 @@ BuildRequires: python3-hatch-jupyter-builder
|
||||
%{python3_sitelib}/jupyter_server/
|
||||
|
||||
%changelog
|
||||
* Fri Oct 25 2024 liningjie <liningjie@xfusion.com> - 2.13.0-2
|
||||
- Fix CVE-2024-35178
|
||||
|
||||
* Wed Mar 6 2024 Dongxing Wang <dongxing.wang_a@thundersoft.com> - 2.13.0-1
|
||||
- Update package with version 2.13.0
|
||||
Add an option to have authentication enabled for all endpoints by default
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user