On Mon, 04 May 2020 at 01:34:33 +0200, Guilhem Moulin wrote:
>   CVE-2020-11651
>   CVE-2020-11652

I found myself needing to mitigate this for a salt deployment, so I
tried backporting the upstream patches to buster.

The attached are not at all thoroughly-tested and should be reviewed
carefully by someone who knows the codebase, but they seem to work, and
the proof-of-concept from
https://github.com/rossengeorgiev/salt-security-backports no longer reports
that the master is vulnerable. This was only a stopgap, because that
deployment is now using the packages from saltstack.com instead, but it
might be useful to the salt maintainers.

There are also unofficial backports in
https://github.com/rossengeorgiev/salt-security-backports - I tried doing
the cherry-picks myself and then compared what I got with those, in an
attempt to guard against mistakes (by either myself or the author of those
backports).

Note that patch 0003 contains unofficial workarounds for regressions in the
release that fixed those CVEs, which you might prefer to exclude from an
official update.

    smcv
>From f17495581f0d7cc7bb94fa2c1f97517d16e2dd7a Mon Sep 17 00:00:00 2001
From: Simon McVittie <s...@collabora.com>
Date: Mon, 4 May 2020 12:39:29 +0100
Subject: [PATCH 1/4] Apply upstream patch for CVE-2019-17361

Signed-off-by: Simon McVittie <s...@collabora.com>
Closes: #949222
---
 .../0003-Use-renamed-python3-tornado4.patch   | 185 ++++++------
 ...17361-various-netapi-fixes-and-tests.patch | 280 ++++++++++++++++++
 debian/patches/series                         |   1 +
 3 files changed, 376 insertions(+), 90 deletions(-)
 create mode 100644 debian/patches/CVE-2019-17361-various-netapi-fixes-and-tests.patch

diff --git a/debian/patches/0003-Use-renamed-python3-tornado4.patch b/debian/patches/0003-Use-renamed-python3-tornado4.patch
index 01ad3154bf..1f0878a7a0 100644
--- a/debian/patches/0003-Use-renamed-python3-tornado4.patch
+++ b/debian/patches/0003-Use-renamed-python3-tornado4.patch
@@ -1,54 +1,53 @@
-From d17823525d0842ea3e643b024714643b4949ec1b Mon Sep 17 00:00:00 2001
 From: Benjamin Drung <benjamin.dr...@cloud.ionos.com>
 Date: Mon, 17 Dec 2018 20:35:19 +0100
 Subject: [PATCH 3/3] Use renamed python3-tornado4
 
 ---
- doc/conf.py                                   | 17 ++++++++++++
- salt/client/__init__.py                       |  5 +++-
- salt/client/mixins.py                         |  5 +++-
- salt/crypt.py                                 | 11 +++++---
- salt/engines/ircbot.py                        |  8 ++++--
- salt/engines/webhook.py                       | 11 +++++---
- salt/fileclient.py                            |  5 +++-
- salt/master.py                                |  5 +++-
- salt/minion.py                                | 17 ++++++++----
- salt/netapi/rest_tornado/__init__.py          | 13 +++++++---
- salt/netapi/rest_tornado/saltnado.py          | 20 +++++++++-----
- .../rest_tornado/saltnado_websockets.py       |  9 ++++---
- salt/pillar/__init__.py                       |  5 +++-
- salt/transport/client.py                      |  9 +++++--
- salt/transport/ipc.py                         | 23 +++++++++++-----
- salt/transport/mixins/auth.py                 |  5 +++-
- salt/transport/tcp.py                         | 26 +++++++++++++------
- salt/transport/zeromq.py                      | 20 +++++++++-----
- salt/utils/asynchronous.py                    |  8 ++++--
- salt/utils/event.py                           |  8 ++++--
- salt/utils/gitfs.py                           |  5 +++-
- salt/utils/http.py                            | 11 +++++---
- salt/utils/process.py                         |  5 +++-
- salt/utils/thin.py                            |  5 +++-
- salt/utils/zeromq.py                          |  8 ++++--
- salt/version.py                               | 10 +++++--
- tests/integration/__init__.py                 |  8 ++++--
- .../files/engines/runtests_engine.py          |  8 +++---
- tests/integration/modules/test_gem.py         |  5 +++-
- tests/integration/modules/test_ssh.py         |  5 +++-
- tests/support/helpers.py                      |  8 ++++--
- tests/unit/fileserver/test_gitfs.py           |  5 +++-
- tests/unit/modules/test_random_org.py         |  5 +++-
- tests/unit/netapi/test_rest_tornado.py        | 20 +++++++++-----
- tests/unit/test_minion.py                     | 18 +++++++++----
- tests/unit/transport/test_ipc.py              | 11 +++++---
- tests/unit/transport/test_tcp.py              | 14 +++++++---
- tests/unit/transport/test_zeromq.py           | 13 ++++++++--
- tests/unit/utils/test_asynchronous.py         | 11 +++++---
- tests/unit/utils/test_context.py              | 11 +++++---
- tests/unit/utils/test_event.py                |  5 +++-
- 41 files changed, 314 insertions(+), 107 deletions(-)
+ doc/conf.py                                        | 17 ++++++++++++++
+ salt/client/__init__.py                            |  5 ++++-
+ salt/client/mixins.py                              |  5 ++++-
+ salt/crypt.py                                      | 11 ++++++---
+ salt/engines/ircbot.py                             |  8 +++++--
+ salt/engines/webhook.py                            | 11 ++++++---
+ salt/fileclient.py                                 |  5 ++++-
+ salt/master.py                                     |  5 ++++-
+ salt/minion.py                                     | 17 +++++++++-----
+ salt/netapi/rest_tornado/__init__.py               | 13 ++++++++---
+ salt/netapi/rest_tornado/saltnado.py               | 20 ++++++++++++-----
+ salt/netapi/rest_tornado/saltnado_websockets.py    |  9 +++++---
+ salt/pillar/__init__.py                            |  5 ++++-
+ salt/transport/client.py                           |  9 ++++++--
+ salt/transport/ipc.py                              | 23 +++++++++++++------
+ salt/transport/mixins/auth.py                      |  5 ++++-
+ salt/transport/tcp.py                              | 26 +++++++++++++++-------
+ salt/transport/zeromq.py                           | 20 ++++++++++++-----
+ salt/utils/asynchronous.py                         |  8 +++++--
+ salt/utils/event.py                                |  8 +++++--
+ salt/utils/gitfs.py                                |  5 ++++-
+ salt/utils/http.py                                 | 11 ++++++---
+ salt/utils/process.py                              |  5 ++++-
+ salt/utils/thin.py                                 |  5 ++++-
+ salt/utils/zeromq.py                               |  8 +++++--
+ salt/version.py                                    | 10 +++++++--
+ tests/integration/__init__.py                      |  8 +++++--
+ tests/integration/files/engines/runtests_engine.py |  8 +++----
+ tests/integration/modules/test_gem.py              |  5 ++++-
+ tests/integration/modules/test_ssh.py              |  5 ++++-
+ tests/support/helpers.py                           | 10 ++++++---
+ tests/unit/fileserver/test_gitfs.py                |  5 ++++-
+ tests/unit/modules/test_random_org.py              |  5 ++++-
+ tests/unit/netapi/test_rest_tornado.py             | 20 ++++++++++++-----
+ tests/unit/test_minion.py                          | 18 ++++++++++-----
+ tests/unit/transport/test_ipc.py                   | 11 ++++++---
+ tests/unit/transport/test_tcp.py                   | 14 ++++++++----
+ tests/unit/transport/test_zeromq.py                | 13 +++++++++--
+ tests/unit/utils/test_asynchronous.py              | 11 ++++++---
+ tests/unit/utils/test_context.py                   | 11 ++++++---
+ tests/unit/utils/test_event.py                     |  5 ++++-
+ 41 files changed, 315 insertions(+), 108 deletions(-)
 
 diff --git a/doc/conf.py b/doc/conf.py
-index 23d3442c16..720026b823 100644
+index 209dab4..8d81bbe 100644
 --- a/doc/conf.py
 +++ b/doc/conf.py
 @@ -137,6 +137,22 @@ MOCK_MODULES = [
@@ -83,7 +82,7 @@ index 23d3442c16..720026b823 100644
  sys.modules['msgpack'].version = (1, 0, 0)
  sys.modules['psutil'].version_info = (3, 0, 0)
 diff --git a/salt/client/__init__.py b/salt/client/__init__.py
-index 0975a9ed7a..230a615713 100644
+index 0975a9e..230a615 100644
 --- a/salt/client/__init__.py
 +++ b/salt/client/__init__.py
 @@ -68,7 +68,10 @@ except ImportError:
@@ -99,7 +98,7 @@ index 0975a9ed7a..230a615713 100644
  log = logging.getLogger(__name__)
  
 diff --git a/salt/client/mixins.py b/salt/client/mixins.py
-index 7d49fe43b0..d930c1e0e2 100644
+index 7d49fe4..d930c1e 100644
 --- a/salt/client/mixins.py
 +++ b/salt/client/mixins.py
 @@ -33,7 +33,10 @@ import salt.log.setup
@@ -115,7 +114,7 @@ index 7d49fe43b0..d930c1e0e2 100644
  log = logging.getLogger(__name__)
  
 diff --git a/salt/crypt.py b/salt/crypt.py
-index 750d0bf103..f37348482c 100644
+index 750d0bf..f373484 100644
 --- a/salt/crypt.py
 +++ b/salt/crypt.py
 @@ -22,9 +22,14 @@ import traceback
@@ -137,7 +136,7 @@ index 750d0bf103..f37348482c 100644
  # Import third party libs
  from salt.ext.six.moves import zip  # pylint: disable=import-error,redefined-builtin
 diff --git a/salt/engines/ircbot.py b/salt/engines/ircbot.py
-index 12d6161c7d..5c8c694e63 100644
+index 12d6161..5c8c694 100644
 --- a/salt/engines/ircbot.py
 +++ b/salt/engines/ircbot.py
 @@ -64,8 +64,12 @@ import socket
@@ -156,7 +155,7 @@ index 12d6161c7d..5c8c694e63 100644
  import logging
  log = logging.getLogger(__name__)
 diff --git a/salt/engines/webhook.py b/salt/engines/webhook.py
-index c4cb8528a1..7e73a88399 100644
+index c4cb852..7e73a88 100644
 --- a/salt/engines/webhook.py
 +++ b/salt/engines/webhook.py
 @@ -5,9 +5,14 @@ Send events from webhook api
@@ -178,7 +177,7 @@ index c4cb8528a1..7e73a88399 100644
  # import salt libs
  import salt.utils.event
 diff --git a/salt/fileclient.py b/salt/fileclient.py
-index 745045600e..508009ec7c 100644
+index 7450456..508009e 100644
 --- a/salt/fileclient.py
 +++ b/salt/fileclient.py
 @@ -12,7 +12,10 @@ import os
@@ -194,7 +193,7 @@ index 745045600e..508009ec7c 100644
  
  # Import salt libs
 diff --git a/salt/master.py b/salt/master.py
-index 63cfe823ac..0c45121f67 100644
+index 782e3bb..a084bb0 100644
 --- a/salt/master.py
 +++ b/salt/master.py
 @@ -27,7 +27,10 @@ from salt.ext.six.moves import range
@@ -210,7 +209,7 @@ index 63cfe823ac..0c45121f67 100644
  # Import salt libs
  import salt.crypt
 diff --git a/salt/minion.py b/salt/minion.py
-index 138d1097b7..b310b2d917 100644
+index dab75ab..5b147e3 100644
 --- a/salt/minion.py
 +++ b/salt/minion.py
 @@ -32,7 +32,10 @@ from salt.ext.six.moves import range
@@ -245,7 +244,7 @@ index 138d1097b7..b310b2d917 100644
  log = logging.getLogger(__name__)
  
 diff --git a/salt/netapi/rest_tornado/__init__.py b/salt/netapi/rest_tornado/__init__.py
-index 115e6cafd1..c4efa8472b 100644
+index 115e6ca..c4efa84 100644
 --- a/salt/netapi/rest_tornado/__init__.py
 +++ b/salt/netapi/rest_tornado/__init__.py
 @@ -18,13 +18,20 @@ log = logging.getLogger(__virtualname__)
@@ -273,7 +272,7 @@ index 115e6cafd1..c4efa8472b 100644
      has_tornado = False
      log.error('ImportError! %s', err)
 diff --git a/salt/netapi/rest_tornado/saltnado.py b/salt/netapi/rest_tornado/saltnado.py
-index f038850c98..7c590ca910 100644
+index f038850..7c590ca 100644
 --- a/salt/netapi/rest_tornado/saltnado.py
 +++ b/salt/netapi/rest_tornado/saltnado.py
 @@ -195,12 +195,20 @@ from collections import defaultdict
@@ -304,7 +303,7 @@ index f038850c98..7c590ca910 100644
  
  # salt imports
 diff --git a/salt/netapi/rest_tornado/saltnado_websockets.py b/salt/netapi/rest_tornado/saltnado_websockets.py
-index 0450b6cc16..03a33c627e 100644
+index 0450b6c..03a33c6 100644
 --- a/salt/netapi/rest_tornado/saltnado_websockets.py
 +++ b/salt/netapi/rest_tornado/saltnado_websockets.py
 @@ -291,12 +291,15 @@ Setup
@@ -327,7 +326,7 @@ index 0450b6cc16..03a33c627e 100644
  import salt.netapi
  
 diff --git a/salt/pillar/__init__.py b/salt/pillar/__init__.py
-index cf6b40467a..e8e054cb8f 100644
+index cf6b404..e8e054c 100644
 --- a/salt/pillar/__init__.py
 +++ b/salt/pillar/__init__.py
 @@ -10,7 +10,10 @@ import fnmatch
@@ -343,7 +342,7 @@ index cf6b40467a..e8e054cb8f 100644
  import traceback
  import inspect
 diff --git a/salt/transport/client.py b/salt/transport/client.py
-index ca83ac9376..b9eeb5d7e8 100644
+index ca83ac9..b9eeb5d 100644
 --- a/salt/transport/client.py
 +++ b/salt/transport/client.py
 @@ -79,9 +79,14 @@ class AsyncChannel(object):
@@ -364,7 +363,7 @@ index ca83ac9376..b9eeb5d7e8 100644
          cls._resolver_configured = True
  
 diff --git a/salt/transport/ipc.py b/salt/transport/ipc.py
-index 6a34abca30..1d44d4d3ff 100644
+index 6a34abc..1d44d4d 100644
 --- a/salt/transport/ipc.py
 +++ b/salt/transport/ipc.py
 @@ -14,13 +14,22 @@ import time
@@ -398,7 +397,7 @@ index 6a34abca30..1d44d4d3ff 100644
  import salt.transport.client
  import salt.transport.frame
 diff --git a/salt/transport/mixins/auth.py b/salt/transport/mixins/auth.py
-index b4e3bbd7ea..7645ec74cf 100644
+index b4e3bbd..7645ec7 100644
 --- a/salt/transport/mixins/auth.py
 +++ b/salt/transport/mixins/auth.py
 @@ -24,7 +24,10 @@ from salt.utils.cache import CacheCli
@@ -414,7 +413,7 @@ index b4e3bbd7ea..7645ec74cf 100644
      from M2Crypto import RSA
      HAS_M2 = True
 diff --git a/salt/transport/tcp.py b/salt/transport/tcp.py
-index 2122e300fe..c6142debeb 100644
+index 2122e30..c6142de 100644
 --- a/salt/transport/tcp.py
 +++ b/salt/transport/tcp.py
 @@ -38,14 +38,24 @@ from salt.exceptions import SaltReqTimeoutError, SaltClientError
@@ -451,7 +450,7 @@ index 2122e300fe..c6142debeb 100644
  # pylint: disable=import-error,no-name-in-module
  if six.PY2:
 diff --git a/salt/transport/zeromq.py b/salt/transport/zeromq.py
-index 999197ba3a..cd1588e1cc 100644
+index 999197b..160e03e 100644
 --- a/salt/transport/zeromq.py
 +++ b/salt/transport/zeromq.py
 @@ -36,9 +36,6 @@ from salt.exceptions import SaltReqTimeoutError
@@ -489,7 +488,7 @@ index 999197ba3a..cd1588e1cc 100644
  # Import third party libs
  try:
 diff --git a/salt/utils/asynchronous.py b/salt/utils/asynchronous.py
-index e5351a5f06..34e28c0abd 100644
+index e5351a5..34e28c0 100644
 --- a/salt/utils/asynchronous.py
 +++ b/salt/utils/asynchronous.py
 @@ -5,8 +5,12 @@ Helpers/utils for working with tornado asynchronous stuff
@@ -508,7 +507,7 @@ index e5351a5f06..34e28c0abd 100644
  from salt.utils import zeromq
  
 diff --git a/salt/utils/event.py b/salt/utils/event.py
-index 91b5160cc8..3d5bee2d4a 100644
+index 91b5160..3d5bee2 100644
 --- a/salt/utils/event.py
 +++ b/salt/utils/event.py
 @@ -71,8 +71,12 @@ from salt.ext.six.moves import range
@@ -527,7 +526,7 @@ index 91b5160cc8..3d5bee2d4a 100644
  # Import salt libs
  import salt.config
 diff --git a/salt/utils/gitfs.py b/salt/utils/gitfs.py
-index f6f95ee60d..7de56d249c 100644
+index 6650076..d7b9c4a 100644
 --- a/salt/utils/gitfs.py
 +++ b/salt/utils/gitfs.py
 @@ -18,7 +18,10 @@ import shutil
@@ -543,7 +542,7 @@ index f6f95ee60d..7de56d249c 100644
  from datetime import datetime
  
 diff --git a/salt/utils/http.py b/salt/utils/http.py
-index 0a970bec33..16c6e70f6d 100644
+index 0a970be..16c6e70 100644
 --- a/salt/utils/http.py
 +++ b/salt/utils/http.py
 @@ -66,9 +66,14 @@ from salt.ext.six.moves.urllib.parse import urlencode as _urlencode
@@ -565,7 +564,7 @@ index 0a970bec33..16c6e70f6d 100644
  try:
      from tornado.curl_httpclient import CurlAsyncHTTPClient
 diff --git a/salt/utils/process.py b/salt/utils/process.py
-index b0d8da06c9..cc1efb75d8 100644
+index b0d8da0..cc1efb75 100644
 --- a/salt/utils/process.py
 +++ b/salt/utils/process.py
 @@ -33,7 +33,10 @@ from salt.log.mixins import NewStyleClassMixIn
@@ -581,7 +580,7 @@ index b0d8da06c9..cc1efb75d8 100644
  log = logging.getLogger(__name__)
  
 diff --git a/salt/utils/thin.py b/salt/utils/thin.py
-index 634309186c..5922de47ad 100644
+index 6343091..5922de4 100644
 --- a/salt/utils/thin.py
 +++ b/salt/utils/thin.py
 @@ -20,7 +20,10 @@ import jinja2
@@ -597,7 +596,7 @@ index 634309186c..5922de47ad 100644
  # pylint: disable=import-error,no-name-in-module
  try:
 diff --git a/salt/utils/zeromq.py b/salt/utils/zeromq.py
-index 5611ad55f7..adddc7bf77 100644
+index 5611ad5..adddc7b 100644
 --- a/salt/utils/zeromq.py
 +++ b/salt/utils/zeromq.py
 @@ -6,8 +6,12 @@ ZMQ-specific functions
@@ -616,7 +615,7 @@ index 5611ad55f7..adddc7bf77 100644
  from salt._compat import ipaddress
  
 diff --git a/salt/version.py b/salt/version.py
-index 715a9eb43e..6a247ceb8b 100644
+index af08d64..59a072f 100644
 --- a/salt/version.py
 +++ b/salt/version.py
 @@ -590,7 +590,7 @@ def dependency_information(include_salt_cloud=False):
@@ -644,7 +643,7 @@ index 715a9eb43e..6a247ceb8b 100644
              if callable(version):
                  version = version()
 diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py
-index 86b3a8d5dc..99fac29b69 100644
+index 86b3a8d..99fac29 100644
 --- a/tests/integration/__init__.py
 +++ b/tests/integration/__init__.py
 @@ -77,8 +77,12 @@ try:
@@ -663,7 +662,7 @@ index 86b3a8d5dc..99fac29b69 100644
  # Import salt tests support libs
  from tests.support.processes import SaltMaster, SaltMinion, SaltSyndic
 diff --git a/tests/integration/files/engines/runtests_engine.py b/tests/integration/files/engines/runtests_engine.py
-index 4db9e72809..d3b28fda88 100644
+index 4db9e72..d3b28fd 100644
 --- a/tests/integration/files/engines/runtests_engine.py
 +++ b/tests/integration/files/engines/runtests_engine.py
 @@ -24,10 +24,10 @@ import salt.utils.event
@@ -682,7 +681,7 @@ index 4db9e72809..d3b28fda88 100644
  log = logging.getLogger(__name__)
  
 diff --git a/tests/integration/modules/test_gem.py b/tests/integration/modules/test_gem.py
-index eb35c6a513..2ad0a1e1e0 100644
+index eb35c6a..2ad0a1e 100644
 --- a/tests/integration/modules/test_gem.py
 +++ b/tests/integration/modules/test_gem.py
 @@ -15,7 +15,10 @@ from tests.support.helpers import destructiveTest
@@ -698,10 +697,10 @@ index eb35c6a513..2ad0a1e1e0 100644
  GEM = 'tidy'
  GEM_VER = '1.1.2'
 diff --git a/tests/integration/modules/test_ssh.py b/tests/integration/modules/test_ssh.py
-index 493c5de8ab..20cb5c3eda 100644
+index 34bbcb9..deffcd1 100644
 --- a/tests/integration/modules/test_ssh.py
 +++ b/tests/integration/modules/test_ssh.py
-@@ -18,7 +18,10 @@ from tests.support.helpers import skip_if_binaries_missing
+@@ -18,7 +18,10 @@ import salt.utils.files
  import salt.utils.platform
  
  # Import 3rd-party libs
@@ -714,7 +713,7 @@ index 493c5de8ab..20cb5c3eda 100644
  SUBSALT_DIR = os.path.join(TMP, 'subsalt')
  AUTHORIZED_KEYS = os.path.join(SUBSALT_DIR, 'authorized_keys')
 diff --git a/tests/support/helpers.py b/tests/support/helpers.py
-index 5cd5ffd1d8..d7fe729bcb 100644
+index e940ab5..7e99175 100644
 --- a/tests/support/helpers.py
 +++ b/tests/support/helpers.py
 @@ -30,8 +30,12 @@ import tempfile
@@ -725,15 +724,24 @@ index 5cd5ffd1d8..d7fe729bcb 100644
 -from tornado.web import Application, StaticFileHandler
 +try:
 +    from tornado4.ioloop import IOLoop
-+    from tornado4.web import Application, StaticFileHandler
++    from tornado4.web import Application, RequestHandler, StaticFileHandler
 +except ImportError:
 +    from tornado.ioloop import IOLoop
-+    from tornado.web import Application, StaticFileHandler
++    from tornado.web import Application, RequestHandler, StaticFileHandler
  import types
  
  # Import 3rd-party libs
+@@ -1582,7 +1586,7 @@ class Webserver(object):
+         self.server_thread.join()
+ 
+ 
+-class SaveRequestsPostHandler(tornado.web.RequestHandler):
++class SaveRequestsPostHandler(RequestHandler):
+     '''
+     Mirror a POST body back to the client.
+     '''
 diff --git a/tests/unit/fileserver/test_gitfs.py b/tests/unit/fileserver/test_gitfs.py
-index 3ff8614b2f..1a07bc2c16 100644
+index 3ff8614..1a07bc2 100644
 --- a/tests/unit/fileserver/test_gitfs.py
 +++ b/tests/unit/fileserver/test_gitfs.py
 @@ -18,7 +18,10 @@ try:
@@ -749,7 +757,7 @@ index 3ff8614b2f..1a07bc2c16 100644
  # Import Salt Testing Libs
  from tests.support.mixins import LoaderModuleMockMixin
 diff --git a/tests/unit/modules/test_random_org.py b/tests/unit/modules/test_random_org.py
-index 1ea6d3b44c..7e9dafd455 100644
+index 1ea6d3b..7e9dafd 100644
 --- a/tests/unit/modules/test_random_org.py
 +++ b/tests/unit/modules/test_random_org.py
 @@ -14,7 +14,10 @@ from tests.support.unit import TestCase
@@ -765,7 +773,7 @@ index 1ea6d3b44c..7e9dafd455 100644
  
  def check_status():
 diff --git a/tests/unit/netapi/test_rest_tornado.py b/tests/unit/netapi/test_rest_tornado.py
-index 99db5e840a..e37b26bd3b 100644
+index 99db5e8..e37b26b 100644
 --- a/tests/unit/netapi/test_rest_tornado.py
 +++ b/tests/unit/netapi/test_rest_tornado.py
 @@ -25,12 +25,20 @@ except ImportError:
@@ -796,7 +804,7 @@ index 99db5e840a..e37b26bd3b 100644
      from salt.netapi.rest_tornado import saltnado
      HAS_TORNADO = True
 diff --git a/tests/unit/test_minion.py b/tests/unit/test_minion.py
-index 6ef680b1b9..a673fba86d 100644
+index d9d1b99..c827568 100644
 --- a/tests/unit/test_minion.py
 +++ b/tests/unit/test_minion.py
 @@ -18,10 +18,18 @@ import salt.minion
@@ -832,7 +840,7 @@ index 6ef680b1b9..a673fba86d 100644
              mock_opts = salt.config.DEFAULT_MINION_OPTS
              mock_opts['minion_jid_queue_hwm'] = 100
 diff --git a/tests/unit/transport/test_ipc.py b/tests/unit/transport/test_ipc.py
-index 48362b9820..e2954eae80 100644
+index 48362b9..e2954ea 100644
 --- a/tests/unit/transport/test_ipc.py
 +++ b/tests/unit/transport/test_ipc.py
 @@ -10,9 +10,14 @@ import errno
@@ -854,7 +862,7 @@ index 48362b9820..e2954eae80 100644
  import salt.config
  import salt.exceptions
 diff --git a/tests/unit/transport/test_tcp.py b/tests/unit/transport/test_tcp.py
-index 84546128d5..8a9e2dac31 100644
+index 8454612..8a9e2da 100644
 --- a/tests/unit/transport/test_tcp.py
 +++ b/tests/unit/transport/test_tcp.py
 @@ -7,10 +7,16 @@
@@ -879,7 +887,7 @@ index 84546128d5..8a9e2dac31 100644
  import salt.config
  from salt.ext import six
 diff --git a/tests/unit/transport/test_zeromq.py b/tests/unit/transport/test_zeromq.py
-index 798ab0c6c6..3846c30cb3 100644
+index 798ab0c..3846c30 100644
 --- a/tests/unit/transport/test_zeromq.py
 +++ b/tests/unit/transport/test_zeromq.py
 @@ -19,12 +19,21 @@ except ImportError:
@@ -907,7 +915,7 @@ index 798ab0c6c6..3846c30cb3 100644
  # Import Salt libs
  import salt.config
 diff --git a/tests/unit/utils/test_asynchronous.py b/tests/unit/utils/test_asynchronous.py
-index c8b4ffadc0..a1764f72a9 100644
+index c8b4ffa..a1764f7 100644
 --- a/tests/unit/utils/test_asynchronous.py
 +++ b/tests/unit/utils/test_asynchronous.py
 @@ -4,9 +4,14 @@
@@ -929,7 +937,7 @@ index c8b4ffadc0..a1764f72a9 100644
  import salt.utils.asynchronous as asynchronous
  
 diff --git a/tests/unit/utils/test_context.py b/tests/unit/utils/test_context.py
-index 787a5b5e57..14aa2712da 100644
+index 787a5b5..14aa271 100644
 --- a/tests/unit/utils/test_context.py
 +++ b/tests/unit/utils/test_context.py
 @@ -5,9 +5,14 @@
@@ -951,7 +959,7 @@ index 787a5b5e57..14aa2712da 100644
  import time
  
 diff --git a/tests/unit/utils/test_event.py b/tests/unit/utils/test_event.py
-index d93e0b94f8..21cd44ecda 100644
+index 15b8fe4..576b7ab 100644
 --- a/tests/unit/utils/test_event.py
 +++ b/tests/unit/utils/test_event.py
 @@ -12,7 +12,10 @@ from __future__ import absolute_import, unicode_literals, print_function
@@ -966,6 +974,3 @@ index d93e0b94f8..21cd44ecda 100644
  import zmq
  import zmq.eventloop.ioloop
  # support pyzmq 13.0.x, TODO: remove once we force people to 14.0.x
--- 
-2.17.1
-
diff --git a/debian/patches/CVE-2019-17361-various-netapi-fixes-and-tests.patch b/debian/patches/CVE-2019-17361-various-netapi-fixes-and-tests.patch
new file mode 100644
index 0000000000..34b93381bc
--- /dev/null
+++ b/debian/patches/CVE-2019-17361-various-netapi-fixes-and-tests.patch
@@ -0,0 +1,280 @@
+From: "Gareth J. Greenaway" <gar...@wiked.org>
+Date: Tue, 7 Jan 2020 11:22:31 -0800
+Subject: CVE-2019-17361: various netapi fixes and tests
+
+(cherry picked from commit bca115f3f00fbde564dd2f12bf036b5d2fd08387)
+
+Origin: upstream, 2019.2.3, commit:https://github.com/saltstack/salt/commit/bca115f3f00fbde564dd2f12bf036b5d2fd08387
+Bug: CVE-2019-17361
+Bug-Debian: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=949222
+---
+ conf/master                             |   6 ++
+ salt/config/__init__.py                 |   5 ++
+ salt/netapi/__init__.py                 |   7 +-
+ tests/integration/netapi/test_client.py | 136 +++++++++++++++++++++++++++++++-
+ tests/support/helpers.py                |  19 +++++
+ 5 files changed, 171 insertions(+), 2 deletions(-)
+
+diff --git a/conf/master b/conf/master
+index ebd7028..6f8008a 100644
+--- a/conf/master
++++ b/conf/master
+@@ -1282,3 +1282,9 @@
+ # use OS defaults, typically 75 seconds on Linux, see
+ # /proc/sys/net/ipv4/tcp_keepalive_intvl.
+ #tcp_keepalive_intvl: -1
++
++
++#####         NetAPI settings          #####
++############################################
++# Allow the raw_shell parameter to be used when calling Salt SSH client via API
++#netapi_allow_raw_shell: True
+diff --git a/salt/config/__init__.py b/salt/config/__init__.py
+index c391558..2e08305 100644
+--- a/salt/config/__init__.py
++++ b/salt/config/__init__.py
+@@ -1194,6 +1194,10 @@ VALID_OPTS = {
+ 
+     # Enable calling ssh minions from the salt master
+     'enable_ssh_minions': bool,
++
++    # Allow raw_shell option when using the ssh
++    # client via the Salt API
++    'netapi_allow_raw_shell': bool,
+ }
+ 
+ # default configurations
+@@ -1835,6 +1839,7 @@ DEFAULT_MASTER_OPTS = {
+     'auth_events': True,
+     'minion_data_cache_events': True,
+     'enable_ssh_minions': False,
++    'netapi_allow_raw_shell': False,
+ }
+ 
+ 
+diff --git a/salt/netapi/__init__.py b/salt/netapi/__init__.py
+index ce70030..6560da8 100644
+--- a/salt/netapi/__init__.py
++++ b/salt/netapi/__init__.py
+@@ -66,10 +66,15 @@ class NetapiClient(object):
+             raise salt.exceptions.SaltInvocationError(
+                     'Invalid client specified: \'{0}\''.format(low.get('client')))
+ 
+-        if not ('token' in low or 'eauth' in low) and low['client'] != 'ssh':
++        if not ('token' in low or 'eauth' in low):
+             raise salt.exceptions.EauthAuthenticationError(
+                     'No authentication credentials given')
+ 
++        if low.get('raw_shell') and \
++                not self.opts.get('netapi_allow_raw_shell'):
++            raise salt.exceptions.EauthAuthenticationError(
++                    'Raw shell option not allowed.')
++
+         l_fun = getattr(self, low['client'])
+         f_call = salt.utils.args.format_call(l_fun, low)
+         return l_fun(*f_call.get('args', ()), **f_call.get('kwargs', {}))
+diff --git a/tests/integration/netapi/test_client.py b/tests/integration/netapi/test_client.py
+index 2a57aee..e967dd3 100644
+--- a/tests/integration/netapi/test_client.py
++++ b/tests/integration/netapi/test_client.py
+@@ -2,17 +2,31 @@
+ 
+ # Import Python libs
+ from __future__ import absolute_import, print_function, unicode_literals
++import logging
+ import os
+ import time
+ 
+ # Import Salt Testing libs
+-from tests.support.paths import TMP_CONF_DIR
++from tests.support.paths import TMP_CONF_DIR, TMP
+ from tests.support.unit import TestCase, skipIf
++from tests.support.mock import patch
++from tests.support.case import SSHCase
++from tests.support.helpers import (
++    Webserver,
++    SaveRequestsPostHandler,
++    requires_sshd_server
++)
+ 
+ # Import Salt libs
+ import salt.config
+ import salt.netapi
+ 
++from salt.exceptions import (
++    EauthAuthenticationError
++)
++
++log = logging.getLogger(__name__)
++
+ 
+ class NetapiClientTest(TestCase):
+     eauth_creds = {
+@@ -61,6 +75,12 @@ class NetapiClientTest(TestCase):
+         ret['minions'] = sorted(ret['minions'])
+         self.assertEqual(ret, {'minions': sorted(['minion', 'sub_minion'])})
+ 
++    def test_local_unauthenticated(self):
++        low = {'client': 'local', 'tgt': '*', 'fun': 'test.ping'}
++
++        with self.assertRaises(EauthAuthenticationError) as excinfo:
++            ret = self.netapi.run(low)
++
+     def test_wheel(self):
+         low = {'client': 'wheel', 'fun': 'key.list_all'}
+         low.update(self.eauth_creds)
+@@ -94,6 +114,12 @@ class NetapiClientTest(TestCase):
+         self.assertIn('jid', ret)
+         self.assertIn('tag', ret)
+ 
++    def test_wheel_unauthenticated(self):
++        low = {'client': 'wheel', 'tgt': '*', 'fun': 'test.ping'}
++
++        with self.assertRaises(EauthAuthenticationError) as excinfo:
++            ret = self.netapi.run(low)
++
+     @skipIf(True, 'This is not testing anything. Skipping for now.')
+     def test_runner(self):
+         # TODO: fix race condition in init of event-- right now the event class
+@@ -112,3 +138,111 @@ class NetapiClientTest(TestCase):
+         low.update(self.eauth_creds)
+ 
+         ret = self.netapi.run(low)
++
++    def test_runner_unauthenticated(self):
++        low = {'client': 'runner', 'tgt': '*', 'fun': 'test.ping'}
++
++        with self.assertRaises(EauthAuthenticationError) as excinfo:
++            ret = self.netapi.run(low)
++
++
++@requires_sshd_server
++class NetapiSSHClientTest(SSHCase):
++    eauth_creds = {
++        'username': 'saltdev_auto',
++        'password': 'saltdev',
++        'eauth': 'auto',
++    }
++
++    def setUp(self):
++        '''
++        Set up a NetapiClient instance
++        '''
++        opts = salt.config.client_config(os.path.join(TMP_CONF_DIR, 'master'))
++        self.netapi = salt.netapi.NetapiClient(opts)
++
++        # Initialize salt-ssh
++        self.run_function('test.ping')
++
++    def tearDown(self):
++        del self.netapi
++
++    @classmethod
++    def setUpClass(cls):
++        cls.post_webserver = Webserver(handler=SaveRequestsPostHandler)
++        cls.post_webserver.start()
++        cls.post_web_root = cls.post_webserver.web_root
++        cls.post_web_handler = cls.post_webserver.handler
++
++    @classmethod
++    def tearDownClass(cls):
++        cls.post_webserver.stop()
++        del cls.post_webserver
++
++    def test_ssh(self):
++        low = {'client': 'ssh', 'tgt': 'localhost', 'fun': 'test.ping'}
++        low.update(self.eauth_creds)
++
++        ret = self.netapi.run(low)
++
++        self.assertIn('localhost', ret)
++        self.assertEqual(ret['localhost']['return'], True)
++        self.assertEqual(ret['localhost']['id'], 'localhost')
++        self.assertEqual(ret['localhost']['fun'], 'test.ping')
++
++    def test_ssh_unauthenticated(self):
++        low = {'client': 'ssh', 'tgt': 'localhost', 'fun': 'test.ping'}
++
++        with self.assertRaises(EauthAuthenticationError) as excinfo:
++            ret = self.netapi.run(low)
++
++    def test_ssh_unauthenticated_raw_shell_curl(self):
++
++        fun = '-o ProxyCommand curl {0}'.format(self.post_web_root)
++        low = {'client': 'ssh',
++               'tgt': 'localhost',
++               'fun': fun,
++               'raw_shell': True}
++
++        ret = None
++        with self.assertRaises(EauthAuthenticationError) as excinfo:
++            ret = self.netapi.run(low)
++
++        self.assertEqual(self.post_web_handler.received_requests, [])
++        self.assertEqual(ret, None)
++
++    def test_ssh_unauthenticated_raw_shell_touch(self):
++
++        badfile = os.path.join(TMP, 'badfile.txt')
++        fun = '-o ProxyCommand touch {0}'.format(badfile)
++        low = {'client': 'ssh',
++               'tgt': 'localhost',
++               'fun': fun,
++               'raw_shell': True}
++
++        ret = None
++        with self.assertRaises(EauthAuthenticationError) as excinfo:
++            ret = self.netapi.run(low)
++
++        self.assertEqual(ret, None)
++        self.assertFalse(os.path.exists('badfile.txt'))
++
++    def test_ssh_authenticated_raw_shell_disabled(self):
++
++        badfile = os.path.join(TMP, 'badfile.txt')
++        fun = '-o ProxyCommand touch {0}'.format(badfile)
++        low = {'client': 'ssh',
++               'tgt': 'localhost',
++               'fun': fun,
++               'raw_shell': True}
++
++        low.update(self.eauth_creds)
++
++        ret = None
++        with patch.dict(self.netapi.opts,
++                        {'netapi_allow_raw_shell': False}):
++            with self.assertRaises(EauthAuthenticationError) as excinfo:
++                ret = self.netapi.run(low)
++
++        self.assertEqual(ret, None)
++        self.assertFalse(os.path.exists('badfile.txt'))
+diff --git a/tests/support/helpers.py b/tests/support/helpers.py
+index 712899a..153000b 100644
+--- a/tests/support/helpers.py
++++ b/tests/support/helpers.py
+@@ -1582,6 +1582,25 @@ class Webserver(object):
+         self.server_thread.join()
+ 
+ 
++class SaveRequestsPostHandler(tornado.web.RequestHandler):
++    '''
++    Mirror a POST body back to the client.
++    '''
++    received_requests = []
++
++    def post(self, *args):  # pylint: disable=arguments-differ
++        '''
++        Handle the post
++        '''
++        self.received_requests.append(self.request)
++
++    def data_received(self):  # pylint: disable=arguments-differ
++        '''
++        Streaming not used for testing
++        '''
++        raise NotImplementedError()
++
++
+ def win32_kill_process_tree(pid, sig=signal.SIGTERM, include_parent=True,
+         timeout=None, on_terminate=None):
+     '''
diff --git a/debian/patches/series b/debian/patches/series
index 2b610d8fbf..344a70c311 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -1,3 +1,4 @@
+CVE-2019-17361-various-netapi-fixes-and-tests.patch
 gitfs-Fix-use-of-deprecated-pygit2-function.patch
 Fix-retrieve-systemd-version-using-regex.patch
 Fix-edge-case-when-minion-ID-is-a-16-character-string.patch
-- 
2.26.2

>From 055c36af0c5e3860ffa8b2e1c9463f380dedff92 Mon Sep 17 00:00:00 2001
From: Simon McVittie <s...@collabora.com>
Date: Mon, 4 May 2020 13:43:04 +0100
Subject: [PATCH 2/4] Backport upstream patches for CVE-2020-11651,
 CVE-2020-11652

Closes: #959684
Signed-off-by: Simon McVittie <s...@collabora.com>
---
 ...-log-messages-to-hardening-salt-docs.patch |  38 ++
 debian/patches/Fix-CVE-2020-11651.patch       | 310 +++++++++++
 debian/patches/Fix-CVE-2020-11652.patch       | 484 ++++++++++++++++++
 debian/patches/series                         |   3 +
 4 files changed, 835 insertions(+)
 create mode 100644 debian/patches/Add-note-about-log-messages-to-hardening-salt-docs.patch
 create mode 100644 debian/patches/Fix-CVE-2020-11651.patch
 create mode 100644 debian/patches/Fix-CVE-2020-11652.patch

diff --git a/debian/patches/Add-note-about-log-messages-to-hardening-salt-docs.patch b/debian/patches/Add-note-about-log-messages-to-hardening-salt-docs.patch
new file mode 100644
index 0000000000..fbbcdd3b89
--- /dev/null
+++ b/debian/patches/Add-note-about-log-messages-to-hardening-salt-docs.patch
@@ -0,0 +1,38 @@
+From: "Daniel A. Wozniak" <dwozn...@saltstack.com>
+Date: Mon, 13 Apr 2020 07:01:07 +0000
+Subject: Add note about log messages to hardening salt docs
+
+(cherry picked from commit de8c777be62449b505af71bb474f9ee18ddf201c)
+---
+ doc/topics/hardening.rst | 4 ++++
+ salt/master.py           | 2 +-
+ 2 files changed, 5 insertions(+), 1 deletion(-)
+
+diff --git a/doc/topics/hardening.rst b/doc/topics/hardening.rst
+index c645b84..569ad65 100644
+--- a/doc/topics/hardening.rst
++++ b/doc/topics/hardening.rst
+@@ -57,6 +57,10 @@ Salt hardening tips
+   particularly sensitive minions. There is also :ref:`salt-ssh` or the
+   :mod:`modules.sudo <salt.modules.sudo>` if you need to further restrict
+   a minion.
++- Monitor specific security releated log messages. Salt ``salt-master`` logs
++  attempts to access methods which are not exposed to network clients. These log
++  messages are logged at the ``error`` log level and start with ``Requested
++  method not exposed``.
+ 
+ .. _salt-users: https://groups.google.com/forum/#!forum/salt-users
+ .. _salt-announce: https://groups.google.com/forum/#!forum/salt-announce
+diff --git a/salt/master.py b/salt/master.py
+index 1999e74..d51af34 100644
+--- a/salt/master.py
++++ b/salt/master.py
+@@ -1116,7 +1116,7 @@ class TransportMethods(object):
+             try:
+                 return getattr(self, name)
+             except AttributeError:
+-                log.error("Expose method not found: %s", name)
++                log.error("Requested method not exposed: %s", name)
+         else:
+             log.error("Requested method not exposed: %s", name)
+ 
diff --git a/debian/patches/Fix-CVE-2020-11651.patch b/debian/patches/Fix-CVE-2020-11651.patch
new file mode 100644
index 0000000000..3dc3fec781
--- /dev/null
+++ b/debian/patches/Fix-CVE-2020-11651.patch
@@ -0,0 +1,310 @@
+From: "Daniel A. Wozniak" <dwozn...@saltstack.com>
+Date: Mon, 13 Apr 2020 06:41:04 +0000
+Subject: Fix CVE-2020-11651
+
+Resolve issue which allows access to un-intended methods in the
+ClearFuncs class of the salt-master process
+
+(cherry picked from commit f47e4856497231eb672da2ce0df3e641581d47e6)
+
+Origin: upstream, 2019.2.4, commit:f47e4856497231eb672da2ce0df3e641581d47e6
+Bug: CVE-2020-11651
+Bug-Debian: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=959684
+---
+ salt/master.py                               |  58 ++++++++++--
+ tests/integration/master/test_clear_funcs.py | 128 +++++++++++++++++++++++++++
+ tests/unit/test_master.py                    |  25 ++++++
+ tests/whitelist.txt                          |   1 +
+ 4 files changed, 203 insertions(+), 9 deletions(-)
+ create mode 100644 tests/integration/master/test_clear_funcs.py
+
+diff --git a/salt/master.py b/salt/master.py
+index 30983c3..1999e74 100644
+--- a/salt/master.py
++++ b/salt/master.py
+@@ -1050,12 +1050,13 @@ class MWorker(salt.utils.process.SignalHandlingMultiprocessingProcess):
+         '''
+         log.trace('Clear payload received with command %s', load['cmd'])
+         cmd = load['cmd']
+-        if cmd.startswith('__'):
+-            return False
++        method = self.clear_funcs.get_method(cmd)
++        if not method:
++            return {}, {'fun': 'send_clear'}
+         if self.opts['master_stats']:
+             start = time.time()
+             self.stats[cmd]['runs'] += 1
+-        ret = getattr(self.clear_funcs, cmd)(load), {'fun': 'send_clear'}
++        ret = method(load), {'fun': 'send_clear'}
+         if self.opts['master_stats']:
+             self._post_stats(start, cmd)
+         return ret
+@@ -1073,8 +1074,9 @@ class MWorker(salt.utils.process.SignalHandlingMultiprocessingProcess):
+             return {}
+         cmd = data['cmd']
+         log.trace('AES payload received with command %s', data['cmd'])
+-        if cmd.startswith('__'):
+-            return False
++        method = self.aes_funcs.get_method(cmd)
++        if not method:
++            return {}, {'fun': 'send'}
+         if self.opts['master_stats']:
+             start = time.time()
+             self.stats[cmd]['runs'] += 1
+@@ -1097,13 +1099,44 @@ class MWorker(salt.utils.process.SignalHandlingMultiprocessingProcess):
+         self.__bind()
+ 
+ 
++class TransportMethods(object):
++    '''
++    Expose methods to the transport layer, methods with their names found in
++    the class attribute 'expose_methods' will be exposed to the transport layer
++    via 'get_method'.
++    '''
++
++    expose_methods = ()
++
++    def get_method(self, name):
++        '''
++        Get a method which should be exposed to the transport layer
++        '''
++        if name in self.expose_methods:
++            try:
++                return getattr(self, name)
++            except AttributeError:
++                log.error("Expose method not found: %s", name)
++        else:
++            log.error("Requested method not exposed: %s", name)
++
++
+ # TODO: rename? No longer tied to "AES", just "encrypted" or "private" requests
+-class AESFuncs(object):
++class AESFuncs(TransportMethods):
+     '''
+     Set up functions that are available when the load is encrypted with AES
+     '''
+-    # The AES Functions:
+-    #
++
++    expose_methods = (
++        'verify_minion', '_master_tops', '_ext_nodes', '_master_opts',
++        '_mine_get', '_mine', '_mine_delete', '_mine_flush', '_file_recv',
++        '_pillar', '_minion_event', '_handle_minion_event', '_return',
++        '_syndic_return', '_minion_runner', 'pub_ret', 'minion_pub',
++        'minion_publish', 'revoke_auth', 'run_func', '_serve_file',
++        '_file_find', '_file_hash', '_file_find_and_stat', '_file_list',
++        '_file_list_emptydirs', '_dir_list', '_symlink_list', '_file_envs',
++    )
++
+     def __init__(self, opts):
+         '''
+         Create a new AESFuncs
+@@ -1817,11 +1850,18 @@ class AESFuncs(object):
+         return ret, {'fun': 'send'}
+ 
+ 
+-class ClearFuncs(object):
++class ClearFuncs(TransportMethods):
+     '''
+     Set up functions that are safe to execute when commands sent to the master
+     without encryption and authentication
+     '''
++
++    # These methods will be exposed to the transport layer by
++    # MWorker._handle_clear
++    expose_methods = (
++        'ping', 'publish', 'get_token', 'mk_token', 'wheel', 'runner',
++    )
++
+     # The ClearFuncs object encapsulates the functions that can be executed in
+     # the clear:
+     # publish (The publish from the LocalClient)
+diff --git a/tests/integration/master/test_clear_funcs.py b/tests/integration/master/test_clear_funcs.py
+new file mode 100644
+index 0000000..5fe3bf9
+--- /dev/null
++++ b/tests/integration/master/test_clear_funcs.py
+@@ -0,0 +1,128 @@
++# -*- coding: utf-8 -*-
++from __future__ import absolute_import, print_function, unicode_literals
++import getpass
++import os
++import tempfile
++import time
++
++import salt.master
++import salt.transport.client
++import salt.utils.platform
++import salt.utils.files
++import salt.utils.user
++
++from tests.support.case import TestCase
++from tests.support.mixins import AdaptedConfigurationTestCaseMixin
++from tests.support.runtests import RUNTIME_VARS
++
++
++def keyuser():
++    user = salt.utils.user.get_specific_user()
++    if user.startswith('sudo_'):
++        user = user[5:].replace('\\', '_')
++    return user
++
++
++class ClearFuncsAuthTestCase(TestCase):
++
++    def test_auth_info_not_allowed(self):
++        assert hasattr(salt.master.ClearFuncs, '_prep_auth_info')
++        master = '127.0.0.1'
++        ret_port = 64506
++        user = getpass.getuser()
++        keyfile = '.{}_key'.format(user)
++
++        keypath = os.path.join(RUNTIME_VARS.TMP, 'rootdir', 'cache', keyfile)
++
++        with salt.utils.files.fopen(keypath) as keyfd:
++            key = keyfd.read()
++
++        minion_config = {
++            'transport': 'zeromq',
++            'pki_dir': '/tmp',
++            'id': 'root',
++            'master_ip': master,
++            'master_port': ret_port,
++            'auth_timeout': 5,
++            'auth_tries': 1,
++            'master_uri': 'tcp://{0}:{1}'.format(master, ret_port)
++        }
++
++        clear_channel = salt.transport.client.ReqChannel.factory(
++            minion_config, crypt='clear')
++
++        msg = {'cmd': '_prep_auth_info'}
++        rets = clear_channel.send(msg, timeout=15)
++        ret_key = None
++        for ret in rets:
++            try:
++                ret_key = ret[user]
++                break
++            except (TypeError, KeyError):
++                pass
++        assert ret_key != key, 'Able to retrieve user key'
++
++
++class ClearFuncsPubTestCase(TestCase):
++
++    def setUp(self):
++        self.master = '127.0.0.1'
++        self.ret_port = 64506
++        self.tmpfile = os.path.join(tempfile.mkdtemp(), 'evil_file')
++        self.master_opts = AdaptedConfigurationTestCaseMixin.get_config('master')
++
++    def tearDown(self):
++        try:
++            os.remove(self.tmpfile + 'x')
++        except OSError:
++            pass
++        delattr(self, 'master')
++        delattr(self, 'ret_port')
++        delattr(self, 'tmpfile')
++
++    def test_pub_not_allowed(self):
++        assert hasattr(salt.master.ClearFuncs, '_send_pub')
++        assert not os.path.exists(self.tmpfile)
++        minion_config = {
++            'transport': 'zeromq',
++            'pki_dir': '/tmp',
++            'id': 'root',
++            'master_ip': self.master,
++            'master_port': self.ret_port,
++            'auth_timeout': 5,
++            'auth_tries': 1,
++            'master_uri': 'tcp://{0}:{1}'.format(self.master, self.ret_port),
++        }
++        clear_channel = salt.transport.client.ReqChannel.factory(
++            minion_config, crypt='clear')
++        jid = '202003100000000001'
++        msg = {
++            'cmd': '_send_pub',
++            'fun': 'file.write',
++            'jid': jid,
++            'arg': [self.tmpfile, 'evil contents'],
++            'kwargs': {'show_jid': False, 'show_timeout': False},
++            'ret': '',
++            'tgt': 'minion',
++            'tgt_type': 'glob',
++            'user': 'root'
++        }
++        eventbus = salt.utils.event.get_event(
++            'master',
++            sock_dir=self.master_opts['sock_dir'],
++            transport=self.master_opts['transport'],
++            opts=self.master_opts)
++        ret = clear_channel.send(msg, timeout=15)
++        if salt.utils.platform.is_windows():
++            time.sleep(30)
++            timeout = 30
++        else:
++            timeout = 5
++        ret_evt = None
++        start = time.time()
++        while time.time() - start <= timeout:
++            raw = eventbus.get_event(timeout, auto_reconnect=True)
++            if raw and 'jid' in raw and raw['jid'] == jid:
++                ret_evt = raw
++                break
++        assert not os.path.exists(self.tmpfile), 'Evil file created'
+diff --git a/tests/unit/test_master.py b/tests/unit/test_master.py
+index b7394ff..c730f61 100644
+--- a/tests/unit/test_master.py
++++ b/tests/unit/test_master.py
+@@ -15,6 +15,24 @@ from tests.support.mock import (
+ )
+ 
+ 
++class TransportMethodsTest(TestCase):
++
++    def test_transport_methods(self):
++
++        class Foo(salt.master.TransportMethods):
++            expose_methods = ['bar']
++
++            def bar(self):
++                pass
++
++            def bang(self):
++                pass
++
++        foo = Foo()
++        assert foo.get_method('bar') is not None
++        assert foo.get_method('bang') is None
++
++
+ class ClearFuncsTestCase(TestCase):
+     '''
+     TestCase for salt.master.ClearFuncs class
+@@ -24,6 +42,13 @@ class ClearFuncsTestCase(TestCase):
+         opts = salt.config.master_config(None)
+         self.clear_funcs = salt.master.ClearFuncs(opts, {})
+ 
++    def tearDown(self):
++        del self.clear_funcs
++
++    def test_get_method(self):
++        assert getattr(self.clear_funcs, '_send_pub', None) is not None
++        assert self.clear_funcs.get_method('_send_pub') is None
++
+     # runner tests
+ 
+     def test_runner_token_not_authenticated(self):
+diff --git a/tests/whitelist.txt b/tests/whitelist.txt
+index d33a719..4c712bf 100644
+--- a/tests/whitelist.txt
++++ b/tests/whitelist.txt
+@@ -9,6 +9,7 @@ integration.doc.test_man
+ integration.grains.test_core
+ integration.loader.test_ext_grains
+ integration.loader.test_ext_modules
++integration.master.test_clear_funcs
+ integration.minion.test_blackout
+ integration.minion.test_pillar
+ integration.minion.test_timeout
diff --git a/debian/patches/Fix-CVE-2020-11652.patch b/debian/patches/Fix-CVE-2020-11652.patch
new file mode 100644
index 0000000000..f1f367e70c
--- /dev/null
+++ b/debian/patches/Fix-CVE-2020-11652.patch
@@ -0,0 +1,484 @@
+From: "Daniel A. Wozniak" <dwozn...@saltstack.com>
+Date: Mon, 13 Apr 2020 06:44:58 +0000
+Subject: Fix CVE-2020-11652
+
+Sanitize paths in ClearFuncs methods provided by salt-master. This
+ensures we do not allow access to un-intended files and directories.
+
+(cherry picked from commit 7bd0ab195fbec4f34523dad11149f741c154e2b7)
+
+Origin: upstream, 2019.2.4, commit:7bd0ab195fbec4f34523dad11149f741c154e2b7
+Bug: CVE-2020-11652
+Bug-Debian: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=959684
+---
+ salt/tokens/localfs.py                       |   3 +
+ salt/utils/verify.py                         |  57 ++++++++-
+ salt/wheel/config.py                         |   8 +-
+ salt/wheel/file_roots.py                     |   7 +-
+ tests/integration/master/test_clear_funcs.py | 182 +++++++++++++++++++++++++++
+ tests/unit/test_module_names.py              |   1 +
+ tests/unit/utils/test_verify.py              |  80 ++++++++++++
+ 7 files changed, 331 insertions(+), 7 deletions(-)
+
+diff --git a/salt/tokens/localfs.py b/salt/tokens/localfs.py
+index 021bdb9..590a096 100644
+--- a/salt/tokens/localfs.py
++++ b/salt/tokens/localfs.py
+@@ -12,6 +12,7 @@ import logging
+ 
+ import salt.utils.files
+ import salt.utils.path
++import salt.utils.verify
+ import salt.payload
+ 
+ from salt.ext import six
+@@ -59,6 +60,8 @@ def get_token(opts, tok):
+     :returns: Token data if successful. Empty dict if failed.
+     '''
+     t_path = os.path.join(opts['token_dir'], tok)
++    if not salt.utils.verify.clean_path(opts['token_dir'], t_path):
++        return {}
+     if not os.path.isfile(t_path):
+         return {}
+     serial = salt.payload.Serial(opts)
+diff --git a/salt/utils/verify.py b/salt/utils/verify.py
+index 5eb8481..f289b65 100644
+--- a/salt/utils/verify.py
++++ b/salt/utils/verify.py
+@@ -31,6 +31,7 @@ import salt.utils.files
+ import salt.utils.path
+ import salt.utils.platform
+ import salt.utils.user
++import salt.ext.six
+ 
+ log = logging.getLogger(__name__)
+ 
+@@ -472,23 +473,69 @@ def check_max_open_files(opts):
+     log.log(level=level, msg=msg)
+ 
+ 
++def _realpath_darwin(path):
++    base = ''
++    for part in path.split(os.path.sep)[1:]:
++        if base != '':
++            if os.path.islink(os.path.sep.join([base, part])):
++                base = os.readlink(os.path.sep.join([base, part]))
++            else:
++                base = os.path.abspath(os.path.sep.join([base, part]))
++        else:
++            base = os.path.abspath(os.path.sep.join([base, part]))
++    return base
++
++
++def _realpath_windows(path):
++    base = ''
++    for part in path.split(os.path.sep):
++        if base != '':
++            try:
++                part = os.readlink(os.path.sep.join([base, part]))
++                base = os.path.abspath(part)
++            except OSError:
++                base = os.path.abspath(os.path.sep.join([base, part]))
++        else:
++            base = part
++    return base
++
++
++def _realpath(path):
++    '''
++    Cross platform realpath method. On Windows when python 3, this method
++    uses the os.readlink method to resolve any filesystem links. On Windows
++    when python 2, this method is a no-op. All other platforms and version use
++    os.realpath
++    '''
++    if salt.utils.platform.is_darwin():
++        return _realpath_darwin(path)
++    elif salt.utils.platform.is_windows():
++        if salt.ext.six.PY3:
++            return _realpath_windows(path)
++        else:
++            return path
++    return os.path.realpath(path)
++
++
+ def clean_path(root, path, subdir=False):
+     '''
+     Accepts the root the path needs to be under and verifies that the path is
+     under said root. Pass in subdir=True if the path can result in a
+     subdirectory of the root instead of having to reside directly in the root
+     '''
+-    if not os.path.isabs(root):
++    real_root = _realpath(root)
++    if not os.path.isabs(real_root):
+         return ''
+     if not os.path.isabs(path):
+         path = os.path.join(root, path)
+     path = os.path.normpath(path)
++    real_path = _realpath(path)
+     if subdir:
+-        if path.startswith(root):
+-            return path
++        if real_path.startswith(real_root):
++            return real_path
+     else:
+-        if os.path.dirname(path) == os.path.normpath(root):
+-            return path
++        if os.path.dirname(real_path) == os.path.normpath(real_root):
++            return real_path
+     return ''
+ 
+ 
+diff --git a/salt/wheel/config.py b/salt/wheel/config.py
+index a8a93c5..3984444 100644
+--- a/salt/wheel/config.py
++++ b/salt/wheel/config.py
+@@ -75,13 +75,19 @@ def update_config(file_name, yaml_contents):
+     dir_path = os.path.join(__opts__['config_dir'],
+                             os.path.dirname(__opts__['default_include']))
+     try:
+-        yaml_out = salt.utils.yaml.safe_dump(yaml_contents, default_flow_style=False)
++        yaml_out = salt.utils.yaml.safe_dump(
++            yaml_contents,
++            default_flow_style=False,
++        )
+ 
+         if not os.path.exists(dir_path):
+             log.debug('Creating directory %s', dir_path)
+             os.makedirs(dir_path, 0o755)
+ 
+         file_path = os.path.join(dir_path, file_name)
++        if not salt.utils.verify.clean_path(dir_path, file_path):
++            return 'Invalid path'
++
+         with salt.utils.files.fopen(file_path, 'w') as fp_:
+             fp_.write(yaml_out)
+ 
+diff --git a/salt/wheel/file_roots.py b/salt/wheel/file_roots.py
+index 02cc8c5..ad42335 100644
+--- a/salt/wheel/file_roots.py
++++ b/salt/wheel/file_roots.py
+@@ -25,6 +25,8 @@ def find(path, saltenv='base'):
+         return ret
+     for root in __opts__['file_roots'][saltenv]:
+         full = os.path.join(root, path)
++        if not salt.utils.verify.clean_path(root, full):
++            continue
+         if os.path.isfile(full):
+             # Add it to the dict
+             with salt.utils.files.fopen(full, 'rb') as fp_:
+@@ -107,7 +109,10 @@ def write(data, path, saltenv='base', index=0):
+     if os.path.isabs(path):
+         return ('The path passed in {0} is not relative to the environment '
+                 '{1}').format(path, saltenv)
+-    dest = os.path.join(__opts__['file_roots'][saltenv][index], path)
++    root = __opts__['file_roots'][saltenv][index]
++    dest = os.path.join(root, path)
++    if not salt.utils.verify.clean_path(root, dest, subdir=True):
++        return 'Invalid path: {}'.format(path)
+     dest_dir = os.path.dirname(dest)
+     if not os.path.isdir(dest_dir):
+         os.makedirs(dest_dir)
+diff --git a/tests/integration/master/test_clear_funcs.py b/tests/integration/master/test_clear_funcs.py
+index 5fe3bf9..4abb257 100644
+--- a/tests/integration/master/test_clear_funcs.py
++++ b/tests/integration/master/test_clear_funcs.py
+@@ -126,3 +126,185 @@ class ClearFuncsPubTestCase(TestCase):
+                 ret_evt = raw
+                 break
+         assert not os.path.exists(self.tmpfile), 'Evil file created'
++
++
++class ClearFuncsConfigTest(TestCase):
++
++    def setUp(self):
++        master_opts = AdaptedConfigurationTestCaseMixin.get_config('master')
++        self.conf_dir = os.path.dirname(master_opts['conf_file'])
++        master = '127.0.0.1'
++        ret_port = 64506
++        user = keyuser()
++        keyfile = '.{}_key'.format(user)
++        keypath = os.path.join(RUNTIME_VARS.TMP, 'rootdir', 'cache', keyfile)
++
++        with salt.utils.files.fopen(keypath) as keyfd:
++            self.key = keyfd.read()
++
++        self.minion_config = {
++            'transport': 'zeromq',
++            'pki_dir': '/tmp',
++            'id': 'root',
++            'master_ip': master,
++            'master_port': ret_port,
++            'auth_timeout': 5,
++            'auth_tries': 1,
++            'master_uri': 'tcp://{0}:{1}'.format(master, ret_port)
++        }
++
++    def tearDown(self):
++        try:
++            os.remove(os.path.join(self.conf_dir, 'evil.conf'))
++        except OSError:
++            pass
++        delattr(self, 'conf_dir')
++        delattr(self, 'key')
++        delattr(self, 'minion_config')
++
++    def test_clearfuncs_config(self):
++        clear_channel = salt.transport.client.ReqChannel.factory(
++            self.minion_config, crypt='clear')
++
++        msg = {
++           'key': self.key,
++           'cmd': 'wheel',
++           'fun': 'config.update_config',
++           'file_name': '../evil',
++           'yaml_contents': 'win',
++        }
++        ret = clear_channel.send(msg, timeout=5)
++        assert not os.path.exists(os.path.join(self.conf_dir, 'evil.conf')), \
++            'Wrote file via directory traversal'
++
++
++class ClearFuncsFileRoots(TestCase):
++
++    def setUp(self):
++        self.master_opts = AdaptedConfigurationTestCaseMixin.get_config('master')
++        self.target_dir = os.path.dirname(
++            self.master_opts['file_roots']['base'][0]
++        )
++        master = '127.0.0.1'
++        ret_port = 64506
++        user = keyuser()
++        self.keyfile = '.{}_key'.format(user)
++        keypath = os.path.join(RUNTIME_VARS.TMP, 'rootdir', 'cache', self.keyfile)
++
++        with salt.utils.files.fopen(keypath) as keyfd:
++            self.key = keyfd.read()
++
++        self.minion_config = {
++            'transport': 'zeromq',
++            'pki_dir': '/tmp',
++            'id': 'root',
++            'master_ip': master,
++            'master_port': ret_port,
++            'auth_timeout': 5,
++            'auth_tries': 1,
++            'master_uri': 'tcp://{0}:{1}'.format(master, ret_port)
++        }
++
++    def tearDown(self):
++        try:
++            os.remove(os.path.join(self.target_dir, 'pwn.txt'))
++        except OSError:
++            pass
++        delattr(self, 'master_opts')
++        delattr(self, 'target_dir')
++        delattr(self, 'keyfile')
++        delattr(self, 'key')
++        delattr(self, 'minion_config')
++
++    def test_fileroots_write(self):
++        clear_channel = salt.transport.client.ReqChannel.factory(
++            self.minion_config, crypt='clear')
++
++        msg = {
++            'key': self.key,
++            'cmd': 'wheel',
++            'fun': 'file_roots.write',
++            'data': 'win',
++            'path': os.path.join('..', 'pwn.txt'),
++            'saltenv': 'base',
++        }
++        ret = clear_channel.send(msg, timeout=5)
++        assert not os.path.exists(os.path.join(self.target_dir, 'pwn.txt')), \
++            'Wrote file via directory traversal'
++
++    def test_fileroots_read(self):
++        rootdir = self.master_opts['root_dir']
++        fileroot = self.master_opts['file_roots']['base'][0]
++        # If this asserion fails the test may need to be re-written
++        assert os.path.dirname(os.path.dirname(rootdir)) == os.path.dirname(fileroot)
++        clear_channel = salt.transport.client.ReqChannel.factory(
++            self.minion_config, crypt='clear')
++        readpath = os.path.join(
++            '..',
++            'salt-tests-tmpdir',
++            'rootdir',
++            'cache',
++            self.keyfile,
++        )
++        msg = {
++            'key': self.key,
++            'cmd': 'wheel',
++            'fun': 'file_roots.read',
++            'path': os.path.join(
++                '..',
++                'salt-tests-tmpdir',
++                'rootdir',
++                'cache',
++                self.keyfile,
++            ),
++            'saltenv': 'base',
++        }
++
++        ret = clear_channel.send(msg, timeout=5)
++        try:
++            # When vulnerable this assertion will fail.
++            assert list(ret['data']['return'][0].items())[0][1] != self.key, \
++                'Read file via directory traversal'
++        except IndexError:
++            pass
++        # If the vulnerability is fixed, no data will be returned.
++        assert ret['data']['return'] == []
++
++
++class ClearFuncsTokenTest(TestCase):
++
++    def setUp(self):
++        self.master_opts = AdaptedConfigurationTestCaseMixin.get_config('master')
++        master = '127.0.0.1'
++        ret_port = 64506
++        self.minion_config = {
++            'transport': 'zeromq',
++            'pki_dir': '/tmp',
++            'id': 'root',
++            'master_ip': master,
++            'master_port': ret_port,
++            'auth_timeout': 5,
++            'auth_tries': 1,
++            'master_uri': 'tcp://{0}:{1}'.format(master, ret_port)
++        }
++
++    def tearDown(self):
++        delattr(self, 'master_opts')
++        delattr(self, 'minion_config')
++
++    def test_token(self):
++        tokensdir = os.path.join(
++            self.master_opts['root_dir'],
++            self.master_opts['cachedir'],
++            'tokens'
++        )
++        assert os.path.exists(tokensdir), tokensdir
++        clear_channel = salt.transport.client.ReqChannel.factory(
++            self.minion_config, crypt='clear')
++        msg = {
++            'arg': [],
++            'cmd': 'get_token',
++            'token': os.path.join('..', 'minions', 'minion', 'data.p'),
++        }
++        ret = clear_channel.send(msg, timeout=5)
++        assert 'pillar' not in ret, 'Read minion data via directory traversal'
+diff --git a/tests/unit/test_module_names.py b/tests/unit/test_module_names.py
+index 0edfe99..3d43e25 100644
+--- a/tests/unit/test_module_names.py
++++ b/tests/unit/test_module_names.py
+@@ -127,6 +127,7 @@ class BadTestModuleNamesTestCase(TestCase):
+             'integration.loader.test_ext_grains',
+             'integration.loader.test_ext_modules',
+             'integration.logging.test_jid_logging',
++            'integration.master.test_clear_funcs',
+             'integration.minion.test_blackout',
+             'integration.minion.test_pillar',
+             'integration.minion.test_timeout',
+diff --git a/tests/unit/utils/test_verify.py b/tests/unit/utils/test_verify.py
+index fb801da..b263a44 100644
+--- a/tests/unit/utils/test_verify.py
++++ b/tests/unit/utils/test_verify.py
+@@ -12,6 +12,7 @@ import stat
+ import shutil
+ import tempfile
+ import socket
++import ctypes
+ 
+ # Import third party libs
+ if sys.platform.startswith('win'):
+@@ -44,6 +45,7 @@ from salt.utils.verify import (
+     valid_id,
+     log,
+     verify_log,
++    clean_path
+ )
+ 
+ # Import 3rd-party libs
+@@ -284,3 +286,81 @@ class TestVerify(TestCase):
+         with patch.object(log, 'warning', mock_info):
+             verify_log({'log_level': 'info'})
+             self.assertTrue(mock_info.call_count == 0)
++
++
++class TestCleanPath(TestCase):
++    '''
++    salt.utils.clean_path works as expected
++    '''
++
++    def setUp(self):
++        self.tmpdir = tempfile.mkdtemp()
++
++    def tearDown(self):
++        shutil.rmtree(self.tmpdir)
++
++    def test_clean_path_valid(self):
++        path_a = os.path.join(self.tmpdir, 'foo')
++        path_b = os.path.join(self.tmpdir, 'foo', 'bar')
++        assert clean_path(path_a, path_b) == path_b
++
++    def test_clean_path_invalid(self):
++        path_a = os.path.join(self.tmpdir, 'foo')
++        path_b = os.path.join(self.tmpdir, 'baz', 'bar')
++        assert clean_path(path_a, path_b) == ''
++
++
++__CSL = None
++
++
++def symlink(source, link_name):
++    '''
++    symlink(source, link_name) Creates a symbolic link pointing to source named
++    link_name
++    '''
++    global __CSL
++    if __CSL is None:
++        csl = ctypes.windll.kernel32.CreateSymbolicLinkW
++        csl.argtypes = (ctypes.c_wchar_p, ctypes.c_wchar_p, ctypes.c_uint32)
++        csl.restype = ctypes.c_ubyte
++        __CSL = csl
++    flags = 0
++    if source is not None and os.path.isdir(source):
++        flags = 1
++    if __CSL(link_name, source, flags) == 0:
++        raise ctypes.WinError()
++
++
++@skipIf(six.PY2 and salt.utils.platform.is_windows(), 'Skipped on windows py2')
++class TestCleanPathLink(TestCase):
++    '''
++    Ensure salt.utils.clean_path works with symlinked directories and files
++    '''
++
++    def setUp(self):
++        self.tmpdir = tempfile.mkdtemp()
++        self.to_path = os.path.join(self.tmpdir, 'linkto')
++        self.from_path = os.path.join(self.tmpdir, 'linkfrom')
++        if six.PY2 or salt.utils.platform.is_windows():
++            kwargs = {}
++        else:
++            kwargs = {'target_is_directory': True}
++        if salt.utils.platform.is_windows():
++            symlink(self.to_path, self.from_path, **kwargs)
++        else:
++            os.symlink(self.to_path, self.from_path, **kwargs)
++
++    def tearDown(self):
++        shutil.rmtree(self.tmpdir)
++
++    def test_clean_path_symlinked_src(self):
++        test_path = os.path.join(self.from_path, 'test')
++        expect_path = os.path.join(self.to_path, 'test')
++        ret = clean_path(self.from_path, test_path)
++        assert ret == expect_path, "{} is not {}".format(ret, expect_path)
++
++    def test_clean_path_symlinked_tgt(self):
++        test_path = os.path.join(self.to_path, 'test')
++        expect_path = os.path.join(self.to_path, 'test')
++        ret = clean_path(self.from_path, test_path)
++        assert ret == expect_path, "{} is not {}".format(ret, expect_path)
diff --git a/debian/patches/series b/debian/patches/series
index 344a70c311..d660309713 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -1,4 +1,7 @@
 CVE-2019-17361-various-netapi-fixes-and-tests.patch
+Fix-CVE-2020-11651.patch
+Fix-CVE-2020-11652.patch
+Add-note-about-log-messages-to-hardening-salt-docs.patch
 gitfs-Fix-use-of-deprecated-pygit2-function.patch
 Fix-retrieve-systemd-version-using-regex.patch
 Fix-edge-case-when-minion-ID-is-a-16-character-string.patch
-- 
2.26.2

>From 7683ce8f69ac7d65b6c89d88b67a6c95062bc11c Mon Sep 17 00:00:00 2001
From: Simon McVittie <s...@collabora.com>
Date: Mon, 4 May 2020 13:43:43 +0100
Subject: [PATCH 3/4] Apply proposed regression fixes in salt.master

Related to CVE-2020-11651. Note that this has not yet been fixed
upstream, only in a collection of unofficial backports.

Signed-off-by: Simon McVittie <s...@collabora.com>
---
 ...Fix-typos-in-list-of-allowed-methods.patch | 28 +++++++++++++++++++
 debian/patches/series                         |  1 +
 2 files changed, 29 insertions(+)
 create mode 100644 debian/patches/Fix-typos-in-list-of-allowed-methods.patch

diff --git a/debian/patches/Fix-typos-in-list-of-allowed-methods.patch b/debian/patches/Fix-typos-in-list-of-allowed-methods.patch
new file mode 100644
index 0000000000..7a6b0675a0
--- /dev/null
+++ b/debian/patches/Fix-typos-in-list-of-allowed-methods.patch
@@ -0,0 +1,28 @@
+From: Rossen <ros...@rgp.io>
+Date: Mon, 4 May 2020 13:10:16 +0100
+Subject: Fix typos in list of allowed methods
+
+Bug: https://docs.saltstack.com/en/latest/topics/releases/3000.2.html#known-issue
+Bug: https://github.com/rossengeorgiev/salt-security-backports/issues/1
+Origin: https://github.com/rossengeorgiev/salt-security-backports/commit/3687cd5533b14ba143d3792be562957c6250eafc
+Origin: https://github.com/rossengeorgiev/salt-security-backports/commit/8e522057035eed4341b0659ba6fd19a9b79577a8
+---
+ salt/master.py | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/salt/master.py b/salt/master.py
+index d51af34..cc97506 100644
+--- a/salt/master.py
++++ b/salt/master.py
+@@ -1131,9 +1131,9 @@ class AESFuncs(TransportMethods):
+         'verify_minion', '_master_tops', '_ext_nodes', '_master_opts',
+         '_mine_get', '_mine', '_mine_delete', '_mine_flush', '_file_recv',
+         '_pillar', '_minion_event', '_handle_minion_event', '_return',
+-        '_syndic_return', '_minion_runner', 'pub_ret', 'minion_pub',
++        '_syndic_return', 'minion_runner', 'pub_ret', 'minion_pub',
+         'minion_publish', 'revoke_auth', 'run_func', '_serve_file',
+-        '_file_find', '_file_hash', '_file_find_and_stat', '_file_list',
++        '_file_find', '_file_hash', '_file_hash_and_stat', '_file_list',
+         '_file_list_emptydirs', '_dir_list', '_symlink_list', '_file_envs',
+     )
+ 
diff --git a/debian/patches/series b/debian/patches/series
index d660309713..dcaf1d8a0c 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -2,6 +2,7 @@ CVE-2019-17361-various-netapi-fixes-and-tests.patch
 Fix-CVE-2020-11651.patch
 Fix-CVE-2020-11652.patch
 Add-note-about-log-messages-to-hardening-salt-docs.patch
+Fix-typos-in-list-of-allowed-methods.patch
 gitfs-Fix-use-of-deprecated-pygit2-function.patch
 Fix-retrieve-systemd-version-using-regex.patch
 Fix-edge-case-when-minion-ID-is-a-16-character-string.patch
-- 
2.26.2

>From 8cde5e73c4eef2ca02cc627db6d580f26d9f1a35 Mon Sep 17 00:00:00 2001
From: Simon McVittie <s...@collabora.com>
Date: Mon, 4 May 2020 13:45:25 +0100
Subject: [PATCH 4/4] Update changelog

---
 debian/changelog | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/debian/changelog b/debian/changelog
index 9d0dfca34e..f8b09f598f 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,14 @@
+salt (2018.3.4+dfsg1-6local1) UNRELEASED; urgency=medium
+
+  * Local build
+  * Apply upstream patch for CVE-2019-17361 (Closes: #949222)
+  * Backport upstream patches for CVE-2020-11651, CVE-2020-11652
+    (Closes: #959684)
+  * Apply proposed regression fixes in salt.master.
+    Related to CVE-2020-11651.
+
+ -- Simon McVittie <s...@collabora.com>  Mon, 04 May 2020 13:45:04 +0100
+
 salt (2018.3.4+dfsg1-6) unstable; urgency=medium
 
   * Revert changes that were rejected by the release team:
-- 
2.26.2

Reply via email to