Created
May 4, 2020 13:42
-
-
Save remijouannet/56673a51cc8d32b862277f1fb0ca03aa to your computer and use it in GitHub Desktop.
patch for salt 2018.3.* CVE-2020-11651 CVE-2020-11652
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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