Put CVE fix in for CVE-2022-22817 Restrict builtins for ImageMath.eval()

Put in fixes from CVE
Update release documentation

Ensure all tests pass as before
This commit is contained in:
Frederick Price 2023-02-15 09:55:13 -05:00
parent 538ac8d360
commit 1184cbf916
5 changed files with 79 additions and 34 deletions

View File

@ -1,4 +1,5 @@
from __future__ import print_function
import pytest
from PIL import Image, ImageMath
@ -56,6 +57,10 @@ class TestImageMath(PillowTestCase):
pixel(ImageMath.eval("float(B)**33", images)), "F 8589934592.0"
)
def test_prevent_exec(self):
with pytest.raises(ValueError):
ImageMath.eval("exec('pass')")
def test_logical(self):
self.assertEqual(pixel(ImageMath.eval("not A", images)), 0)
self.assertEqual(pixel(ImageMath.eval("A and B", images)), "L 2")

View File

@ -1,4 +1,8 @@
from PIL import Image, ImageShow
from tempfile import mkdtemp
from os import rmdir
from os.path import join
from shutil import rmtree
from .helper import PillowTestCase, hopper, on_ci, unittest
@ -50,3 +54,11 @@ class TestImageShow(PillowTestCase):
def test_viewers(self):
for viewer in ImageShow._viewers:
viewer.get_command("test.jpg")
def test_file_deprecated(self):
tmp_path = mkdtemp()
f = join(tmp_path, "temp.jpg")
for viewer in ImageShow._viewers:
hopper().save(f)
viewer.show_file(file=f)
# viewer.show_file()

View File

@ -0,0 +1,21 @@
6.2.2.3
-------
Security
========
This release addresses several critical CVEs.
restrict builtins available to ImageMath.eval
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
CVE-2022-22817 Restrict builtins for ImageMath.eval()
To limit :py:class:`PIL.ImageMath` to working with images, Pillow will now restrict the
builtins available to :py:meth:`PIL.ImageMath.eval`. This will help prevent problems
arising if users evaluate arbitrary expressions, such as
``ImageMath.eval("exec(exit())")``.
CVE-2022-24303 Pillow before 9.0.1 allows attackers to delete files because spaces in temporary pathnames are mishandled.
A bunch of changes related to temporary files and race conditions are fixed

View File

@ -264,7 +264,13 @@ def eval(expression, _dict={}, **kw):
if hasattr(v, "im"):
args[k] = _Operand(v)
out = builtins.eval(expression, args)
# out = builtins.eval(expression, args)
code = compile(expression, "<string>", "eval")
for name in code.co_names:
if name not in args and name != "abs":
raise ValueError(f"'{name}' not allowed")
out = builtins.eval(expression, {"__builtins": {"abs": abs}}, args)
try:
return out.im
except AttributeError:

View File

@ -17,7 +17,6 @@ from __future__ import print_function
import os
import subprocess
import sys
import tempfile
from PIL import Image
@ -98,6 +97,15 @@ class Viewer(object):
os.system(self.get_command(file, **options))
return 1
def _remove_path_after_delay(self, path):
subprocess.Popen(
[
sys.executable,
"-c",
"import os, sys, time; time.sleep(20); os.remove(sys.argv[1])",
path,
]
)
# --------------------------------------------------------------------
@ -136,16 +144,9 @@ elif sys.platform == "darwin":
def show_file(self, file, **options):
"""Display given file"""
fd, path = tempfile.mkstemp()
with os.fdopen(fd, "w") as f:
f.write(file)
with open(path, "r") as f:
subprocess.Popen(
["im=$(cat); open -a Preview.app $im; sleep 20; rm -f $im"],
shell=True,
stdin=f,
)
os.remove(path)
subprocess.call(["open", "-a", "Preview.app", file])
self._remove_path_after_delay(file)
return 1
register(MacViewer)
@ -168,49 +169,49 @@ else:
format = "PNG"
options = {"compress_level": 1}
def get_command(self, file, **options):
command = self.get_command_ex(file, **options)[0]
return "(%s %s; rm -f %s)&" % (command, quote(file), quote(file))
def get_command(self,file,**options):
return " ".join(self.get_command_ex(file,options=options))
def get_command_ex(self, file, **options):
return ["display",file]
def show_file(self, file, **options):
"""Display given file"""
fd, path = tempfile.mkstemp()
with os.fdopen(fd, "w") as f:
f.write(file)
with open(path, "r") as f:
command = self.get_command_ex(file, **options)[0]
subprocess.Popen(
["im=$(cat);" + command + " $im; rm -f $im"], shell=True, stdin=f
)
os.remove(path)
"""
Display given file.
"""
args = self.get_command_ex(file,**options)
subprocess.Popen(args)
self._remove_path_after_delay(file)
return 1
# implementations
class DisplayViewer(UnixViewer):
def get_command_ex(self, file, **options):
command = executable = "display"
return command, executable
return ["display", file]
if which("display"):
register(DisplayViewer)
class EogViewer(UnixViewer):
def get_command_ex(self, file, **options):
command = executable = "eog"
return command, executable
return ["eog", "-n", file]
if which("eog"):
register(EogViewer)
class XVViewer(UnixViewer):
def get_command_ex(self, file, title=None, **options):
def get_command_ex(self, file, **options):
# note: xv is pretty outdated. most modern systems have
# imagemagick's display command instead.
command = executable = "xv"
if title:
command += " -name %s" % quote(title)
return command, executable
args = ["xv"]
if options.get("title") is not None:
args.append("-name")
args.append(options.get("title"))
args.append(file)
return args
if which("xv"):
register(XVViewer)