commit 7fbc5ab3cf475d778d64ca92aedbd6cde73ba0e8
Author: Pavel Sanda <[email protected]>
Date: Wed Jul 15 14:10:29 2026 +0200
Security hardening backport for LyX 2.2.x
Accumulated backport of the coordinated LyX security release, encompassing
Tier 00 variants. Folded hardening cases (per-case detail in the advisory):
00a kpsewhich filename -> shell command open/export -> exec
00b lyx2lyx invocation filename open -> exec
00c graphics filename extension -> os.system() open -> exec
00g mangled graphics filename extension export -> exec
00h document basename -> conversion helpers open/import -> exec
[DiD]
00i document basename backtick in "..." export -> exec
00k \paperwidth/\paperheight -> parsecmd redirect export -> file
write
00h ships as defence-in-depth on 2.2: reviewed and applied, but the 2.4 PoC
does not reproduce here (a similar PoC variant is nonetheless likely).
Assisted-by: Claude Opus 4.8
---
src/Buffer.cpp | 18 +++++++++++++++---
src/BufferParams.cpp | 10 ++++++++++
src/graphics/GraphicsConverter.cpp | 9 ++++++++-
src/support/FileName.cpp | 12 +++++++++---
src/support/filetools.cpp | 19 +++++++++++++++++--
5 files changed, 59 insertions(+), 9 deletions(-)
diff --git a/src/Buffer.cpp b/src/Buffer.cpp
index ce345bce38..f13a71fabc 100644
--- a/src/Buffer.cpp
+++ b/src/Buffer.cpp
@@ -1286,12 +1286,22 @@ Buffer::ReadStatus Buffer::convertLyXFormat(FileName
const & fn,
// Run lyx2lyx:
// $python$ "$lyx2lyx$" -t $LYX_FORMAT$ -o "$tempfile$" "$filetoread$"
+
+ // guard against command expansion in filename strings on linux,
+ // keep " on windows
+ auto sh_quote = [](string const & s) -> string {
+#ifdef _WIN32
+ return quoteName(s);
+#else
+ return '\'' + subst(s, "'", "'\\''") + '\'';
+#endif
+ };
ostringstream command;
command << os::python()
- << ' ' << quoteName(lyx2lyx.toFilesystemEncoding())
+ << ' ' << sh_quote(lyx2lyx.toFilesystemEncoding())
<< " -t " << convert<string>(LYX_FORMAT)
- << " -o " << quoteName(tmpfile.toFilesystemEncoding())
- << ' ' << quoteName(fn.toSafeFilesystemEncoding());
+ << " -o " << sh_quote(tmpfile.toFilesystemEncoding())
+ << ' ' << sh_quote(fn.toSafeFilesystemEncoding());
string const command_str = command.str();
LYXERR(Debug::INFO, "Running '" << command_str << '\'');
@@ -4182,6 +4192,8 @@ Buffer::ExportStatus Buffer::doExport(string const &
target, bool put_in_tempdir
formats.extension(backend_format));
LYXERR(Debug::FILES, "filename=" << filename);
+ // (00i-wide) makeLatexName keep-set is now shell-safe; no re-sanitize
here.
+
// Plain text backend
if (backend_format == "text") {
runparams.flavor = OutputParams::TEXT;
diff --git a/src/BufferParams.cpp b/src/BufferParams.cpp
index a379e2740d..6b869fb7ab 100644
--- a/src/BufferParams.cpp
+++ b/src/BufferParams.cpp
@@ -951,8 +951,18 @@ string BufferParams::readToken(Lexer & lex, string const &
token,
lcolor.setColor("boxbgcolor", color);
} else if (token == "\\paperwidth") {
lex >> paperwidth;
+ if (!paperwidth.empty() && !isValidLength(paperwidth)) {
+ lyxerr << "Rejecting non-Length \\paperwidth value: "
+ << paperwidth << endl;
+ paperwidth.clear();
+ }
} else if (token == "\\paperheight") {
lex >> paperheight;
+ if (!paperheight.empty() && !isValidLength(paperheight)) {
+ lyxerr << "Rejecting non-Length \\paperheight value: "
+ << paperheight << endl;
+ paperheight.clear();
+ }
} else if (token == "\\leftmargin") {
lex >> leftmargin;
} else if (token == "\\topmargin") {
diff --git a/src/graphics/GraphicsConverter.cpp
b/src/graphics/GraphicsConverter.cpp
index 4bd5cf1ef4..0ffe4840f3 100644
--- a/src/graphics/GraphicsConverter.cpp
+++ b/src/graphics/GraphicsConverter.cpp
@@ -28,6 +28,8 @@
#include "support/TempFile.h"
#include <sstream>
+#include <algorithm>
+#include <cctype>
#include <fstream>
using namespace std;
@@ -288,7 +290,12 @@ static void build_script(string const & from_file,
theConverters().getPath(from_format, to_format);
// Create a temporary base file-name for all intermediate steps.
- string const from_ext = getExtension(from_file);
+ // The extension string is user-controlled. Avoid metacharacters
+ // to prevent havoc down the pipeline.
+ string from_ext = getExtension(from_file);
+ from_ext.erase(remove_if(from_ext.begin(), from_ext.end(),
+ [](unsigned char c){ return !(isalnum(c) || c == '_' || c ==
'-'); }),
+ from_ext.end());
TempFile tempfile(addExtension("gconvertXXXXXX", from_ext));
tempfile.setAutoRemove(false);
string outfile = tempfile.name().toFilesystemEncoding();
diff --git a/src/support/FileName.cpp b/src/support/FileName.cpp
index 6d40687a25..9b3620e643 100644
--- a/src/support/FileName.cpp
+++ b/src/support/FileName.cpp
@@ -944,14 +944,20 @@ string DocFileName::mangledFileName(string const & dir)
const
// are forbidden: '/', '.', ' ', and ':'.
// On windows it is not possible to create files with '<', '>' or '?'
// in the name.
+ // We forbid ';', '=' as they coudl become active in shell.
static string const keep = "abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
- "+-0123456789;=";
+ "+-0123456789";
string::size_type pos = 0;
while ((pos = mname.find_first_not_of(keep, pos)) != string::npos)
mname[pos++] = '_';
- // Add the extension back on
- mname = support::changeExtension(mname, getExtension(name));
+ // Add the extension back on, but sanitize from metachars,
+ // it's user-controlled string.
+ string ext = getExtension(name);
+ pos = 0;
+ while ((pos = ext.find_first_not_of(keep, pos)) != string::npos)
+ ext[pos++] = '_';
+ mname = support::changeExtension(mname, ext);
// Prepend a counter to the filename. This is necessary to make
// the mangled name unique.
diff --git a/src/support/filetools.cpp b/src/support/filetools.cpp
index ff9ea8110a..044b3e77a2 100644
--- a/src/support/filetools.cpp
+++ b/src/support/filetools.cpp
@@ -202,7 +202,7 @@ FileName const makeLatexName(FileName const & file)
// a non-latin world out there...
string const keep = "abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
- "@!'()*+,-./0123456789:;<=>?[]`|";
+ "0123456789+-._,@";
string::size_type pos = 0;
while ((pos = name.find_first_not_of(keep, pos)) != string::npos)
@@ -1153,7 +1153,22 @@ FileName const findtexfile(string const & fil, string
const & /*format*/)
// tfm - TFMFONTS, TEXFONTS
// This means that to use kpsewhich in the best possible way we
// should help it by setting additional path in the approp. envir.var.
- string const kpsecmd = "kpsewhich " + fil;
+
+ if (fil.empty())
+ return FileName();
+
+ // Wrap fil in the shell's quoting form that disables the relevant
+ // metacharacter set.
+#ifdef _WIN32
+ // Reject '"' in filename, can't be backslashed & forbidden by NTFS
anyway
+ if (fil.find('"') != string::npos)
+ return FileName();
+ // disable metacharacters
+ string const kpsecmd = "kpsewhich -- \"" + fil + "\"";
+#else
+ // disable metacharacters & escape existing '
+ string const kpsecmd = "kpsewhich -- '" + subst(fil, "'", "'\\''") +
"'";
+#endif
cmd_ret const c = runCommand(kpsecmd);
--
lyx-cvs mailing list
[email protected]
https://lists.lyx.org/mailman/listinfo/lyx-cvs