fix(syntax): disable checker in non-project elisp files

CVE-2024-53920 describes an arbitrary code execution vulnerability
during macro expansion, which occurs during byte-compilation or when
evaluating macro calls in uncompiled elisp files.

Flycheck and flymake use byte-compilation to lint elisp files, exposing
users to this vulnerability. This commit attempts to protect users from
this by disabling both in elisp files that aren't part of a
project (because, presumably, untrusted elisp won't live in a project).
What a "project" is depends on your projectile settings, but generally
means a file that lives in a version controlled directory and/or a
directory containing a recognizable project root marker (like a
packages.json or Cargo.toml file).

This heuristic won't catch cases of untrusted elisp living within
legitimate projects, or the case where the user's $HOME is a project and
*all* their elisp files live under it, but there are already too many
ways to shoot yourself in the foot with Emacs to begin with, and
disabling fly(check|make) altogether stands a higher chance of making
people blindly re-enable them to "work around" the fact it's not
"working as expected", bringing them back to square one.

Anyhow, long story short, don't open elisp files you don't trust in
Emacs, mkay?

Ref: CVE-2024-53920
This commit is contained in:
Henrik Lissner
2024-12-03 09:20:36 -05:00
parent ec645b8381
commit 4418c80c95
2 changed files with 73 additions and 50 deletions

View File

@ -23,6 +23,20 @@
;; Display errors a little quicker (default is 0.9s) ;; Display errors a little quicker (default is 0.9s)
(setq flycheck-display-errors-delay 0.25) (setq flycheck-display-errors-delay 0.25)
;; HACK: Protect against eager expansion of `setf'. The gv setter won't be
;; available until after `flycheck' loads, but macro expand occurs when this
;; file is loaded.
(eval '(setf (flycheck-checker-get 'emacs-lisp 'predicate)
(lambda ()
(and
;; Do not check buffers that ask not to be byte-compiled.
(not (bound-and-true-p no-byte-compile))
;; Disable the emacs-lisp checker in non-project (likely
;; untrusted) buffers to mitigate potential code execution
;; vulnerability during macro expansion. See CVE-2024-53920.
(doom-project-p))))
t)
;; Don't commandeer input focus if the error message pops up (happens when ;; Don't commandeer input focus if the error message pops up (happens when
;; tooltips and childframes are disabled). ;; tooltips and childframes are disabled).
(set-popup-rules! (set-popup-rules!
@ -111,7 +125,15 @@
:when (modulep! +flymake) :when (modulep! +flymake)
:hook ((prog-mode text-mode) . flymake-mode) :hook ((prog-mode text-mode) . flymake-mode)
:config :config
(setq flymake-fringe-indicator-position 'right-fringe)) (setq flymake-fringe-indicator-position 'right-fringe)
;; HACK: Disable the emacs-lisp checker in non-project (likely untrusted)
;; buffers to mitigate potential code execution vulnerability during macro
;; expansion. See CVE-2024-53920.
(defadvice! +syntax--only-check-elisp-buffers-in-projects-a (fn &rest args)
"Prevent the elisp checker in non-project buffers (for CVE-2024-53920)."
:before-while #'elisp-flymake-byte-compile
(doom-project-p)))
(use-package! flymake-popon (use-package! flymake-popon

View File

@ -306,55 +306,56 @@ are set by `+emacs-lisp-linter-warnings'
This backend does not need to be added directly This backend does not need to be added directly
as `+emacs-lisp-non-package-mode' will enable it and disable the other checkers." as `+emacs-lisp-non-package-mode' will enable it and disable the other checkers."
;; if a process already exists. kill it. (when (doom-project-p)
(when (and +emacs-lisp-reduced-flymake-byte-compile--process ;; if a process already exists. kill it.
(process-live-p +emacs-lisp-reduced-flymake-byte-compile--process)) (when (and +emacs-lisp-reduced-flymake-byte-compile--process
(kill-process +emacs-lisp-reduced-flymake-byte-compile--process)) (process-live-p +emacs-lisp-reduced-flymake-byte-compile--process))
(let ((source (current-buffer)) (kill-process +emacs-lisp-reduced-flymake-byte-compile--process))
(tmp-file (make-temp-file "+emacs-lisp-byte-compile-src")) (let ((source (current-buffer))
(out-buf (generate-new-buffer "+emacs-lisp-byte-compile-out"))) (tmp-file (make-temp-file "+emacs-lisp-byte-compile-src"))
;; write the content to a temp file (out-buf (generate-new-buffer "+emacs-lisp-byte-compile-out")))
(save-restriction ;; write the content to a temp file
(widen) (save-restriction
(write-region nil nil tmp-file nil 'nomessage)) (widen)
;; make the process (write-region nil nil tmp-file nil 'nomessage))
(setq +emacs-lisp-reduced-flymake-byte-compile--process ;; make the process
(make-process (setq +emacs-lisp-reduced-flymake-byte-compile--process
:name "+emacs-reduced-flymake" (make-process
:noquery t :name "+emacs-reduced-flymake"
:connection-type 'pipe :noquery t
:buffer out-buf :connection-type 'pipe
:command `(,(expand-file-name invocation-name invocation-directory) :buffer out-buf
"-Q" :command `(,(expand-file-name invocation-name invocation-directory)
"--batch" "-Q"
,@(mapcan (lambda (p) (list "-L" p)) elisp-flymake-byte-compile-load-path) "--batch"
;; this is what silences the byte compiler ,@(mapcan (lambda (p) (list "-L" p)) elisp-flymake-byte-compile-load-path)
"--eval" ,(prin1-to-string `(setq doom-modules ',doom-modules ;; this is what silences the byte compiler
doom-disabled-packages ',doom-disabled-packages "--eval" ,(prin1-to-string `(setq doom-modules ',doom-modules
byte-compile-warnings ',+emacs-lisp-linter-warnings)) doom-disabled-packages ',doom-disabled-packages
"-f" "elisp-flymake--batch-compile-for-flymake" byte-compile-warnings ',+emacs-lisp-linter-warnings))
,tmp-file) "-f" "elisp-flymake--batch-compile-for-flymake"
:stderr "*stderr of +elisp-flymake-byte-compile-out*" ,tmp-file)
:sentinel :stderr "*stderr of +elisp-flymake-byte-compile-out*"
;; deal with the process when it exits :sentinel
(lambda (proc _event) ;; deal with the process when it exits
(when (memq (process-status proc) '(exit signal)) (lambda (proc _event)
(unwind-protect (when (memq (process-status proc) '(exit signal))
(cond (unwind-protect
;; if the buffer is dead or the process is not the same, log the process as old. (cond
((or (not (buffer-live-p source)) ;; if the buffer is dead or the process is not the same, log the process as old.
(not (with-current-buffer source (eq proc +emacs-lisp-reduced-flymake-byte-compile--process)))) ((or (not (buffer-live-p source))
(flymake-log :warning "byte compile process %s is old" proc)) (not (with-current-buffer source (eq proc +emacs-lisp-reduced-flymake-byte-compile--process))))
;; if the process exited without problem process the buffer (flymake-log :warning "byte compile process %s is old" proc))
((zerop (process-exit-status proc)) ;; if the process exited without problem process the buffer
(elisp-flymake--byte-compile-done report-fn source out-buf)) ((zerop (process-exit-status proc))
;; otherwise something else horrid has gone wrong and we panic (elisp-flymake--byte-compile-done report-fn source out-buf))
(t (funcall report-fn :panic ;; otherwise something else horrid has gone wrong and we panic
:explanation (t (funcall report-fn :panic
(format "byte compile process %s died" proc)))) :explanation
;; cleanup (format "byte compile process %s died" proc))))
(ignore-errors (delete-file tmp-file)) ;; cleanup
(kill-buffer out-buf)))))))) (ignore-errors (delete-file tmp-file))
(kill-buffer out-buf)))))))))
(define-minor-mode +emacs-lisp--flymake-non-package-mode (define-minor-mode +emacs-lisp--flymake-non-package-mode
"" ""