>From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Pavel Sanda <sanda () lyx org>
Date: Thu, 4 Jun 2026 13:20:02 +0200
Subject: [PATCH] Hardening case 00b - arbitrary command execution via lyx2lyx invocation

convertLyXFormat() builds the lyx2lyx command as a shell string and passes
the filenames through quoteName(), which leaves active characters alive.
Crafted filename on an old-format .lyx injects arbitrary commands.

Fires on .lyx load (old-format file needing conversion).

Tier 00 hotfix: single-quote the filename args on POSIX, keep quoteName on Windows.
Tier 01 DiD will land in master (argv-form lyx2lyx).

Assisted-by: Claude Opus 4.7
---
 src/Buffer.cpp | 16 +++++++++++++---
 1 file changed, 13 insertions(+), 3 deletions(-)

diff --git a/src/Buffer.cpp b/src/Buffer.cpp
index 5471f89cfa..4466b5d131 100644
--- a/src/Buffer.cpp
+++ b/src/Buffer.cpp
@@ -1402,12 +1402,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.toSafeFilesystemEncoding())
-		<< ' ' << quoteName(fn.toSafeFilesystemEncoding());
+		<< " -o " << sh_quote(tmpfile.toSafeFilesystemEncoding())
+		<< ' ' << sh_quote(fn.toSafeFilesystemEncoding());
 	string const command_str = command.str();
 
 	LYXERR(Debug::INFO, "Running '" << command_str << '\'');
