--- emacs-27.2/doc/emacs/misc.texi | 33 +++++++++++++++++ emacs-27.2/lisp/emacs-lisp/macroexp.el | 10 ++++- emacs-27.2/lisp/files.el | 60 +++++++++++++++++++++++++++++--- emacs-27.2/lisp/ielm.el | 3 + emacs-27.2/lisp/progmodes/elisp-mode.el | 58 +++++++++++++++++++++++++----- emacs-27.2/lisp/simple.el | 1 6 files changed, 189 insertions(+), 20 deletions(-) --- emacs-27.2/doc/emacs/misc.texi +++ emacs-27.2/doc/emacs/misc.texi 2025-03-03 09:18:41.368169799 +0000 @@ -279,6 +279,39 @@ trusted and the default checking for the you can set @code{enable-local-variables} to @code{:all}. @xref{Safe File Variables}. +@cindex trusted files and directories +Loading a file of Emacs Lisp code with @code{load-file} or +@code{load-library} (@pxref{Lisp Libraries}) can execute some of the +Lisp code in the file being loaded, so you should only load Lisp files +whose source you trust. However, some Emacs features can in certain +situations execute Lisp code even without your explicit command or +request. For example, Flymake, the on-the-fly syntax checker for Emacs +(@pxref{Top,,, flymake, GNU Flymake}), if it is enabled, can +automatically execute some of the code in a Lisp file you visit as part +of its syntax-checking job. Similarly, some completion commands +(@pxref{Completion}) in buffers visiting Lisp files sometimes need to +expand Lisp macros for best results. In these cases, just visiting a +Lisp file and performing some editing in it could trigger execution of +Lisp code. If the visited file came from an untrusted source, it could +include dangerous or even malicious code that Emacs would execute in +those situations. + +To protect against this, Emacs disables execution of Lisp code by +Flymake, completion, and some other features, unless the visited file is +@dfn{trusted}. It is up to you to specify which files on your system +should be trusted, by customizing the user option +@code{trusted-content}. + +@defopt trusted-content +The value of this option is @code{nil} by default, which means no file +is trusted. You can customize the variable to be a list of one or more +names of trusted files and directories. A file name that ends in a +slash @file{/} is interpreted as a directory, which means all its files +and subdirectories are also trusted. A special value @code{:all} means +@emph{all} the files and directories on your system should be trusted; +@strong{this is not recommended}, as it opens a gaping security hole. +@end defopt + @xref{Security Considerations,,, elisp, The Emacs Lisp Reference Manual}, for more information about security considerations when using Emacs as part of a larger application. --- emacs-27.2/lisp/emacs-lisp/macroexp.el +++ emacs-27.2/lisp/emacs-lisp/macroexp.el 2025-03-03 09:18:41.368169799 +0000 @@ -94,12 +94,20 @@ each clause." (macroexp--all-forms clause skip) clause))) +(defvar macroexp-inhibit-compiler-macros nil + "Inhibit application of compiler macros if non-nil.") + (defun macroexp--compiler-macro (handler form) + "Apply compiler macro HANDLER to FORM and return the result. +Unless `macroexp-inhibit-compiler-macros' is non-nil, in which +case return FORM unchanged." + (if macroexp-inhibit-compiler-macros + form (condition-case err (apply handler form (cdr form)) (error (message "Compiler-macro error for %S: %S" (car form) err) - form))) + form)))) (defun macroexp--funcall-if-compiled (_form) "Pseudo function used internally by macroexp to delay warnings. --- emacs-27.2/lisp/files.el +++ emacs-27.2/lisp/files.el 2025-03-03 09:20:04.078645249 +0000 @@ -591,10 +596,57 @@ buffer contents as untrusted. (other :tag "Query" other)) :group 'find-file) -(defvar enable-dir-local-variables t - "Non-nil means enable use of directory-local variables. -Some modes may wish to set this to nil to prevent directory-local -settings being applied, but still respect file-local ones.") +(defcustom trusted-content nil + "List of files and directories whose content we trust. +Be extra careful here since trusting means that Emacs might execute the +code contained within those files and directories without an explicit +request by the user. +One important case when this might happen is when `flymake-mode' is +enabled (for example, when it is added to a mode hook). +Each element of the list should be a string: +- If it ends in \"/\", it is considered as a directory name and means that + Emacs should trust all the files whose name has this directory as a prefix. +- Otherwise, it is considered a file name. +Use abbreviated file names. For example, an entry \"~/mycode/\" means +that Emacs will trust all the files in your directory \"mycode\". +This variable can also be set to `:all', in which case Emacs will trust +all files, which opens a gaping security hole. Emacs Lisp authors +should note that this value must never be set by a major or minor mode." + :type '(choice (repeat :tag "List" file) + (const :tag "Trust everything (DANGEROUS!)" :all)) + :version "27.2") +(put 'trusted-content 'risky-local-variable t) + +(defun trusted-content-p () + "Return non-nil if we trust the contents of the current buffer. +Here, \"trust\" means that we are willing to run code found inside of it. +See also `trusted-content'." + ;; We compare with `buffer-file-truename' i.s.o `buffer-file-name' + ;; to try and avoid marking as trusted a file that's merely accessed + ;; via a symlink that happens to be inside a trusted dir. + (and (not untrusted-content) + (or + (eq trusted-content :all) + (and + buffer-file-truename + (with-demoted-errors "trusted-content-p: %S" + (let ((exists (file-exists-p buffer-file-truename))) + (or + ;; We can't avoid trusting the user's init file. + (if (and exists user-init-file) + (file-equal-p buffer-file-truename user-init-file) + (equal buffer-file-truename user-init-file)) + (let ((file (abbreviate-file-name buffer-file-truename)) + (trusted nil)) + (dolist (tf trusted-content) + (when (or (if exists (file-equal-p tf file) (equal tf file)) + ;; We don't use `file-in-directory-p' here, because + ;; we want to err on the conservative side: "guilty + ;; until proven innocent". + (and (string-suffix-p "/" tf) + (string-prefix-p tf file))) + (setq trusted t))) + trusted)))))))) ;; This is an odd variable IMO. ;; You might wonder why it is needed, when we could just do: --- emacs-27.2/lisp/ielm.el +++ emacs-27.2/lisp/ielm.el 2025-03-03 09:18:41.372169725 +0000 @@ -616,7 +616,8 @@ See `inferior-emacs-lisp-mode' for detai (unless (comint-check-proc "*ielm*") (with-current-buffer (get-buffer-create "*ielm*") (unless (zerop (buffer-size)) (setq old-point (point))) - (inferior-emacs-lisp-mode))) + (inferior-emacs-lisp-mode) + (setq-local trusted-content :all))) (pop-to-buffer-same-window "*ielm*") (when old-point (push-mark old-point)))) --- emacs-27.2/lisp/progmodes/elisp-mode.el +++ emacs-27.2/lisp/progmodes/elisp-mode.el 2025-03-03 09:18:41.372169725 +0000 @@ -333,6 +333,43 @@ Blank lines separate paragraphs. Semico (defvar warning-minimum-log-level) +(defvar elisp--local-macroenv + `((cl-eval-when . ,(lambda (&rest args) `(progn . ,(cdr args)))) + (eval-when-compile . ,(lambda (&rest args) `(progn . ,args))) + (eval-and-compile . ,(lambda (&rest args) `(progn . ,args)))) + "Environment to use while tentatively expanding macros. +This is used to try and avoid the most egregious problems linked to the +use of `macroexpand-all' as a way to find the \"underlying raw code\".") + +(defvar elisp--macroexpand-untrusted-warning t) + +(defun elisp--safe-macroexpand-all (sexp) + (if (not (trusted-content-p)) + ;; FIXME: We should try and do better here, either using a notion + ;; of "safe" macros, or with `bwrap', or ... + (progn + (when elisp--macroexpand-untrusted-warning + (setq-local elisp--macroexpand-untrusted-warning nil) ;Don't spam! + (let ((inhibit-message t)) ;Only log. + (message "Completion of local vars is disabled in %s (untrusted content)" + (buffer-name)))) + sexp) + (let ((macroexpand-advice + (lambda (expander form &rest args) + (condition-case err + (apply expander form args) + (error + (message "Ignoring macroexpansion error: %S" err) form))))) + (unwind-protect + ;; Silence any macro expansion errors when + ;; attempting completion at point (bug#58148). + (let ((inhibit-message t) + (macroexp-inhibit-compiler-macros t) + (warning-minimum-log-level :emergency)) + (advice-add 'macroexpand-1 :around macroexpand-advice) + (macroexpand-all sexp elisp--local-macroenv)) + (advice-remove 'macroexpand-1 macroexpand-advice))))) + (defun elisp--local-variables () "Return a list of locally let-bound variables at point." (save-excursion @@ -348,17 +385,8 @@ Blank lines separate paragraphs. Semico (car (read-from-string (concat txt "elisp--witness--lisp" closer))) ((invalid-read-syntax end-of-file) nil))) - (macroexpand-advice (lambda (expander form &rest args) - (condition-case nil - (apply expander form args) - (error form)))) - (sexp - (unwind-protect - (let ((warning-minimum-log-level :emergency)) - (advice-add 'macroexpand :around macroexpand-advice) - (macroexpand-all sexp)) - (advice-remove 'macroexpand macroexpand-advice))) - (vars (elisp--local-variables-1 nil sexp))) + (vars (elisp--local-variables-1 + nil (elisp--safe-macroexpand-all sexp)))) (delq nil (mapcar (lambda (var) (and (symbolp var) @@ -1721,6 +1749,14 @@ directory of the buffer being compiled, "A Flymake backend for elisp byte compilation. Spawn an Emacs process that byte-compiles a file representing the current buffer state and calls REPORT-FN when done." + (unless (trusted-content-p) + ;; FIXME: Use `bwrap' and friends to compile untrusted content. + ;; FIXME: We emit a message *and* signal an error, because by default + ;; Flymake doesn't display the warning it puts into "*flmake log*". + (message "Disabling elisp-flymake-byte-compile in %s (untrusted content)" + (buffer-name)) + (error "Disabling elisp-flymake-byte-compile in %s (untrusted content)" + (buffer-name))) (when elisp-flymake--byte-compile-process (when (process-live-p elisp-flymake--byte-compile-process) (kill-process elisp-flymake--byte-compile-process))) --- emacs-27.2/lisp/simple.el +++ emacs-27.2/lisp/simple.el 2025-03-03 09:18:41.372169725 +0000 @@ -1621,6 +1621,7 @@ display the result of expression evaluat (eldoc-mode 1) (add-hook 'completion-at-point-functions #'elisp-completion-at-point nil t) + (setq-local trusted-content :all) (run-hooks 'eval-expression-minibuffer-setup-hook)) (read-from-minibuffer prompt initial-contents read-expression-map t