From 4418c80c9519f3af6bc4cc894c5292aa4ab3c852 Mon Sep 17 00:00:00 2001 From: Henrik Lissner Date: Tue, 3 Dec 2024 09:20:36 -0500 Subject: [PATCH] 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 --- modules/checkers/syntax/config.el | 24 ++++++- modules/lang/emacs-lisp/autoload.el | 99 +++++++++++++++-------------- 2 files changed, 73 insertions(+), 50 deletions(-) diff --git a/modules/checkers/syntax/config.el b/modules/checkers/syntax/config.el index cf6f0a2fc..4b74cf63c 100644 --- a/modules/checkers/syntax/config.el +++ b/modules/checkers/syntax/config.el @@ -23,6 +23,20 @@ ;; Display errors a little quicker (default is 0.9s) (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 ;; tooltips and childframes are disabled). (set-popup-rules! @@ -111,7 +125,15 @@ :when (modulep! +flymake) :hook ((prog-mode text-mode) . flymake-mode) :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 diff --git a/modules/lang/emacs-lisp/autoload.el b/modules/lang/emacs-lisp/autoload.el index 7ff00de6f..8a344c65d 100644 --- a/modules/lang/emacs-lisp/autoload.el +++ b/modules/lang/emacs-lisp/autoload.el @@ -306,55 +306,56 @@ are set by `+emacs-lisp-linter-warnings' This backend does not need to be added directly as `+emacs-lisp-non-package-mode' will enable it and disable the other checkers." - ;; if a process already exists. kill it. - (when (and +emacs-lisp-reduced-flymake-byte-compile--process - (process-live-p +emacs-lisp-reduced-flymake-byte-compile--process)) - (kill-process +emacs-lisp-reduced-flymake-byte-compile--process)) - (let ((source (current-buffer)) - (tmp-file (make-temp-file "+emacs-lisp-byte-compile-src")) - (out-buf (generate-new-buffer "+emacs-lisp-byte-compile-out"))) - ;; write the content to a temp file - (save-restriction - (widen) - (write-region nil nil tmp-file nil 'nomessage)) - ;; make the process - (setq +emacs-lisp-reduced-flymake-byte-compile--process - (make-process - :name "+emacs-reduced-flymake" - :noquery t - :connection-type 'pipe - :buffer out-buf - :command `(,(expand-file-name invocation-name invocation-directory) - "-Q" - "--batch" - ,@(mapcan (lambda (p) (list "-L" p)) elisp-flymake-byte-compile-load-path) - ;; this is what silences the byte compiler - "--eval" ,(prin1-to-string `(setq doom-modules ',doom-modules - doom-disabled-packages ',doom-disabled-packages - byte-compile-warnings ',+emacs-lisp-linter-warnings)) - "-f" "elisp-flymake--batch-compile-for-flymake" - ,tmp-file) - :stderr "*stderr of +elisp-flymake-byte-compile-out*" - :sentinel - ;; deal with the process when it exits - (lambda (proc _event) - (when (memq (process-status proc) '(exit signal)) - (unwind-protect - (cond - ;; if the buffer is dead or the process is not the same, log the process as old. - ((or (not (buffer-live-p source)) - (not (with-current-buffer source (eq proc +emacs-lisp-reduced-flymake-byte-compile--process)))) - (flymake-log :warning "byte compile process %s is old" proc)) - ;; if the process exited without problem process the buffer - ((zerop (process-exit-status proc)) - (elisp-flymake--byte-compile-done report-fn source out-buf)) - ;; otherwise something else horrid has gone wrong and we panic - (t (funcall report-fn :panic - :explanation - (format "byte compile process %s died" proc)))) - ;; cleanup - (ignore-errors (delete-file tmp-file)) - (kill-buffer out-buf)))))))) + (when (doom-project-p) + ;; if a process already exists. kill it. + (when (and +emacs-lisp-reduced-flymake-byte-compile--process + (process-live-p +emacs-lisp-reduced-flymake-byte-compile--process)) + (kill-process +emacs-lisp-reduced-flymake-byte-compile--process)) + (let ((source (current-buffer)) + (tmp-file (make-temp-file "+emacs-lisp-byte-compile-src")) + (out-buf (generate-new-buffer "+emacs-lisp-byte-compile-out"))) + ;; write the content to a temp file + (save-restriction + (widen) + (write-region nil nil tmp-file nil 'nomessage)) + ;; make the process + (setq +emacs-lisp-reduced-flymake-byte-compile--process + (make-process + :name "+emacs-reduced-flymake" + :noquery t + :connection-type 'pipe + :buffer out-buf + :command `(,(expand-file-name invocation-name invocation-directory) + "-Q" + "--batch" + ,@(mapcan (lambda (p) (list "-L" p)) elisp-flymake-byte-compile-load-path) + ;; this is what silences the byte compiler + "--eval" ,(prin1-to-string `(setq doom-modules ',doom-modules + doom-disabled-packages ',doom-disabled-packages + byte-compile-warnings ',+emacs-lisp-linter-warnings)) + "-f" "elisp-flymake--batch-compile-for-flymake" + ,tmp-file) + :stderr "*stderr of +elisp-flymake-byte-compile-out*" + :sentinel + ;; deal with the process when it exits + (lambda (proc _event) + (when (memq (process-status proc) '(exit signal)) + (unwind-protect + (cond + ;; if the buffer is dead or the process is not the same, log the process as old. + ((or (not (buffer-live-p source)) + (not (with-current-buffer source (eq proc +emacs-lisp-reduced-flymake-byte-compile--process)))) + (flymake-log :warning "byte compile process %s is old" proc)) + ;; if the process exited without problem process the buffer + ((zerop (process-exit-status proc)) + (elisp-flymake--byte-compile-done report-fn source out-buf)) + ;; otherwise something else horrid has gone wrong and we panic + (t (funcall report-fn :panic + :explanation + (format "byte compile process %s died" proc)))) + ;; cleanup + (ignore-errors (delete-file tmp-file)) + (kill-buffer out-buf))))))))) (define-minor-mode +emacs-lisp--flymake-non-package-mode ""