diff --git a/Lib/pydoc.py b/Lib/pydoc.py
index 282a9179983407..f1090c095b07f8 100755
--- a/Lib/pydoc.py
+++ b/Lib/pydoc.py
@@ -2544,9 +2544,42 @@ def bltinlink(name):
'key = %s' % key, '#ffffff', '#ee77aa', '
'.join(results))
return 'Search Results', contents
+ def validate_source_path(path):
+ with warnings.catch_warnings():
+ warnings.filterwarnings('ignore') # ignore problems during import
+ def onerror(modname):
+ pass
+ for importer, modname, ispkg in pkgutil.walk_packages(onerror=onerror):
+ try:
+ spec = pkgutil._get_spec(importer, modname)
+ except SyntaxError:
+ # raised by tests for bad coding cookies or BOM
+ continue
+ loader = spec.loader
+ if hasattr(loader, 'get_source'):
+ try:
+ source = loader.get_source(modname)
+ except Exception:
+ continue
+ if hasattr(loader, 'get_filename'):
+ sourcepath = loader.get_filename(modname)
+ if path == sourcepath:
+ return
+ else:
+ try:
+ module = importlib._bootstrap._load(spec)
+ except ImportError:
+ continue
+ sourcepath = getattr(module, '__file__', None)
+ if path == sourcepath:
+ return
+ else:
+ raise ValueError('not found {found}')
+
def html_getfile(path):
"""Get and display a source file listing safely."""
path = urllib.parse.unquote(path)
+ validate_source_path(path)
with tokenize.open(path) as fp:
lines = html.escape(fp.read())
body = '
%s' % lines diff --git a/Lib/test/test_pydoc.py b/Lib/test/test_pydoc.py index 2f502627f4d0a2..7356ff9df121b7 100644 --- a/Lib/test/test_pydoc.py +++ b/Lib/test/test_pydoc.py @@ -1369,7 +1369,11 @@ def test_url_requests(self): ("topics", "Pydoc: Topics"), ("keywords", "Pydoc: Keywords"), ("pydoc", "Pydoc: module pydoc"), + ("test.test_pydoc", "Pydoc: module test.test_pydoc"), ("get?key=pydoc", "Pydoc: module pydoc"), + ("get?key=test.test_pydoc", "Pydoc: module test.test_pydoc"), + ("get?key=html", "Pydoc: package html"), + ("get?key=sys", "Pydoc: built-in module sys"), ("search?key=pydoc", "Pydoc: Search Results"), ("topic?key=def", "Pydoc: KEYWORD def"), ("topic?key=STRINGS", "Pydoc: TOPIC STRINGS"), @@ -1381,11 +1385,18 @@ def test_url_requests(self): for url, title in requests: self.call_url_handler(url, title) - path = string.__file__ + # File in restricted walk_packages path. + path = __file__ title = "Pydoc: getfile " + path url = "getfile?key=" + path self.call_url_handler(url, title) + # File outside of restricted walk_packages path. + path = pydoc.__file__ + title = "Pydoc: Error - getfile?key=" + path + url = "getfile?key=" + path + self.call_url_handler(url, title) + class TestHelper(unittest.TestCase): def test_keywords(self): diff --git a/Misc/NEWS.d/next/Security/2021-01-26-15-00-41.bpo-42988.PGpcqg.rst b/Misc/NEWS.d/next/Security/2021-01-26-15-00-41.bpo-42988.PGpcqg.rst new file mode 100644 index 00000000000000..f38c1987f97b53 --- /dev/null +++ b/Misc/NEWS.d/next/Security/2021-01-26-15-00-41.bpo-42988.PGpcqg.rst @@ -0,0 +1,3 @@ +The ``/getfile?key=`` route of the :mod:`pydoc` Web server checks now that +the argument is the file path of the source of one of modules. It prevents +reading arbitrary files.