Subject: bug#61709: [PATCH] Security hardening: safely invoke `shell-command*' function.

From: lux <[email-masked]>
Date: Thu, 23 Feb 2023 21:17:12 +0800

On Wed, 2023-02-22 at 17:29 +0200, Eli Zaretskii wrote:
> > Cc: Xi Lu <[email-masked]>
> > From: Xi Lu <[email-masked]>
> > Date: Wed, 22 Feb 2023 22:35:54 +0800
> > 
> >  (defun filesets-which-command-p (cmd)
> >    "Call \"which CMD\" and return non-nil if the command was
> > found."
> > @@ -1264,9 +1265,11 @@ filesets-spawn-external-viewer
> >                   (funcall vwr file)
> >                   nil)
> >                  (co-flag
> > -                 (shell-command-to-string (format "%s %s" vwr
> > args)))
> > +                 (shell-command-to-string (shell-quote-argument
> > +                                            (format "%s %s" vwr
> > args))))
> >                  (t
> > -                 (shell-command (format "%s %s&" vwr args))
> > +                 (shell-command (shell-quote-argument
> > +                                  (format "%s %s&" vwr args)))
> >                   nil))))
> 
> These two cannot be right: you are quoting several separate
> command-line arguments.
> 
> >           (if co-flag
> >               (progn
> > @@ -1578,7 +1581,7 @@ filesets-run-cmd
> >                                    " "))
> >                                  (cmd (concat fn " " args)))
> >                             (filesets-cmd-show-result
> > -                            cmd (shell-command-to-string cmd))))
> > +                            cmd (shell-command-to-string (shell-
> > quote-argument cmd)))))
> >                          ((symbolp fn)
> >                           (apply fn
> >                                  (mapcan (lambda (this)
> 
> I think this is also wrong: cmd is not a single word.
> 
> In general, you cannot quote arbitrary parts of a shell command, you
> can only quote each command-line argument separately.
> 
> 
> 

You're right, thank you. I rewrited this patch.

Let me briefly explain this patch:

1. I think `filesets-select-command' not need fixed, because it not
used, and I cleaned up relevant old comments in `filesets-external-
viewers'.

2. Using `shell-quote-argument' to replace `filesets-quote' and
`(format "%S" ...)'. Because in the shell, double quotation marks can
still execute unexpected code, such as $(), `command` and $var.

From 6b85abb2de545094a3725d003a4ddb744b1e1eec Mon Sep 17 00:00:00 2001
From: Xi Lu <[email-masked]>
Date: Thu, 23 Feb 2023 20:58:00 +0800
Subject: [PATCH] Security hardening: safely invoke `shell-command*' function.

* lisp/filesets.el:
(filesets-select-command, filesets-quote): Remove unused function.
(filesets-external-viewers): Remove old comments.
(filesets-which-command, filesets-get-quoted-selection,
filesets-spawn-external-viewer): Add `shell-quote-argument'.
---
 lisp/filesets.el | 40 +++++++++-------------------------------
 1 file changed, 9 insertions(+), 31 deletions(-)

diff --git a/lisp/filesets.el b/lisp/filesets.el
index 1b7e6ffa81f..c4d51ccaf4a 100644
--- a/lisp/filesets.el
+++ b/lisp/filesets.el
@@ -161,18 +161,9 @@ 'filesets-some
 (define-obsolete-function-alias 'filesets-member #'cl-member "28.1")
 (define-obsolete-function-alias 'filesets-sublist #'seq-subseq "28.1")
 
-(defun filesets-select-command (cmd-list)
-  "Select one command from CMD-LIST -- a string with space separated names."
-  (let ((this (shell-command-to-string
-	       (format "which --skip-alias %s 2> %s | head -n 1"
-		       cmd-list null-device))))
-    (if (equal this "")
-	nil
-      (file-name-nondirectory (substring this 0 (- (length this) 1))))))
-
 (defun filesets-which-command (cmd)
   "Call \"which CMD\"."
-  (shell-command-to-string (format "which %s" cmd)))
+  (shell-command-to-string (format "which %s" (shell-quote-argument cmd))))
 
 (defun filesets-which-command-p (cmd)
   "Call \"which CMD\" and return non-nil if the command was found."
@@ -551,16 +542,6 @@ filesets-commands
 
 (defcustom filesets-external-viewers
   (let
-      ;; ((ps-cmd  (or (and (boundp 'my-ps-viewer) my-ps-viewer)
-      ;;    	    (filesets-select-command "ggv gv")))
-      ;;  (pdf-cmd (or (and (boundp 'my-ps-viewer) my-pdf-viewer)
-      ;;    	    (filesets-select-command "xpdf acroread")))
-      ;;  (dvi-cmd (or (and (boundp 'my-ps-viewer) my-dvi-viewer)
-      ;;    	    (filesets-select-command "xdvi tkdvi")))
-      ;;  (doc-cmd (or (and (boundp 'my-ps-viewer) my-doc-viewer)
-      ;;    	    (filesets-select-command "antiword")))
-      ;;  (pic-cmd (or (and (boundp 'my-ps-viewer) my-pic-viewer)
-      ;;    	    (filesets-select-command "gqview ee display"))))
       ((ps-cmd  "ggv")
        (pdf-cmd "xpdf")
        (dvi-cmd "xdvi")
@@ -1089,10 +1070,6 @@ filesets-directory-files
    (t
     (error "Filesets: %s does not exist" dir))))
 
-(defun filesets-quote (txt)
-  "Return TXT in quotes."
-  (concat "\"" txt "\""))
-
 (defun filesets-get-selection ()
   "Get the text between mark and point -- i.e. the selection or region."
   (let ((m (mark))
@@ -1103,7 +1080,7 @@ filesets-get-selection
 
 (defun filesets-get-quoted-selection ()
   "Return the currently selected text in quotes."
-  (filesets-quote (filesets-get-selection)))
+  (shell-quote-argument (filesets-get-selection)))
 
 (defun filesets-get-shortcut (n)
   "Create menu shortcuts based on number N."
@@ -1250,12 +1227,13 @@ filesets-spawn-external-viewer
 		       (if fmt
 			   (mapconcat
 			    (lambda (this)
-			      (if (stringp this) (format this file)
-				(format "%S" (if (functionp this)
-				                 (funcall this)
-				               this))))
+			      (if (stringp this)
+                                  (format this (shell-quote-argument file))
+				(shell-quote-argument (if (functionp this)
+				                          (funcall this)
+				                        this))))
 			    fmt "")
-			 (format "%S" file))))
+			 (shell-quote-argument file))))
 	       (output
 		(cond
 		 ((and (functionp vwr) co-flag)
@@ -1264,7 +1242,7 @@ filesets-spawn-external-viewer
 		  (funcall vwr file)
 		  nil)
 		 (co-flag
-		  (shell-command-to-string (format "%s %s" vwr args)))
+                  (shell-command-to-string (format "%s %s" vwr args)))
 		 (t
 		  (shell-command (format "%s %s&" vwr args))
 		  nil))))
-- 
2.39.2