← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~cjwatson/launchpad:avoid-subprocess-shell into launchpad:master

 

Colin Watson has proposed merging ~cjwatson/launchpad:avoid-subprocess-shell into launchpad:master.

Commit message:
Avoid calling subprocess with shell=True

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/+git/launchpad/+merge/445865

While none of these are actually security vulnerabilities as far as I know (they're all in tests or in utilities that are only used locally), it's generally bad practice to run subprocesses via a shell.  Avoid this where it's relatively trivial to do so.
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/launchpad:avoid-subprocess-shell into launchpad:master.
diff --git a/lib/lp/answers/doc/expiration.rst b/lib/lp/answers/doc/expiration.rst
index 18b467e..84ac916 100644
--- a/lib/lp/answers/doc/expiration.rst
+++ b/lib/lp/answers/doc/expiration.rst
@@ -144,8 +144,7 @@ somebody are subject to expiration.
     # Run the script.
     >>> import subprocess
     >>> process = subprocess.Popen(
-    ...     "cronscripts/expire-questions.py",
-    ...     shell=True,
+    ...     ["cronscripts/expire-questions.py"],
     ...     stdin=subprocess.PIPE,
     ...     stdout=subprocess.PIPE,
     ...     stderr=subprocess.PIPE,
diff --git a/lib/lp/bugs/doc/bugnotification-sending.rst b/lib/lp/bugs/doc/bugnotification-sending.rst
index abf8f70..d254cc3 100644
--- a/lib/lp/bugs/doc/bugnotification-sending.rst
+++ b/lib/lp/bugs/doc/bugnotification-sending.rst
@@ -661,8 +661,7 @@ makes it write out the emails it sends.
 
     >>> import subprocess
     >>> process = subprocess.Popen(
-    ...     "cronscripts/send-bug-notifications.py -v",
-    ...     shell=True,
+    ...     ["cronscripts/send-bug-notifications.py", "-v"],
     ...     stdin=subprocess.PIPE,
     ...     stdout=subprocess.PIPE,
     ...     stderr=subprocess.PIPE,
diff --git a/lib/lp/bugs/doc/bugtask-expiration.rst b/lib/lp/bugs/doc/bugtask-expiration.rst
index 8087a48..abaa762 100644
--- a/lib/lp/bugs/doc/bugtask-expiration.rst
+++ b/lib/lp/bugs/doc/bugtask-expiration.rst
@@ -561,8 +561,7 @@ config.malone.expiration_dbuser.
 
     >>> import subprocess
     >>> process = subprocess.Popen(
-    ...     "cronscripts/expire-bugtasks.py",
-    ...     shell=True,
+    ...     ["cronscripts/expire-bugtasks.py"],
     ...     stdin=subprocess.PIPE,
     ...     stdout=subprocess.PIPE,
     ...     stderr=subprocess.PIPE,
diff --git a/lib/lp/bugs/doc/externalbugtracker-debbugs.rst b/lib/lp/bugs/doc/externalbugtracker-debbugs.rst
index d35b5e3..34861ea 100644
--- a/lib/lp/bugs/doc/externalbugtracker-debbugs.rst
+++ b/lib/lp/bugs/doc/externalbugtracker-debbugs.rst
@@ -706,8 +706,7 @@ tracker.
 
     >>> import subprocess
     >>> process = subprocess.Popen(
-    ...     "scripts/import-debian-bugs.py 237001 322535",
-    ...     shell=True,
+    ...     ["scripts/import-debian-bugs.py", "237001", "322535"],
     ...     stdin=subprocess.PIPE,
     ...     stdout=subprocess.PIPE,
     ...     stderr=subprocess.PIPE,
diff --git a/lib/lp/bugs/model/tests/test_bugtask.py b/lib/lp/bugs/model/tests/test_bugtask.py
index c3eb248..e26983b 100644
--- a/lib/lp/bugs/model/tests/test_bugtask.py
+++ b/lib/lp/bugs/model/tests/test_bugtask.py
@@ -3871,8 +3871,7 @@ class TestTargetNameCache(TestCase):
         transaction.commit()
 
         process = subprocess.Popen(
-            "cronscripts/update-bugtask-targetnamecaches.py",
-            shell=True,
+            ["cronscripts/update-bugtask-targetnamecaches.py"],
             stdin=subprocess.PIPE,
             stdout=subprocess.PIPE,
             stderr=subprocess.PIPE,
diff --git a/lib/lp/registry/doc/convert-person-to-team.rst b/lib/lp/registry/doc/convert-person-to-team.rst
index be3ef31..a64e685 100644
--- a/lib/lp/registry/doc/convert-person-to-team.rst
+++ b/lib/lp/registry/doc/convert-person-to-team.rst
@@ -16,8 +16,7 @@ team and the name of the team owner as arguments.
 
     >>> from subprocess import Popen, PIPE
     >>> process = Popen(
-    ...     "scripts/convert-person-to-team.py -q matsubara mark",
-    ...     shell=True,
+    ...     ["scripts/convert-person-to-team.py", "-q", "matsubara", "mark"],
     ...     stdin=PIPE,
     ...     stdout=PIPE,
     ...     stderr=PIPE,
diff --git a/lib/lp/registry/doc/distribution-mirror.rst b/lib/lp/registry/doc/distribution-mirror.rst
index a3bb64d..db50e08 100644
--- a/lib/lp/registry/doc/distribution-mirror.rst
+++ b/lib/lp/registry/doc/distribution-mirror.rst
@@ -741,14 +741,14 @@ First we need to run the http server that's going to answer our requests.
     >>> http_server.setUp()
 
     >>> import subprocess
-    >>> def run_prober(arguments):
-    ...     cmd = (
-    ...         "cronscripts/distributionmirror-prober.py %s "
-    ...         "--no-remote-hosts" % arguments
-    ...     )
+    >>> def run_prober(*arguments):
+    ...     cmd = [
+    ...         "cronscripts/distributionmirror-prober.py",
+    ...         "--no-remote-hosts",
+    ...     ]
+    ...     cmd.extend(arguments)
     ...     prober = subprocess.Popen(
     ...         cmd,
-    ...         shell=True,
     ...         stdin=subprocess.PIPE,
     ...         stdout=subprocess.PIPE,
     ...         stderr=subprocess.PIPE,
@@ -811,7 +811,9 @@ probed again.
 But we can override the default behaviour and tell the prober to check
 all official mirrors independently if they were probed recently or not.
 
-    >>> prober, stdout, stderr = run_prober("--content-type=cdimage --force")
+    >>> prober, stdout, stderr = run_prober(
+    ...     "--content-type=cdimage", "--force"
+    ... )
     >>> print(stdout)
     <BLANKLINE>
     >>> print(stderr)
@@ -834,7 +836,9 @@ we'll enable them again.
     True
     >>> cdimage_mirror.enabled = False
     >>> transaction.commit()
-    >>> prober, stdout, stderr = run_prober("--content-type=cdimage --force")
+    >>> prober, stdout, stderr = run_prober(
+    ...     "--content-type=cdimage", "--force"
+    ... )
     >>> print(stderr)
     INFO    Creating lockfile:
             /var/lock/launchpad-distributionmirror-prober.lock
diff --git a/lib/lp/registry/doc/person-karma.rst b/lib/lp/registry/doc/person-karma.rst
index 2091cbd..aaf5ace 100644
--- a/lib/lp/registry/doc/person-karma.rst
+++ b/lib/lp/registry/doc/person-karma.rst
@@ -125,8 +125,7 @@ is updated by the foaf-update-karma-cache.py cronscript.
 
     >>> import subprocess
     >>> process = subprocess.Popen(
-    ...     "cronscripts/foaf-update-karma-cache.py",
-    ...     shell=True,
+    ...     ["cronscripts/foaf-update-karma-cache.py"],
     ...     stdin=subprocess.PIPE,
     ...     stdout=subprocess.PIPE,
     ...     stderr=subprocess.PIPE,
diff --git a/lib/lp/registry/doc/person-notification.rst b/lib/lp/registry/doc/person-notification.rst
index 3751d2d..7c36130 100644
--- a/lib/lp/registry/doc/person-notification.rst
+++ b/lib/lp/registry/doc/person-notification.rst
@@ -92,8 +92,7 @@ This includes notifications to teams owned by other teams.
 
     >>> import subprocess
     >>> process = subprocess.Popen(
-    ...     "cronscripts/send-person-notifications.py -q",
-    ...     shell=True,
+    ...     ["cronscripts/send-person-notifications.py", "-q"],
     ...     stdin=subprocess.PIPE,
     ...     stdout=subprocess.PIPE,
     ...     stderr=subprocess.PIPE,
diff --git a/lib/lp/registry/doc/standing.rst b/lib/lp/registry/doc/standing.rst
index 936a2b2..2f13ad6 100644
--- a/lib/lp/registry/doc/standing.rst
+++ b/lib/lp/registry/doc/standing.rst
@@ -248,8 +248,7 @@ standing untouched.
 
     >>> import subprocess
     >>> process = subprocess.Popen(
-    ...     "cronscripts/update-standing.py",
-    ...     shell=True,
+    ...     ["cronscripts/update-standing.py"],
     ...     stdin=subprocess.PIPE,
     ...     stdout=subprocess.PIPE,
     ...     stderr=subprocess.PIPE,
@@ -284,8 +283,7 @@ update-standing script bumps his standing to Good too.
     >>> LaunchpadZopelessLayer.txn.commit()
 
     >>> process = subprocess.Popen(
-    ...     "cronscripts/update-standing.py",
-    ...     shell=True,
+    ...     ["cronscripts/update-standing.py"],
     ...     stdin=subprocess.PIPE,
     ...     stdout=subprocess.PIPE,
     ...     stderr=subprocess.PIPE,
diff --git a/lib/lp/registry/tests/test_karmacache_updater.py b/lib/lp/registry/tests/test_karmacache_updater.py
index fc13a39..26810e2 100644
--- a/lib/lp/registry/tests/test_karmacache_updater.py
+++ b/lib/lp/registry/tests/test_karmacache_updater.py
@@ -34,8 +34,7 @@ class TestKarmaCacheUpdater(unittest.TestCase):
 
     def _runScript(self):
         process = subprocess.Popen(
-            "cronscripts/foaf-update-karma-cache.py",
-            shell=True,
+            ["cronscripts/foaf-update-karma-cache.py"],
             stdin=subprocess.PIPE,
             stdout=subprocess.PIPE,
             stderr=subprocess.PIPE,
diff --git a/lib/lp/services/doc/pidfile.rst b/lib/lp/services/doc/pidfile.rst
index 70eabb3..a6b9173 100644
--- a/lib/lp/services/doc/pidfile.rst
+++ b/lib/lp/services/doc/pidfile.rst
@@ -67,8 +67,7 @@ the correct PID is stored in it.
     ...     int(open(pidfile_path('nuts')).read().strip() == str(os.getpid()))
     ...     )
     ... '''
-    >>> cmd = '%s -c "%s"' % (sys.executable, cmd)
-    >>> subprocess.call(cmd, shell=True)
+    >>> subprocess.call([sys.executable, "-c", cmd])
     1
 
 Moreover, the pidfile has been removed.
diff --git a/lib/lp/services/librarianserver/tests/test_apachelogparser.py b/lib/lp/services/librarianserver/tests/test_apachelogparser.py
index 82b26b4..bfa7f7b 100644
--- a/lib/lp/services/librarianserver/tests/test_apachelogparser.py
+++ b/lib/lp/services/librarianserver/tests/test_apachelogparser.py
@@ -152,8 +152,7 @@ class TestScriptRunning(TestCase):
         self.assertEqual(libraryfile_set[3].hits, 0)
 
         process = subprocess.Popen(
-            "cronscripts/parse-librarian-apache-access-logs.py",
-            shell=True,
+            ["cronscripts/parse-librarian-apache-access-logs.py"],
             stdin=subprocess.PIPE,
             stdout=subprocess.PIPE,
             stderr=subprocess.PIPE,
diff --git a/lib/lp/soyuz/scripts/tests/test_ppa_apache_log_parser.py b/lib/lp/soyuz/scripts/tests/test_ppa_apache_log_parser.py
index c62c8b2..69337ec 100644
--- a/lib/lp/soyuz/scripts/tests/test_ppa_apache_log_parser.py
+++ b/lib/lp/soyuz/scripts/tests/test_ppa_apache_log_parser.py
@@ -116,8 +116,7 @@ class TestScriptRunning(TestCaseWithFactory):
         )
 
         process = subprocess.Popen(
-            "cronscripts/parse-ppa-apache-access-logs.py",
-            shell=True,
+            ["cronscripts/parse-ppa-apache-access-logs.py"],
             stdin=subprocess.PIPE,
             stdout=subprocess.PIPE,
             stderr=subprocess.PIPE,
diff --git a/lib/lp/translations/doc/poexport-language-pack.rst b/lib/lp/translations/doc/poexport-language-pack.rst
index d4067a3..996cccd 100644
--- a/lib/lp/translations/doc/poexport-language-pack.rst
+++ b/lib/lp/translations/doc/poexport-language-pack.rst
@@ -385,7 +385,6 @@ number of command-line arguments is wrong.
     >>> def get_subprocess(command):
     ...     return subprocess.Popen(
     ...         command,
-    ...         shell=True,
     ...         stdin=subprocess.PIPE,
     ...         stdout=subprocess.PIPE,
     ...         stderr=subprocess.PIPE,
@@ -393,7 +392,7 @@ number of command-line arguments is wrong.
     ...     )
     ...
 
-    >>> proc = get_subprocess("cronscripts/language-pack-exporter.py")
+    >>> proc = get_subprocess(["cronscripts/language-pack-exporter.py"])
     >>> (out, err) = proc.communicate()
     >>> print(err)
     Traceback (most recent call last):
@@ -416,7 +415,7 @@ into the lockfilename to allow multiple exports to run concurrently for
 different distribution and series combinations.
 
     >>> proc = get_subprocess(
-    ...     "cronscripts/language-pack-exporter.py ubuntu hoary"
+    ...     ["cronscripts/language-pack-exporter.py", "ubuntu", "hoary"]
     ... )
     >>> (out, err) = proc.communicate()
     >>> print(err)
diff --git a/test_on_merge.py b/test_on_merge.py
index 05fe1cb..8df3dc7 100755
--- a/test_on_merge.py
+++ b/test_on_merge.py
@@ -9,6 +9,7 @@ import _pythonpath  # noqa: F401
 
 import os
 import select
+import shlex
 import sys
 import time
 from signal import SIGHUP, SIGINT, SIGKILL, SIGTERM
@@ -142,20 +143,18 @@ def run_test_process():
     cmd = [
         "/usr/bin/xvfb-run",
         "--error-file=/var/tmp/xvfb-errors.log",
-        "--server-args='-screen 0 1024x768x24'",
+        "--server-args=-screen 0 1024x768x24",
         os.path.join(HERE, "bin", "test"),
     ] + sys.argv[1:]
-    command_line = " ".join(cmd)
-    print("Running command:", command_line)
+    print("Running command:", " ".join(shlex.quote(arg) for arg in cmd))
 
     # Run the test suite.  Make the suite the leader of a new process group
     # so that we can signal the group without signaling ourselves.
     xvfb_proc = Popen(
-        command_line,
+        cmd,
         stdout=PIPE,
         stderr=STDOUT,
         preexec_fn=os.setpgrp,
-        shell=True,
     )
 
     # This code is very similar to what takes place in Popen._communicate(),
diff --git a/utilities/local-latency b/utilities/local-latency
index dd33c58..65df36a 100755
--- a/utilities/local-latency
+++ b/utilities/local-latency
@@ -6,35 +6,68 @@ import sys
 from script_commands import UserError, helps, run_subcommand
 
 
-def tc(command):
+def tc(*arguments):
     """Run a tc command under sudo.
 
-    :param tc: The remainder of the command (leaving out tc).
+    :param arguments: The remainder of the command (leaving out tc).
     """
-    subprocess.call("sudo tc " + command, shell=True)
+    subprocess.call(["sudo", "tc"] + arguments)
 
 
 @helps(
-    delay="Length of delay in miliseconds (each way).",
+    delay="Length of delay in milliseconds (each way).",
     port="Port to induce delay on.",
 )
 def start(delay=500, port=443):
     """Add artificial latency to the lo interface on the specified port."""
-    tc("qdisc add dev lo root handle 1: prio")
-    tc("qdisc add dev lo parent 1:3 handle 30: netem delay %dms" % delay)
+    qdisc_add = ["qdisc", "add", "dev", "lo"]
+    tc(*qdisc_add, "root", "handle", "1:", "prio")
     tc(
-        "filter add dev lo protocol ip parent 1:0 prio 3 u32 match ip"
-        " dport %d 0xffff flowid 1:3" % port
+        *qdisc_add,
+        "parent",
+        "1:3",
+        "handle",
+        "30:",
+        "netem",
+        "delay",
+        "%dms" % delay,
     )
+    filter_add_ip = ["filter", "add", "dev", "lo", "protocol", "ip"]
     tc(
-        "filter add dev lo protocol ip parent 1:0 prio 3 u32 match ip"
-        " sport %d 0xffff flowid 1:3" % port
+        *filter_add_ip,
+        "parent",
+        "1:0",
+        "prio",
+        "3",
+        "u32",
+        "match",
+        "ip",
+        "dport",
+        str(port),
+        "0xffff",
+        "flowid",
+        "1:3",
+    )
+    tc(
+        *filter_add_ip,
+        "parent",
+        "1:0",
+        "prio",
+        "3",
+        "u32",
+        "match",
+        "ip",
+        "sport",
+        str(port),
+        "0xffff",
+        "flowid",
+        "1:3",
     )
 
 
 def stop():
     """Remove latency from the lo."""
-    tc("qdisc del dev lo root")
+    tc("qdisc", "del", "dev", "lo", "root")
 
 
 subcommands = {