From: Eshel Yaron Date: Sun, 27 Apr 2025 09:48:02 +0000 (+0200) Subject: trust.el: New file with improved trust API. X-Git-Url: http://git.eshelyaron.com/gitweb/?a=commitdiff_plain;h=0b3c6d886e1a7a0355802486ec243b9422886b6c;p=emacs.git trust.el: New file with improved trust API. --- diff --git a/lisp/emacs-lisp/find-func.el b/lisp/emacs-lisp/find-func.el index 6d7dede9d8b..d9315002dba 100644 --- a/lisp/emacs-lisp/find-func.el +++ b/lisp/emacs-lisp/find-func.el @@ -551,7 +551,7 @@ The search is done in the source for library LIBRARY." ;; macro-expansion involves arbitrary code execution, ;; only attempt it in trusted buffers. (cons (current-buffer) - (when (trusted-content-p) + (when (trust-trusted-content-p) (find-function--search-by-expanding-macros (current-buffer) symbol type))))))))))) diff --git a/lisp/emacs-lisp/scope.el b/lisp/emacs-lisp/scope.el index 0b3d64632ad..f5db0ce1fa9 100644 --- a/lisp/emacs-lisp/scope.el +++ b/lisp/emacs-lisp/scope.el @@ -1428,7 +1428,7 @@ a (possibly empty) list of safe macros.") (or (eq scope-safe-macros t) (memq macro scope-safe-macros) (get macro 'safe-macro) - (trusted-content-p)))) + (trust-trusted-content-p)))) (defvar warning-minimum-log-level) @@ -1595,7 +1595,7 @@ a (possibly empty) list of safe macros.") special-variable-p local-variable-p local-variable-if-set-p default-value set-default make-local-variable - buffer-local-value add-to-list add-to-history + buffer-local-value add-to-list add-to-history find-buffer add-hook remove-hook run-hook-with-args run-hook-wrapped)) (put sym 'scope-analyzer #'scope--analyze-boundp)) diff --git a/lisp/files.el b/lisp/files.el index 5006064d477..4be36335482 100644 --- a/lisp/files.el +++ b/lisp/files.el @@ -702,59 +702,6 @@ Also see the `permanently-enabled-local-variables' and Some modes may wish to set this to nil to prevent directory-local settings being applied, but still respect file-local ones.") -(defvar-local untrusted-content nil - "Non-nil means that current buffer originated from an untrusted source. -Email clients and some other modes may set this non-nil to mark the -buffer contents as untrusted. - -This variable might be subject to change without notice.") -(put 'untrusted-content 'permanent-local t) - -(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 "30.1") -(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'." - (and (not untrusted-content) - (or - (eq trusted-content :all) - (and - (or vc-followed-link buffer-file-name) - (with-demoted-errors "trusted-content-p: %S" - (let* ((file (expand-file-name (or vc-followed-link buffer-file-name))) - (exists (file-exists-p file))) - (catch 'ball - (dolist (tf trusted-content) - (let ((ef (expand-file-name tf))) - (and - (or (if exists (file-equal-p ef file) (equal ef 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 "/" ef) - (string-prefix-p ef file))) - (throw 'ball t))))))))))) - (defcustom enable-local-eval nil "Control processing of the \"variable\" `eval' in a file's local variables. The value can be t, nil or something else. diff --git a/lisp/gnus/mm-view.el b/lisp/gnus/mm-view.el index a3162d13e91..35230ec2f84 100644 --- a/lisp/gnus/mm-view.el +++ b/lisp/gnus/mm-view.el @@ -502,7 +502,7 @@ If MODE is not set, try to find mode automatically." (setq coding-system (mm-find-buffer-file-coding-system))) (setq text (buffer-string)))) (with-temp-buffer - (setq untrusted-content t) + (trust-set-buffer-trust) (insert (cond ((eq charset 'gnus-decoded) (with-current-buffer (mm-handle-buffer handle) (buffer-string))) diff --git a/lisp/ielm.el b/lisp/ielm.el index 81d22ee4405..a7ba8fc9512 100644 --- a/lisp/ielm.el +++ b/lisp/ielm.el @@ -692,7 +692,7 @@ See `inferior-emacs-lisp-mode' for details." (with-current-buffer (get-buffer-create buf-name) (unless (zerop (buffer-size)) (setq old-point (point))) (inferior-emacs-lisp-mode) - (setq-local trusted-content :all))) + (trust-set-buffer-trust nil t))) (pop-to-buffer-same-window buf-name) (when old-point (push-mark old-point)))) diff --git a/lisp/org/org.el b/lisp/org/org.el index d783e992df0..9902fc2d01b 100644 --- a/lisp/org/org.el +++ b/lisp/org/org.el @@ -1181,9 +1181,6 @@ the following lines anywhere in the buffer: :package-version '(Org . "8.0") :type 'boolean) -(unless (boundp 'untrusted-content) - (defvar untrusted-content nil)) -(defvar untrusted-content) ; defined in files.el since Emacs 29.3 (defvar org--latex-preview-when-risky nil "If non-nil, enable LaTeX preview in Org buffers from unsafe source. @@ -1195,7 +1192,7 @@ fragments that originate from incoming email messages. It has no effect when Org mode is unable to determine the origin of the Org buffer. An Org buffer is considered to be from unsafe source when the -variable `untrusted-content' has a non-nil value in the buffer. +`trust-trusted-content-p' returns nil in the buffer. If this variable is non-nil, LaTeX previews are rendered unconditionally. @@ -16167,7 +16164,7 @@ fragments in the buffer." (interactive "P") (cond ((not (display-graphic-p)) nil) - ((and untrusted-content (not org--latex-preview-when-risky)) nil) + ((not (or (trust-trusted-content-p) org--latex-preview-when-risky)) nil) ;; Clear whole buffer. ((equal arg '(64)) (org-clear-latex-preview (point-min) (point-max)) diff --git a/lisp/progmodes/elisp-mode.el b/lisp/progmodes/elisp-mode.el index a9794579fe8..dc56c35af8d 100644 --- a/lisp/progmodes/elisp-mode.el +++ b/lisp/progmodes/elisp-mode.el @@ -565,6 +565,44 @@ This is the `font-lock-fontify-region-function' for `emacs-lisp-mode'." (2 font-lock-function-name-face prepend t))) "Highlighting patterns for Emacs Lisp mode.") +(defvar elisp-mode-line-trusted-buffer + `(:eval + (if (trust-trusted-content-p) + ,(propertize "!" + 'face 'success + 'help-echo "mouse-2: Stop trusting this buffer\nBuffer is trusted" + 'mouse-face 'mode-line-highlight + 'follow-link t + 'keymap + (let ((map (make-sparse-keymap))) + (define-key map [mode-line follow-link] 'mouse-face) + (define-key map [mode-line mouse-2] + (lambda (event) + (interactive "e") + (with-current-buffer (window-buffer (posn-window (event-start event))) + (trust-set-buffer-trust) + (message "Buffer %S is now UNTRUSTED" (current-buffer)) + (force-mode-line-update)))) + map)) + ,(propertize "?" + 'face 'error + 'help-echo "mouse-2: Trust this buffer\nBuffer is untrusted" + 'mouse-face 'mode-line-highlight + 'follow-link t + 'keymap + (let ((map (make-sparse-keymap))) + (define-key map [mode-line follow-link] 'mouse-face) + (define-key map [mode-line mouse-2] + (lambda (event) + (interactive "e") + (with-current-buffer (window-buffer (posn-window (event-start event))) + (trust-set-buffer-trust nil t) + (message "Buffer %S is now TRUSTED" (current-buffer)) + (force-mode-line-update)))) + map))))) + +(put 'elisp-mode-line-trusted-buffer 'risky-local-variable t) + ;;;###autoload (define-derived-mode emacs-lisp-mode lisp-data-mode `("ELisp" @@ -576,39 +614,7 @@ mouse-1: Enable lexical-binding mode" face warning mouse-face mode-line-highlight local-map ,elisp--dynlex-modeline-map)) - (:eval (if (trusted-content-p) - ,(propertize "!" - 'face 'success - 'help-echo "mouse-2: Stop trusting this buffer\nBuffer is trusted" - 'mouse-face 'mode-line-highlight - 'follow-link t - 'keymap - (let ((map (make-sparse-keymap))) - (define-key map [mode-line follow-link] 'mouse-face) - (define-key map [mode-line mouse-2] - (lambda (event) - (interactive "e") - (with-current-buffer (window-buffer (posn-window (event-start event))) - (setq-local trusted-content nil) - (message "Buffer %S is now UNTRUSTED" (current-buffer)) - (force-mode-line-update)))) - map)) - ,(propertize "?" - 'face 'error - 'help-echo "mouse-2: Trust this buffer\nBuffer is untrusted" - 'mouse-face 'mode-line-highlight - 'follow-link t - 'keymap - (let ((map (make-sparse-keymap))) - (define-key map [mode-line follow-link] 'mouse-face) - (define-key map [mode-line mouse-2] - (lambda (event) - (interactive "e") - (with-current-buffer (window-buffer (posn-window (event-start event))) - (setq-local trusted-content :all) - (message "Buffer %S is now TRUSTED" (current-buffer)) - (force-mode-line-update)))) - map))))) + elisp-mode-line-trusted-buffer) "Major mode for editing Lisp code to run in Emacs. \\ - \\[backward-delete-char-untabify] converts tabs to spaces as it moves back. @@ -654,6 +660,12 @@ be used instead. (add-hook 'flymake-diagnostic-functions #'elisp-flymake-checkdoc nil t) (add-hook 'flymake-diagnostic-functions #'elisp-flymake-byte-compile nil t) + (add-hook + 'trust-buffer-hook + (lambda (arg) + (when (and arg (bound-and-true-p flymake-mode)) + (flymake-enable-backend 'elisp-flymake-byte-compile))) + nil t) (add-hook 'refactor-backend-functions #'elisp-refactor-backend nil t) (add-hook 'context-menu-functions #'elisp-context-menu 10 t) (setq-local imenu-create-index-function #'elisp-create-imenu-index) @@ -1453,7 +1465,8 @@ All commands in `lisp-mode-shared-map' are inherited by this map." ["Evaluate Defun" eval-defun :help "Evaluate the top-level form containing point, or after point"])) -(define-derived-mode lisp-interaction-mode emacs-lisp-mode "Lisp Interaction" +(define-derived-mode lisp-interaction-mode emacs-lisp-mode + '("Lisp Interaction" elisp-mode-line-trusted-buffer) "Major mode for typing and evaluating Lisp forms. Like Lisp mode except that \\[eval-print-last-sexp] evals the Lisp expression before point, and prints its value into the buffer, advancing point. @@ -2371,12 +2384,8 @@ directory of the buffer being compiled, and nothing else.") "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) + (unless (trust-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)) (user-error "Disabling elisp-flymake-byte-compile in %s (untrusted content)" (buffer-name))) (when elisp-flymake--byte-compile-process diff --git a/lisp/progmodes/flymake.el b/lisp/progmodes/flymake.el index 995a4dd4c0d..5008f800f4d 100644 --- a/lisp/progmodes/flymake.el +++ b/lisp/progmodes/flymake.el @@ -1392,6 +1392,12 @@ If it is running also stop it." (flymake--state-disabled state) explanation (flymake--state-reported-p state) t))) +;;;###autoload +(defun flymake-enable-backend (backend) + "Enable Flymake BACKEND." + (flymake--with-backend-state backend state + (setf (flymake--state-disabled state) nil))) + (defun flymake--run-backend (backend &optional args) "Run the backend BACKEND, re-enabling if necessary. ARGS is a keyword-value plist passed to the backend along diff --git a/lisp/simple.el b/lisp/simple.el index 34b34426b74..e80301a5fdb 100644 --- a/lisp/simple.el +++ b/lisp/simple.el @@ -2043,7 +2043,7 @@ function `read-from-minibuffer'." #'elisp-completion-at-point nil t) (add-hook 'minibuffer-exit-hook #'minibuffer-kill-completions-buffer nil t) - (setq-local trusted-content :all) + (trust-set-buffer-trust nil t) (run-hooks 'eval-expression-minibuffer-setup-hook)) (read-from-minibuffer prompt initial-contents read--expression-map t @@ -10858,7 +10858,8 @@ too short to have a dst element. (when initial-scratch-message (insert (substitute-command-keys initial-scratch-message)) (set-buffer-modified-p nil)) - (funcall initial-major-mode)) + (funcall initial-major-mode) + (trust-set-buffer-trust nil t)) scratch))) (defun scratch-buffer () diff --git a/lisp/trust.el b/lisp/trust.el new file mode 100644 index 00000000000..3961648b18b --- /dev/null +++ b/lisp/trust.el @@ -0,0 +1,119 @@ +;;; trust.el --- Trust API -*- lexical-binding:t -*- + +;; Copyright (C) 2025 Eshel Yaron + +;; Author: Eshel Yaron + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;;; Code: + +(defgroup trust () + "Trust related settings." + :group 'files) + +(defcustom trust--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 "30.1") +(put 'trust--trusted-content 'risky-local-variable t) + +;;;###autoload +(defun trust-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 `trust--trusted-content'." + (or + (eq trust--trusted-content :all) + (and + (or vc-followed-link buffer-file-name) + (with-demoted-errors "trusted-content-p: %S" + (let* ((file (expand-file-name (or vc-followed-link buffer-file-name))) + (exists (file-exists-p file))) + (catch 'ball + (dolist (tf trust--trusted-content) + (let ((ef (expand-file-name tf))) + (and + (or (if exists (file-equal-p ef file) (equal ef 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 "/" ef) + (string-prefix-p ef file))) + (throw 'ball t)))))))))) + +(defvar trust-buffer-hook nil + "Abnormal hook run when buffer is affected by change in trust settings. +Functions on this hook are called with one argument, which is non-nil if +the current buffer is now trusted, and nil if the it is now untrusted.") + +;;;###autoload +(defun trust-set-buffer-trust (&optional buf trust) + (interactive + (let ((trust (unless current-prefix-arg :all))) + (list (read-buffer + (format-prompt (if trust "Trust buffer" "Untrust buffer")) + (buffer-name (current-buffer)) t + (lambda (b) + (not (eq trust + (when (buffer-local-value + 'trust--trusted-content (get-buffer b)) + :all))))) + trust))) + (with-current-buffer (or buf (current-buffer)) + (setq-local trust--trusted-content (when trust :all)) + (run-hook-with-args 'trust-buffer-hook trust))) + +;;;###autoload +(defun trust-set-file-trust (file &optional trust) + (interactive + (let ((trust (not current-prefix-arg)) + (def (when default-directory (abbreviate-file-name default-directory)))) + (list (read-file-name + (format-prompt (if trust "Trust file" "Untrust file") def) + nil def) + trust))) + (let* ((file (expand-file-name (if (file-directory-p file) + (file-name-as-directory file) + file))) + (exists (file-exists-p file)) + (cur (delete file (default-value 'trust--trusted-content)))) + (setq-default trust--trusted-content (if trust (cons file cur) cur)) + (dolist (buf (buffer-list)) + (with-current-buffer buf + (when-let ((fbuf (or vc-followed-link buffer-file-name)) + (fbuf (expand-file-name fbuf))) + (when (or (and (string-suffix-p "/" file) (string-prefix-p file fbuf)) + (if exists (file-equal-p fbuf file) (equal fbuf file))) + (run-hook-with-args 'trust-buffer-hook trust))))))) + +(provide 'trust) +;;; trust.el ends here diff --git a/test/lisp/emacs-lisp/find-func-tests.el b/test/lisp/emacs-lisp/find-func-tests.el index 325430196b4..3ba1b1e334b 100644 --- a/test/lisp/emacs-lisp/find-func-tests.el +++ b/test/lisp/emacs-lisp/find-func-tests.el @@ -103,9 +103,8 @@ expected function symbol and function library, respectively." (declare-function compilation--message->loc nil "compile") (ert-deftest find-func-tests--locate-macro-generated-symbols () ;bug#45443 - (let ((trusted-content - (list (abbreviate-file-name (find-library-name "compile")) - (abbreviate-file-name (find-library-name "cc-mode"))))) + (let ((trust--trusted-content + (list (find-library-name "compile") (find-library-name "cc-mode")))) (should (cdr (find-function-search-for-symbol #'compilation--message->loc nil "compile"))) (should (cdr (find-function-search-for-symbol