|
|
Log in / Subscribe / Register

Emacs code completion can cause compromise

By Daroc Alden
December 18, 2024

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.



to post comments

Prompt for trusted files/projects

Posted Dec 18, 2024 16:40 UTC (Wed) by jbills (subscriber, #161176) [Link] (7 responses)

This is an inevitable problem in lots of different programming languages and extensions to editors. Fully sandboxing is not really a solution, as that would be quite intrusive, and would potentially limit useful functionality. VSCode solves this in a quite reasonable way, by prompting for trust when opening a directory and otherwise not allowing extensions to run on the files.

Prompt for trusted files/projects

Posted Dec 18, 2024 17:24 UTC (Wed) by smurf (subscriber, #17840) [Link] (1 responses)

Arguably it's only inevitable when the editor in question doesn't have a firm border ("sandbox") between its own code and the code it edits.

This is obviously a larger problem for editors written in the same dynamic language as the code they're editing … the temptation to not use a sandbox and simply run (part of) the code you're interpreting in a context that can access the editor's environment is just too great to ignore.

Prompt for trusted files/projects

Posted Dec 18, 2024 18:04 UTC (Wed) by iabervon (subscriber, #722) [Link]

It's additionally a step harder in the case where a supported use of the program is as an interactive interpreter with nice features around typing your commands. It'll execute arbitrary elisp that you type after typing M-:, and it'll execute arbitrary elisp that you type in *scratch* and then type C-x C-e, so there's a bunch of code that's working with elisp that's in buffers because the user intends to execute it, and a lot of code for making it easy to write the elisp you intend that is useful for both commands you want to execute and files you are editing.

Prompt for trusted files/projects

Posted Dec 18, 2024 17:51 UTC (Wed) by Nahor (subscriber, #51583) [Link] (4 responses)

> VSCode solves this in a quite reasonable way, by prompting for trust when opening a directory

At the same time, how can you judge if a directory is trustworthy or not if you can't look at the code? ... and even if you can and are willing to look at it, can you be truly judge it? Can you be reasonably sure that you didn't miss/overlook the one issue hidden in it?
And what about an xz scenario where the code used to be trustworthy but changed and became hostile?

Trust is usually just blind faith and has little to do with reality.

> Fully sandboxing is not really a solution, as that would be quite intrusive, and would potentially limit useful functionality

Sandboxing is much safer than trust though.

VSCode's prompt for trust

Posted Dec 18, 2024 17:53 UTC (Wed) by farnz (subscriber, #17727) [Link] (1 responses)

S Code still lets you look at the code after saying it's untrusted, but it won't run potentially hazardous extensions. You can change trust status of a directory at any time after opening - so you can open untrusted, review, then grant trust.

The remaining objections still apply, however.

VSCode's prompt for trust

Posted Dec 18, 2024 18:37 UTC (Wed) by Nahor (subscriber, #51583) [Link]

> [VS] Code still lets you look at the code after saying it's untrusted, but it won't run potentially hazardous extensions.

Of course, silly me. I'll add that one can also use `less` to look at the code before switching to VSCode. Or use `dd | hexdump` to look at the storage sectors directly. Ideally though, one wouldn't even use a computer to look at the storage.

The possibilities are endless, one _can_ (probably) look at the code in a safe way, I stand corrected :).

Prompt for trusted files/projects

Posted Dec 18, 2024 19:14 UTC (Wed) by NYKevin (subscriber, #129325) [Link] (1 responses)

IMHO the answer is fairly straightforward: If it's trying to modify the filesystem, and I did not intend for it to do that, then it's malicious and I want it off my computer now. Maybe some anti-malware security researcher can look at it, but as an end user, that is frankly not my problem.

Prompt for trusted files/projects

Posted Dec 18, 2024 19:38 UTC (Wed) by warrax (subscriber, #103205) [Link]

You don't even need writes to do a lot of damage. Even if you protect against arbitrary writes to the file system, exfiltration of secrets is still a huge issue -- arguably even more dangerous depending on the people being targeted.

Ultimately, something like fine-grained Capabilities will be necessary to actually have sane security around extensible software like editors, operating systems, etc. The current situation is entirely untenable.

Long since known

Posted Dec 18, 2024 17:50 UTC (Wed) by lunaryorn (guest, #111088) [Link]

I'm a bit surprised that this is presented as if it was a new issue, reported in private as if nobody knew, and even apparently deserves a CVE number.

This problem is well known since a long time, really.

Flycheck already discussed in 2016(!) that syntax checking can run arbitrary code in many languages, not just Emacs Lisp, see https://github.com/flycheck/flycheck/issues/894

FWIW my installation does not seem vulnerable

Posted Dec 18, 2024 20:35 UTC (Wed) by dskoll (subscriber, #1630) [Link] (8 responses)

I tried the test code in the article and no /tmp/owned file was created. I'm running Emacs 28.2+1-15+deb12u3 as shipped with Debian 12 ("bookworm")

I added the mitigations to my $HOME/.emacs file anyway.

FWIW my installation does not seem vulnerable

Posted Dec 18, 2024 20:52 UTC (Wed) by daroc (editor, #160859) [Link] (7 responses)

If your particular setup isn't affected, you don't need the tweaks I made to my config. As I mentioned, stock Emacs isn't actually affected. But Spacemacs, Doom Emacs, and related configuration frameworks are — plus anyone who configured their own installation to behave the same way. With the infinite diversity of Emacs configs, the only way to really be sure is to check.

FWIW my installation does not seem vulnerable

Posted Dec 18, 2024 20:54 UTC (Wed) by gray_-_wolf (subscriber, #131074) [Link] (6 responses)

Beware of that advice. My setup was not affected out of the box neither, but give a try opening this file (saved as /tmp/x.el):

;; -*- eval: (flymake-mode 1) -*-
(rx (eval (call-process "touch" nil nil nil "/tmp/owned")))

I will probably go with (setopt enable-local-eval nil) though, local variables are useful, so I will disable just the eval.

And thanks daroc for the article. :)

FWIW my installation does not seem vulnerable

Posted Dec 18, 2024 21:14 UTC (Wed) by dskoll (subscriber, #1630) [Link]

My setup was vulnerable to gray_-_wolf's example, and it was still vulnerable even after I did (setopt enable-local-eval nil), so I disabled local variable's completely as per daroc's suggestion.

FWIW my installation does not seem vulnerable

Posted Dec 18, 2024 21:20 UTC (Wed) by daroc (editor, #160859) [Link]

Oh, neat! I didn't realize that there was an "eval" local variable. That just goes to show that I always have more to learn about Emacs, I guess. Thanks for pointing that out!

disable flymake?

Posted Dec 19, 2024 7:53 UTC (Thu) by SiB (subscriber, #4048) [Link] (1 responses)

¿ How about
   find /usr/local/share/emacs -name \*flymake\* | xargs chmod a-r
That file gives me
   File local-variables error: (file-error Cannot open load file Permission denied flymake)
as expected.

disable flymake?

Posted Dec 19, 2024 13:31 UTC (Thu) by daroc (editor, #160859) [Link]

That's certainly an effective way of disabling Flymake, but it isn't the only affected mode; anything that tries to do macro expansion of ELisp files has a problem. If you don't have Flycheck installed, don't try to use another source of code completion or jump-to-definition in untrustworthy files, and have your local file variable settings configured such that setting eval can't bypass your hard work as gray_-_wolf pointed out, then I my guess is that would suffice.

Danger

Posted Dec 28, 2024 1:23 UTC (Sat) by floppus (guest, #137245) [Link] (1 responses)

Setting enable-local-eval to nil is not good enough!

A file can also enable minor modes using "mode:" syntax. Like so:

;; -*- mode: emacs-lisp; mode: flymake -*-
(rx (eval (call-process "touch" nil nil nil "/tmp/owned")))

Until this can be fixed, I would advise removing the flymake package entirely (or making the files unreadable as SiB suggests.)

Danger

Posted Dec 28, 2024 13:03 UTC (Sat) by gray_-_wolf (subscriber, #131074) [Link]

I had no idea you can use `mode' multiple times to enable also minor modes! That is very useful to know.

Since modifying the global installation of Emacs on my distribution is bit problematic, in the end I went with

(setopt flymake-start-on-flymake-mode nil
flymake-start-on-save-buffer nil
flymake-no-changes-timeout 99999999)

While flymake does get enabled, it does not seem to actually do anything.

Alliteration

Posted Dec 18, 2024 23:27 UTC (Wed) by willy (subscriber, #9762) [Link] (2 responses)

Are none of you jabronis going to compliment Daroc on the title of this article?

Alliteration

Posted Dec 19, 2024 2:31 UTC (Thu) by dskoll (subscriber, #1630) [Link]

Speccccctacular!

Alliteration

Posted Dec 19, 2024 10:04 UTC (Thu) by k3ninho (subscriber, #50375) [Link]

I would, it's solid alliteration, but I was stuck unable to exit Vim before I could make a comment here.

k3n.

LSPs

Posted Dec 19, 2024 7:23 UTC (Thu) by pabs (subscriber, #43278) [Link]

A lot of LSPs have similar issues, rust-analyzer for eg:

https://rust-analyzer.github.io/manual.html#security


Copyright © 2024, Eklektix, Inc.
This article may be redistributed under the terms of the Creative Commons CC BY-SA 4.0 license
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds