From c9ec76aa0db65b52c96dd3973a755367a9c41755 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 28 Jun 2024 21:27:44 +1000 Subject: [PATCH] Raise FileNotFoundError if show_file() path does not exist --- Tests/test_imageshow.py | 22 ++++++++++++++++++++++ docs/releasenotes/10.4.0.rst | 34 ++++++++-------------------------- src/PIL/ImageShow.py | 16 ++++++++++++++++ 3 files changed, 46 insertions(+), 26 deletions(-) diff --git a/Tests/test_imageshow.py b/Tests/test_imageshow.py index 4e9291fbbfb..0bff4389607 100644 --- a/Tests/test_imageshow.py +++ b/Tests/test_imageshow.py @@ -1,5 +1,6 @@ from __future__ import annotations +import os from typing import Any import pytest @@ -65,6 +66,27 @@ def test_show_without_viewers() -> None: ImageShow._viewers = viewers +@pytest.mark.parametrize( + "viewer", + ( + ImageShow.Viewer(), + ImageShow.WindowsViewer(), + ImageShow.MacViewer(), + ImageShow.XDGViewer(), + ImageShow.DisplayViewer(), + ImageShow.GmDisplayViewer(), + ImageShow.EogViewer(), + ImageShow.XVViewer(), + ImageShow.IPythonViewer(), + ), +) +def test_show_file(viewer: ImageShow.Viewer) -> None: + assert not os.path.exists("missing.png") + + with pytest.raises(FileNotFoundError): + viewer.show_file("missing.png") + + def test_viewer() -> None: viewer = ImageShow.Viewer() diff --git a/docs/releasenotes/10.4.0.rst b/docs/releasenotes/10.4.0.rst index 96300c008e8..8d3706be617 100644 --- a/docs/releasenotes/10.4.0.rst +++ b/docs/releasenotes/10.4.0.rst @@ -4,21 +4,16 @@ Security ======== -TODO -^^^^ - -TODO - -:cve:`YYYY-XXXXX`: TODO -^^^^^^^^^^^^^^^^^^^^^^^ - -TODO +ImageShow.WindowsViewer.show_file +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Backwards Incompatible Changes -============================== +If an attacker has control over the ``path`` passed to +``ImageShow.WindowsViewer.show_file()``, they may be able to +execute arbitrary shell commands. -TODO -^^^^ +To prevent this, a :py:exc:`FileNotFoundError` will be raised if the ``path`` +does not exist as a file. To provide a consistent experience, the error has +been added to all :py:class:`~PIL.ImageShow` viewers. Deprecations ============ @@ -46,14 +41,6 @@ ImageDraw.getdraw hints parameter The ``hints`` parameter in :py:meth:`~PIL.ImageDraw.getdraw()` has been deprecated. -API Changes -=========== - -TODO -^^^^ - -TODO - API Additions ============= @@ -64,11 +51,6 @@ Added :py:meth:`~PIL.ImageDraw.ImageDraw.circle`. It provides the same functiona :py:meth:`~PIL.ImageDraw.ImageDraw.ellipse`, but instead of taking a bounding box, it takes a center point and radius. -TODO -^^^^ - -TODO - Other Changes ============= diff --git a/src/PIL/ImageShow.py b/src/PIL/ImageShow.py index f60b1e11e18..037d6f492ce 100644 --- a/src/PIL/ImageShow.py +++ b/src/PIL/ImageShow.py @@ -118,6 +118,8 @@ def show_file(self, path: str, **options: Any) -> int: """ Display given file. """ + if not os.path.exists(path): + raise FileNotFoundError os.system(self.get_command(path, **options)) # nosec return 1 @@ -142,6 +144,8 @@ def show_file(self, path: str, **options: Any) -> int: """ Display given file. """ + if not os.path.exists(path): + raise FileNotFoundError subprocess.Popen( self.get_command(path, **options), shell=True, @@ -171,6 +175,8 @@ def show_file(self, path: str, **options: Any) -> int: """ Display given file. """ + if not os.path.exists(path): + raise FileNotFoundError subprocess.call(["open", "-a", "Preview.app", path]) executable = sys.executable or shutil.which("python3") if executable: @@ -215,6 +221,8 @@ def show_file(self, path: str, **options: Any) -> int: """ Display given file. """ + if not os.path.exists(path): + raise FileNotFoundError subprocess.Popen(["xdg-open", path]) return 1 @@ -237,6 +245,8 @@ def show_file(self, path: str, **options: Any) -> int: """ Display given file. """ + if not os.path.exists(path): + raise FileNotFoundError args = ["display"] title = options.get("title") if title: @@ -259,6 +269,8 @@ def show_file(self, path: str, **options: Any) -> int: """ Display given file. """ + if not os.path.exists(path): + raise FileNotFoundError subprocess.Popen(["gm", "display", path]) return 1 @@ -275,6 +287,8 @@ def show_file(self, path: str, **options: Any) -> int: """ Display given file. """ + if not os.path.exists(path): + raise FileNotFoundError subprocess.Popen(["eog", "-n", path]) return 1 @@ -299,6 +313,8 @@ def show_file(self, path: str, **options: Any) -> int: """ Display given file. """ + if not os.path.exists(path): + raise FileNotFoundError args = ["xv"] title = options.get("title") if title: