Emacs code completion can cause compromise
Emacs has had a few bugs related to accidentally permitting the execution of untrusted code. Unfortunately, it seems as though another bug of that sort has appeared — and may be harder to patch, because the problem comes from the way Emacs handles expansion of Lisp macros in code being analyzed. The vulnerability is only practically exploitable in a non-default configuration, so not every Emacs user has something to worry about. The Emacs developers are reportedly working on a fix, but have not yet shared details about it. In the meantime, every Emacs version since at least 26.1 (released in May 2018) through the current development version is vulnerable.
Eshel Yaron publicly disclosed the problem on November 27, although they reported it to the Emacs maintainers in August. The problem has two parts: expanding a macro in Emacs Lisp (Elisp) can run arbitrary code (including invoking a shell to run arbitrary commands), and common operations such as code-completion or jump-to-definition in Elisp files can require macro expansion. Since those operations are quite useful for reading and understanding code, many Emacs users have them enabled.
One of the things that makes the Lisp family of languages unique is the flexibility of macros. Conceptually, a Lisp macro is a program that is run on the abstract syntax tree of its argument, and produces a new abstract syntax tree to replace it. Different Lisp implementations add various niceties on top of that, but the core of Elisp's implementation just involves calling the macro in the same context as whatever code originally required macro expansion. Since a macro can invoke arbitrary code, this means running that code in Emacs, with the full privileges of the user running Emacs.
Unfortunately, performing macro expansion is a necessary prerequisite to examining many common Elisp idioms. For example, there are macros that create and use local variables. So even something as simple as identifying where a definition occurs can require performing macro expansion in order to find the answer. There are several common packages that perform macro expansion on code that is simply being edited in Emacs; Yaron highlighted Flymake and Flycheck, two Emacs packages that provide syntax checking and linting, as particularly prominent examples. Yaron's own completion preview mode, which has been accepted by the Emacs project for inclusion in version 30, needs to use macro expansion when it is completing names in the source file being edited.
So, while the current default configuration is not vulnerable, many users' configurations will be. Interested readers can save this in a file and then open it in Emacs to see whether their configuration is affected:
;; -*- mode: emacs-lisp -*-
(rx (eval (call-process "touch" nil nil nil "/tmp/owned")))
If viewing the file creates /tmp/owned, then the current Emacs configuration is vulnerable. Anyone who can get a file onto the local system and then induce the user to open it in Emacs could potentially take advantage. On the one hand, this is not the most worrying exploit path, since it cannot be triggered remotely. On the other hand, many Emacs users are fairly technical people, who may be used to, for example, downloading an installation script and perusing it with Emacs before running it.
Yaron's post
sparked a certain amount of discussion on the Emacs mailing list. Eli
Zaretskii said that
a "solution is in the works
", but
did not think that it was a good idea
to share details of the proposed solution publicly yet.
There was some debate over whether the fact that Emacs's default configuration is unaffected meant that users who enabled flycheck, flymake, or similar modes had essentially opted in to the behavior. The general consensus was, however, that users would find the fact that these modes opened them up to arbitrary code execution less than obvious, and that even if it were documented that would not be sufficient.
Elisp isn't the first language that has had to contend with this problem, of course. Yaron pointed out that Prolog, another language known for its flexible metaprogramming, has a sandbox for safely executing arbitrary code. Guile, the Scheme implementation that several people are trying to integrate into Emacs, has a similar ability. It's possible that the fix the Emacs developers are working on is a sandbox of the same kind.
Currently, there's no sign that this arbitrary code execution vulnerability has been exploited in the wild; the impact is entirely hypothetical. But until the Emacs developers manage to mitigate it, Emacs users might do well to be cautious opening files from untrusted sources. I have added these lines to my own Emacs config, to prevent the emacs-lisp major mode from loading automatically:
(rassq-delete-all 'emacs-lisp-mode auto-mode-alist)
(setq enable-local-variables nil)
The first line prevents Emacs from associating ".el" files with the major mode, and the second line prevents the editor from obeying local variables in files (such as the "mode" line in the example above). Preventing the major mode from activating automatically keeps Flymake (and many other code-analysis commands) from activating as well — although it is, arguably, a bigger hammer than is really required. Since every Emacs configuration is different, individual Emacs users may need to make different changes to render their setup safe from this vulnerability while preserving the functionality that matters to them.
