From 5be942f10fab7fed8321a9e20fe831730d79738b Mon Sep 17 00:00:00 2001 From: Daniel Mendler Date: Sun, 15 Dec 2024 00:48:26 +0100 Subject: compat-30: untrusted-content, trusted-files, trusted-content-p --- NEWS.org | 6 ++++++ compat-30.el | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ compat-macs.el | 7 ++++++- compat-tests.el | 26 ++++++++++++++++++++++++++ compat.texi | 29 +++++++++++++++++++++++++++++ 5 files changed, 122 insertions(+), 1 deletion(-) diff --git a/NEWS.org b/NEWS.org index acc06d0..2f38878 100644 --- a/NEWS.org +++ b/NEWS.org @@ -2,6 +2,12 @@ #+link: compat-gh https://github.com/emacs-compat/compat/issues/ #+options: toc:nil num:nil author:nil +* Development + +- compat-30: New variable =untrusted-content=. +- compat-30: New variable =trusted-files=. +- compat-30: New function =trusted-content-p=. + * Release of "Compat" Version 30.0.0.0 - compat-28: Mark =subr-native-elisp-p= as obsolete (renamed in Emacs 30). diff --git a/compat-30.el b/compat-30.el index e5a3a9e..02ef426 100644 --- a/compat-30.el +++ b/compat-30.el @@ -24,6 +24,15 @@ (eval-when-compile (load "compat-macs.el" nil t t)) (compat-require compat-29 "29.1") +(compat-version "29.3") +(compat-defvar 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." + :local permanent) + ;; TODO Update to 30.1 as soon as the Emacs emacs-30 branch version bumped (compat-version "30.0.50") @@ -50,6 +59,52 @@ See also `find-buffer-visiting'." ;;;; Defined in files.el +(compat-defvar trusted-files 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. +- else it is considered as 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." + :risky t) + +(compat-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-files'." + ;; 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) + buffer-file-truename + (with-demoted-errors "trusted-content-p: %S" + (let ((exists (file-exists-p buffer-file-truename))) + (or + (eq trusted-files :all) + ;; 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-files) + (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)))))) + (compat-defun require-with-check (feature &optional filename noerror) ;; "If FEATURE is not already loaded, load it from FILENAME. This is like `require' except if FEATURE is already a member of the list diff --git a/compat-macs.el b/compat-macs.el index ffd5223..d200fbb 100644 --- a/compat-macs.el +++ b/compat-macs.el @@ -221,6 +221,8 @@ definition is generated. - :constant :: Mark the variable as constant if t. +- :risky :: Mark the variable as risky if t. + - :local :: Make the variable buffer-local if t. If the value is `permanent' make the variable additionally permanently local. @@ -232,11 +234,13 @@ definition is generated. (doc-string 3) (indent 2)) (compat-macs--guard attrs (list :constant #'booleanp + :risky #'booleanp :local (lambda (x) (memq x '(nil t permanent))) :obsolete (lambda (x) (or (booleanp x) (stringp x)))) - (lambda (constant local obsolete) + (lambda (constant risky local obsolete) (compat-macs--strict (not (boundp name)) "%s already defined" name) (compat-macs--assert (not (and constant local)) "Both :constant and :local") + (compat-macs--assert (not (and local risky)) "Both :risky and :local") ;; The boundp check is performed at runtime to make sure that we never ;; redefine an existing definition if Compat is loaded on a newer Emacs ;; version. @@ -250,6 +254,7 @@ definition is generated. ',name ,(if (stringp obsolete) obsolete "No substitute") ,compat-macs--version)))) ,@(and local `((make-variable-buffer-local ',name))) + ,@(and risky `((put ',name 'risky-local-variable t))) ,@(and (eq local 'permanent) `((put ',name 'permanent-local t))))))) (defmacro compat-version (version) diff --git a/compat-tests.el b/compat-tests.el index b736015..b8ae0c1 100644 --- a/compat-tests.el +++ b/compat-tests.el @@ -3232,5 +3232,31 @@ (let ((completion-category-overrides '((compat-test (a . 10))))) (should-equal 10 (compat-call completion-metadata-get md 'a)))))) +(ert-deftest compat-untrusted-content () + (should (local-variable-if-set-p 'untrusted-content))) + +(ert-deftest compat-trusted-files () + (static-if (< emacs-major-version 30) ;; TODO reenable on Emacs 30 + (progn + (should (boundp 'trusted-files)) + (should (risky-local-variable-p 'trusted-files))))) + +(ert-deftest compat-trusted-content-p () + (static-if (< emacs-major-version 30) ;; TODO reenable on Emacs 30 + (progn + (should-not (trusted-content-p)) + (let ((untrusted-content t) + (buffer-file-truename user-init-file)) + (should-not (trusted-content-p))) + (let ((buffer-file-truename (expand-file-name "compat-tests.el"))) + (should-not (trusted-content-p))) + (let ((buffer-file-truename (expand-file-name "compat-tests.el")) + (trusted-files '("compat-tests.el"))) + (should (trusted-content-p))) + (let ((untrusted-content t) + (buffer-file-truename (expand-file-name "compat-tests.el")) + (trusted-files '("compat-tests.el"))) + (should-not (trusted-content-p)))))) + (provide 'compat-tests) ;;; compat-tests.el ends here diff --git a/compat.texi b/compat.texi index 70afd76..b8b1335 100644 --- a/compat.texi +++ b/compat.texi @@ -2264,6 +2264,13 @@ care. The @code{defcustom} type @code{key} introduced in Emacs 29.1 is made available by Compat. +@defvar untrusted-content +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. +@end defvar + @c copied from lispref/loading.texi @defvar lisp-directory This variable holds a string naming the directory which holds Emacs's @@ -3326,6 +3333,28 @@ older than 30.1. Note that due to upstream changes, it might happen that there will be the need for changes, so use these functions with care. +@defvar trusted-files +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 +@code{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. +- else it is considered as 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 @code{:all}, in which case Emacs will trust all +files, which opens a gaping security hole. +@end defvar + +@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 @code{trusted-files}. +@end defun + @c copied from lispref/nonascii.texi @defun char-to-name char This function returns the Unicode name of @var{char}. It returns -- cgit v1.0