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

Reply via email to