From 6674e065da73198863cbfb26d6574eea6b56501c Mon Sep 17 00:00:00 2001 From: Stan Lagun Date: Wed, 1 Nov 2017 00:11:48 -0700 Subject: [PATCH] Use secure path join Change-Id: If0eeef8b025b1f3be863728a8def81d944873ac5 Closes-bug: #1729214 --- .../cloudify_tosca_package.py | 7 ++-- murano/common/helpers/path.py | 33 +++++++++++++++++++ murano/packages/hot_package.py | 19 ++++++----- murano/packages/load_utils.py | 5 +-- murano/packages/mpl_package.py | 7 ++-- murano/packages/package.py | 10 +++--- murano/packages/package_base.py | 7 ++-- 7 files changed, 64 insertions(+), 24 deletions(-) create mode 100644 murano/common/helpers/path.py diff --git a/contrib/plugins/cloudify_plugin/murano_cloudify_plugin/cloudify_tosca_package.py b/contrib/plugins/cloudify_plugin/murano_cloudify_plugin/cloudify_tosca_package.py index 1e6ae2113..fe04e8fc8 100644 --- a/contrib/plugins/cloudify_plugin/murano_cloudify_plugin/cloudify_tosca_package.py +++ b/contrib/plugins/cloudify_plugin/murano_cloudify_plugin/cloudify_tosca_package.py @@ -10,10 +10,9 @@ # License for the specific language governing permissions and limitations # under the License. -import os - import yaml +from murano.common.helpers import path from murano.packages import exceptions from murano.packages import package_base @@ -136,9 +135,9 @@ class CloudifyToscaPackage(package_base.PackageBase): } def _get_inputs_outputs(self): - path = os.path.join( + entry_point_path = path.secure_join( self.source_directory, RESOURCES_DIR_NAME, self._entry_point) - with open(path) as blueprint: + with open(entry_point_path) as blueprint: data = yaml.safe_load(blueprint) return data.get('inputs') or {}, data.get('outputs') or {} diff --git a/murano/common/helpers/path.py b/murano/common/helpers/path.py new file mode 100644 index 000000000..927c34343 --- /dev/null +++ b/murano/common/helpers/path.py @@ -0,0 +1,33 @@ +# Copyright (c) 2017 Mirantis Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os.path + + +def secure_join(*parts): + """Secure version of os.path.join(*parts) + + Joins pathname components and ensures that with each join the result + is a subdirectory of the previous join + """ + new = prev = "" + for part in parts: + new = os.path.normpath(os.path.join(prev, part)) + if len(new) <= len(prev) or prev != "" and not new.startswith( + prev + os.path.sep): + raise ValueError('path {0} is not allowed {1}'.format( + os.path.join(*parts), parts)) + prev = new + return new diff --git a/murano/packages/hot_package.py b/murano/packages/hot_package.py index ea3ddabe9..de780f13e 100644 --- a/murano/packages/hot_package.py +++ b/murano/packages/hot_package.py @@ -19,6 +19,7 @@ import sys import six import yaml +from murano.common.helpers import path from murano.packages import exceptions from murano.packages import package_base @@ -76,7 +77,8 @@ class HotPackage(package_base.PackageBase): return self._translated_class, '' def _translate_class(self): - template_file = os.path.join(self._source_directory, 'template.yaml') + template_file = path.secure_join( + self._source_directory, 'template.yaml') if not os.path.isfile(template_file): raise exceptions.PackageClassLoadError( @@ -92,9 +94,8 @@ class HotPackage(package_base.PackageBase): 'Extends': 'io.murano.Application' } - hot_envs_path = os.path.join(self._source_directory, - RESOURCES_DIR_NAME, - HOT_ENV_DIR_NAME) + hot_envs_path = path.secure_join( + self._source_directory, RESOURCES_DIR_NAME, HOT_ENV_DIR_NAME) # if using hot environments, doing parameter validation with contracts # will overwrite the parameters in the hot environment. @@ -190,9 +191,8 @@ class HotPackage(package_base.PackageBase): @staticmethod def _translate_files(source_directory): - hot_files_path = os.path.join(source_directory, - RESOURCES_DIR_NAME, - HOT_FILES_DIR_NAME) + hot_files_path = path.secure_join( + source_directory, RESOURCES_DIR_NAME, HOT_FILES_DIR_NAME) return HotPackage._build_hot_resources(hot_files_path) @@ -202,7 +202,7 @@ class HotPackage(package_base.PackageBase): if os.path.isdir(basedir): for root, _, files in os.walk(os.path.abspath(basedir)): for f in files: - full_path = os.path.join(root, f) + full_path = path.secure_join(root, f) relative_path = os.path.relpath(full_path, basedir) result.append(relative_path) return result @@ -517,7 +517,8 @@ class HotPackage(package_base.PackageBase): return app def _translate_ui(self): - template_file = os.path.join(self._source_directory, 'template.yaml') + template_file = path.secure_join( + self._source_directory, 'template.yaml') if not os.path.isfile(template_file): raise exceptions.PackageClassLoadError( diff --git a/murano/packages/load_utils.py b/murano/packages/load_utils.py index 6b374123c..68357a62b 100644 --- a/murano/packages/load_utils.py +++ b/murano/packages/load_utils.py @@ -22,6 +22,7 @@ import zipfile import six import yaml +from murano.common.helpers import path from murano.common.plugins import package_types_loader import murano.packages.exceptions as e import murano.packages.hot_package @@ -76,14 +77,14 @@ def load_from_file(archive_path, target_dir=None, drop_dir=False): shutil.rmtree(target_dir) else: for f in os.listdir(target_dir): - os.unlink(os.path.join(target_dir, f)) + os.unlink(path.secure_join(target_dir, f)) def load_from_dir(source_directory, filename='manifest.yaml'): if not os.path.isdir(source_directory) or not os.path.exists( source_directory): raise e.PackageLoadError('Invalid package directory') - full_path = os.path.join(source_directory, filename) + full_path = path.secure_join(source_directory, filename) if not os.path.isfile(full_path): raise e.PackageLoadError('Unable to find package manifest') diff --git a/murano/packages/mpl_package.py b/murano/packages/mpl_package.py index 1c173e7cd..09f1d8228 100644 --- a/murano/packages/mpl_package.py +++ b/murano/packages/mpl_package.py @@ -14,6 +14,7 @@ import os +from murano.common.helpers import path from murano.packages import exceptions from murano.packages import package_base @@ -34,7 +35,8 @@ class MuranoPlPackage(package_base.PackageBase): @property def ui(self): - full_path = os.path.join(self._source_directory, 'UI', self._ui_file) + full_path = path.secure_join( + self._source_directory, 'UI', self._ui_file) if not os.path.isfile(full_path): return None with open(full_path, 'rb') as stream: @@ -49,7 +51,8 @@ class MuranoPlPackage(package_base.PackageBase): raise exceptions.PackageClassLoadError( name, 'Class not defined in package ' + self.full_name) def_file = self._classes[name] - full_path = os.path.join(self._source_directory, 'Classes', def_file) + full_path = path.secure_join( + self._source_directory, 'Classes', def_file) if not os.path.isfile(full_path): raise exceptions.PackageClassLoadError( name, 'File with class definition not found') diff --git a/murano/packages/package.py b/murano/packages/package.py index 057a3f3b3..40fb35032 100644 --- a/murano/packages/package.py +++ b/murano/packages/package.py @@ -20,6 +20,8 @@ import zipfile import six +from murano.common.helpers import path + class PackageType(object): Library = 'Library' @@ -114,11 +116,11 @@ class Package(object): raise NotImplementedError() -def _zip_dir(path, zip_file): - for root, _, files in os.walk(path): +def _zip_dir(base, zip_file): + for root, _, files in os.walk(base): for f in files: - abs_path = os.path.join(root, f) - relative_path = os.path.relpath(abs_path, path) + abs_path = path.secure_join(root, f) + relative_path = os.path.relpath(abs_path, base) zip_file.write(abs_path, relative_path) diff --git a/murano/packages/package_base.py b/murano/packages/package_base.py index 475a55bfa..b3926457c 100644 --- a/murano/packages/package_base.py +++ b/murano/packages/package_base.py @@ -22,6 +22,7 @@ import sys import semantic_version import six +from murano.common.helpers import path from murano.common.i18n import _ from murano.packages import exceptions from murano.packages import package @@ -119,13 +120,13 @@ class PackageBase(package.Package): self._supplier.get('Logo'), 'supplier_logo.png', 'supplier logo') def get_resource(self, name): - resources_dir = os.path.join(self._source_directory, 'Resources') + resources_dir = path.secure_join(self._source_directory, 'Resources') if not os.path.exists(resources_dir): os.makedirs(resources_dir) - return os.path.join(resources_dir, name) + return path.secure_join(resources_dir, name) def _load_image(self, file_name, default_name, what_image): - full_path = os.path.join( + full_path = path.secure_join( self._source_directory, file_name or default_name) if not os.path.isfile(full_path) and not file_name: return