From acc60241a70b920eaf04fce41a8cf0a77010fb7d Mon Sep 17 00:00:00 2001 From: Benjamin Root Date: Fri, 13 Mar 2026 09:07:57 -0400 Subject: [PATCH] Merge pull request #31282 from scottshambaugh/tex_no_shell SEC: Block shell escapes in latex and ps commands (cherry picked from commit 8ff895d0750f3b16c3214b38a91ad78029c82df7) The test that was edited had significant updates on main, so the old test was kept on backport and no similar call exists in the old test. --- lib/matplotlib/backends/backend_pgf.py | 6 +++--- lib/matplotlib/backends/backend_ps.py | 6 ++++-- lib/matplotlib/testing/__init__.py | 3 ++- lib/matplotlib/texmanager.py | 4 ++-- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/lib/matplotlib/backends/backend_pgf.py b/lib/matplotlib/backends/backend_pgf.py index 48b6e8ac152c..2d2e24c3286c 100644 --- a/lib/matplotlib/backends/backend_pgf.py +++ b/lib/matplotlib/backends/backend_pgf.py @@ -281,7 +281,7 @@ def _setup_latex_process(self, *, expect_reply=True): # it. try: self.latex = subprocess.Popen( - [mpl.rcParams["pgf.texsystem"], "-halt-on-error"], + [mpl.rcParams["pgf.texsystem"], "-halt-on-error", "-no-shell-escape"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, encoding="utf-8", cwd=self.tmpdir) except FileNotFoundError as err: @@ -848,7 +848,7 @@ def print_pdf(self, fname_or_fh, *, metadata=None, **kwargs): texcommand = mpl.rcParams["pgf.texsystem"] cbook._check_and_log_subprocess( [texcommand, "-interaction=nonstopmode", "-halt-on-error", - "figure.tex"], _log, cwd=tmpdir) + "-no-shell-escape", "figure.tex"], _log, cwd=tmpdir) with ((tmppath / "figure.pdf").open("rb") as orig, cbook.open_file_cm(fname_or_fh, "wb") as dest): shutil.copyfileobj(orig, dest) # copy file contents to target @@ -965,7 +965,7 @@ def _run_latex(self): tex_source.write_bytes(self._file.getvalue()) cbook._check_and_log_subprocess( [texcommand, "-interaction=nonstopmode", "-halt-on-error", - tex_source], + "-no-shell-escape", tex_source], _log, cwd=tmpdir) shutil.move(tex_source.with_suffix(".pdf"), self._output_name) diff --git a/lib/matplotlib/backends/backend_ps.py b/lib/matplotlib/backends/backend_ps.py index f1f914ae5420..4dfdb2a6a095 100644 --- a/lib/matplotlib/backends/backend_ps.py +++ b/lib/matplotlib/backends/backend_ps.py @@ -1257,8 +1257,9 @@ def _convert_psfrags(tmppath, psfrags, paper_width, paper_height, orientation): with TemporaryDirectory() as tmpdir: psfile = os.path.join(tmpdir, "tmp.ps") + # -R1 is a security flag used to prevent shell command execution cbook._check_and_log_subprocess( - ['dvips', '-q', '-R0', '-o', psfile, dvifile], _log) + ['dvips', '-q', '-R1', '-o', psfile, dvifile], _log) shutil.move(psfile, tmppath) # check if the dvips created a ps in landscape paper. Somehow, @@ -1302,7 +1303,7 @@ def gs_distill(tmpfile, eps=False, ptype='letter', bbox=None, rotated=False): cbook._check_and_log_subprocess( [mpl._get_executable_info("gs").executable, - "-dBATCH", "-dNOPAUSE", "-r%d" % dpi, "-sDEVICE=ps2write", + "-dBATCH", "-dNOPAUSE", "-dSAFER", "-r%d" % dpi, "-sDEVICE=ps2write", *paper_option, f"-sOutputFile={psfile}", tmpfile], _log) @@ -1346,6 +1347,7 @@ def xpdf_distill(tmpfile, eps=False, ptype='letter', bbox=None, rotated=False): # happy (https://ghostscript.com/doc/9.56.1/Use.htm#MS_Windows). cbook._check_and_log_subprocess( ["ps2pdf", + "-dSAFER", "-dAutoFilterColorImages#false", "-dAutoFilterGrayImages#false", "-sAutoRotatePages#None", diff --git a/lib/matplotlib/testing/__init__.py b/lib/matplotlib/testing/__init__.py index 19113d399626..73b1645468a6 100644 --- a/lib/matplotlib/testing/__init__.py +++ b/lib/matplotlib/testing/__init__.py @@ -164,7 +164,8 @@ def _check_for_pgf(texsystem): """, encoding="utf-8") try: subprocess.check_call( - [texsystem, "-halt-on-error", str(tex_path)], cwd=tmpdir, + [texsystem, "-halt-on-error", "-no-shell-escape", + str(tex_path)], cwd=tmpdir, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) except (OSError, subprocess.CalledProcessError): return False diff --git a/lib/matplotlib/texmanager.py b/lib/matplotlib/texmanager.py index 0424aede16eb..6856329931ef 100644 --- a/lib/matplotlib/texmanager.py +++ b/lib/matplotlib/texmanager.py @@ -291,8 +291,8 @@ def make_dvi(cls, tex, fontsize): Path(tmpdir, "file.tex").write_text( cls._get_tex_source(tex, fontsize), encoding='utf-8') cls._run_checked_subprocess( - ["latex", "-interaction=nonstopmode", "--halt-on-error", - "file.tex"], tex, cwd=tmpdir) + ["latex", "-interaction=nonstopmode", "-halt-on-error", + "-no-shell-escape", "file.tex"], tex, cwd=tmpdir) Path(tmpdir, "file.dvi").replace(dvifile) # Also move the tex source to the main cache directory, but # only for backcompat.