diff --git a/util/safetar.py b/util/safetar.py new file mode 100644 index 000000000..ce9119054 --- /dev/null +++ b/util/safetar.py @@ -0,0 +1,52 @@ +import os.path +import logging +import copy +import operator + + +logger = logging.getLogger(__name__) + + +def safe_extractall(tar, path): + """Extract all members from the archive to the current working + directory and set owner, modification time and permissions on + directories afterwards. `path' specifies a different directory + to extract to. `members' is optional and must be a subset of the + list returned by getmembers(). + """ + abs_extract_path = os.path.abspath(path) + def is_member_safe(member): + """ Returns True if the member is safe, False otherwise. """ + if member.issym(): + link_tar_path = os.path.dirname(member.path) + abs_link_path = os.path.abspath(os.path.join(path, link_tar_path, member.linkpath)) + if not abs_link_path.startswith(abs_extract_path): + logger.warning('Filtering symlink outside of extract dir: %s', member.linkpath) + return False + elif member.isblk(): + logger.warning('Filtering block device from tarfile: %s', member.path) + return False + + return True + + directories = [] + + for tarinfo in tar: + if is_member_safe(tarinfo): + if tarinfo.isdir(): + # Extract directories with a safe mode. + directories.append(tarinfo) + tarinfo = copy.copy(tarinfo) + tarinfo.mode = 0700 + tar.extract(tarinfo, path) + + # Reverse sort directories. + directories.sort(key=operator.attrgetter('name')) + directories.reverse() + + # Set correct owner, mtime and filemode on directories. + for tarinfo in directories: + dirpath = os.path.join(path, tarinfo.name) + tar.chown(tarinfo, dirpath) + tar.utime(tarinfo, dirpath) + tar.chmod(tarinfo, dirpath) diff --git a/workers/dockerfilebuild.py b/workers/dockerfilebuild.py index f44642ff2..9055d0152 100644 --- a/workers/dockerfilebuild.py +++ b/workers/dockerfilebuild.py @@ -17,6 +17,7 @@ from data.queue import dockerfile_build_queue from data import model from workers.worker import Worker from app import app +from util.safetar import safe_extractall root_logger = logging.getLogger('') @@ -152,8 +153,14 @@ class DockerfileBuildContext(object): for status in build_status: fully_unwrapped = "" if isinstance(status, dict): - if len(status) > 0: - fully_unwrapped = status.values()[0] + keys_to_extract = ['error', 'status', 'stream'] + for key in keys_to_extract: + if key in status: + fully_unwrapped = status[key] + break + + if not fully_unwrapped: + logger.debug('Status dict did not have any extractable keys and was: %s', status) elif isinstance(status, basestring): fully_unwrapped = status @@ -322,7 +329,7 @@ class DockerfileBuildWorker(Worker): # Save the zip file to temp somewhere with tarfile.open(mode='r|*', fileobj=request_file.raw) as tar_stream: - tar_stream.extractall(build_dir) + safe_extractall(tar_stream, build_dir) return build_dir