>From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Pavel Sanda Date: Wed, 8 Jul 2026 12:26:10 +0200 Subject: [PATCH] Hardening case 00de (xindex) - version check + --restricted jail Stacks on the processing gate 00de: adds xindex to the processor table. For >= 1.07 ungated + appends --restricted to the spawned command so a hostile document cannot shadow xindex's own config/modules in CWD. Assisted-by: Claude Opus 4.8 --- src/LaTeX.cpp | 35 +++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/src/LaTeX.cpp b/src/LaTeX.cpp --- a/src/LaTeX.cpp +++ b/src/LaTeX.cpp @@ -613,6 +613,19 @@ && !checkProcessorAuth(doc_fname, tmp)) return Systemcall::KILLED; + // A --restricted-capable xindex (not gated, i.e. version >= the table + // threshold) is run with --restricted so a document cannot shadow + // xindex's own modules/configs from the current directory and get code + // executed. This also covers a document-supplied \index_command, which + // replaces the whole command string. Older xindex would reject the + // unknown flag, hence the !isProcessorGated guard (cached above). + { + string iprog; + split(tmp, iprog, ' '); + if (onlyFileName(iprog) == "xindex" && !isProcessorGated(tmp)) + tmp += " --restricted"; + } + Language const * doc_lang = languages.getLanguage(rp.document_language); if (contains(tmp, "$$x")) { @@ -844,6 +857,11 @@ { "upmendex", true, nullptr, nullptr, 0, 0 }, // biber: code-capable; fixed upstream at 2.22 { "biber", false, "--version", "version:\\s*([0-9]+)\\.([0-9]+)", 2, 22 }, + // xindex: code-capable (texlua); the CWD-shadowing RCE is closed by + // running it with --restricted (see runMakeIndex). Versions that support + // that flag are not gated. PIN this threshold to the xindex release that + // actually ships --restricted before enabling (placeholder: 1.07). + { "xindex", false, "--version", "xindex version ([0-9]+)\\.([0-9]+)", 1, 7 }, }; // False only when >= required version. @@ -871,8 +889,21 @@ break; } //safe because prog was matched against the table - cmd_ret const r = - runCommand(quoteName(prog) + ' ' + p.version_arg); + // Probe in a fresh empty directory, not the export dir: a + // texlua-based processor (xindex) executes CWD-first module loads + // before it prints its version, so probing where the document has + // written files would run planted code. createLyXTmpDir(FileName()) + // makes a unique dir under the system temp; on failure we leave the + // fail-safe gated=true. + cmd_ret r{}; + FileName const probe_dir = createLyXTmpDir(FileName()); + if (!probe_dir.empty()) { + FileName const saved = FileName::getcwd(); + probe_dir.chdir(); + r = runCommand(quoteName(prog) + ' ' + p.version_arg); + saved.chdir(); + probe_dir.destroyDirectory(); + } smatch m; regex const re(p.version_re); if (r.valid && regex_search(r.result, m, re)) {