Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save remijouannet/56673a51cc8d32b862277f1fb0ca03aa to your computer and use it in GitHub Desktop.
Save remijouannet/56673a51cc8d32b862277f1fb0ca03aa to your computer and use it in GitHub Desktop.
patch for salt 2018.3.* CVE-2020-11651 CVE-2020-11652
diff --git a/salt/tokens/localfs.py b/salt/tokens/localfs.py
index 021bdb9e50..747f8eea1e 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
@@ -34,6 +35,7 @@ def mk_token(opts, tdata):
hash_type = getattr(hashlib, opts.get('hash_type', 'md5'))
tok = six.text_type(hash_type(os.urandom(512)).hexdigest())
t_path = os.path.join(opts['token_dir'], tok)
+ temp_t_path = '{}.tmp'.format(t_path)
while os.path.isfile(t_path):
tok = six.text_type(hash_type(os.urandom(512)).hexdigest())
t_path = os.path.join(opts['token_dir'], tok)
@@ -41,8 +43,9 @@ def mk_token(opts, tdata):
serial = salt.payload.Serial(opts)
try:
with salt.utils.files.set_umask(0o177):
- with salt.utils.files.fopen(t_path, 'w+b') as fp_:
+ with salt.utils.files.fopen(temp_t_path, 'w+b') as fp_:
fp_.write(serial.dumps(tdata))
+ os.rename(temp_t_path, t_path)
except (IOError, OSError):
log.warning(
'Authentication failure: can not write token file "%s".', t_path)
@@ -59,6 +62,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 5eb8481069..9b74fd797a 100644
--- a/salt/utils/verify.py
+++ b/salt/utils/verify.py
@@ -31,10 +31,12 @@ import salt.utils.files
import salt.utils.path
import salt.utils.platform
import salt.utils.user
+import salt.ext.six
log = logging.getLogger(__name__)
ROOT_DIR = 'c:\\salt' if salt.utils.platform.is_windows() else '/'
+DEFAULT_SCHEMES = ['tcp://', 'udp://', 'file://']
def zmq_version():
@@ -146,6 +148,28 @@ def verify_socket(interface, pub_port, ret_port):
return True
+def verify_logs_filter(files):
+ to_verify = []
+ for filename in files:
+ verify_file = True
+ for scheme in DEFAULT_SCHEMES:
+ if filename.startswith(scheme):
+ verify_file = False
+ break
+ if verify_file:
+ to_verify.append(filename)
+ return to_verify
+
+
+def verify_log_files(files, user):
+ '''
+ Verify the log files exist and are owned by the named user. Filenames that
+ begin with tcp:// and udp:// will be filtered out. Filenames that begin
+ with file:// are handled correctly
+ '''
+ return verify_files(verify_logs_filter(files), user)
+
+
def verify_files(files, user):
'''
Verify that the named files exist and are owned by the named user
@@ -472,23 +496,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 a8a93c53e5..3984444f8f 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 02cc8c5b32..ad42335734 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)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment